From ebdd2f583eedee9cd22c1b7c6ca57c03e0b3e1e1 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 May 2025 15:10:07 +0200 Subject: [PATCH] Use `toVector` for XML literal sequences In ``, a `NodeBuffer` (which extends `ArrayBuffer`) is used to accumulate the children. The buffer is passed to `new Elem($buf: _*)`, which only works thanks to the implicit `collection.Seq[Node] => NodeSeq` declared in scala-xml. With `-Vprint:typer`: ```scala scala> [[syntax trees at end of typer]] // rs$line$1 val res0: scala.xml.Elem = { new _root_.scala.xml.Elem(null, "a", _root_.scala.xml.Null, scala.xml.TopScope, false, { val $buf: scala.xml.NodeBuffer = new _root_.scala.xml.NodeBuffer() $buf.&+( { { new _root_.scala.xml.Elem(null, "b", _root_.scala.xml.Null, scala.xml.TopScope, true, [ : scala.xml.Node]*) } } ) scala.xml.NodeSeq.seqToNodeSeq($buf) }* ) } ``` The implicit was not inserted in 2.12 because the varargs parameter of Elem accepted a `collection.Seq`. --- .../dotc/parsing/xml/MarkupParsers.scala | 2 +- .../dotc/parsing/xml/SymbolicXMLBuilder.scala | 14 ++++++----- tests/run/xml.scala | 25 ++++++++++++++++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala index 06e8645b82c0..84e77547662c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala @@ -376,7 +376,7 @@ object MarkupParsers { content_LT(ts) charComingAfter(xSpaceOpt()) == '<' } do () - handle.makeXMLseq(Span(start, curOffset, start), ts) + handle.makeXMLseq(Span(start, curOffset, start), ts, toVector = false) } else { assert(ts.length == 1, "Require one tree") diff --git a/compiler/src/dotty/tools/dotc/parsing/xml/SymbolicXMLBuilder.scala b/compiler/src/dotty/tools/dotc/parsing/xml/SymbolicXMLBuilder.scala index d1f2875064d4..2db7b0d46d0b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/xml/SymbolicXMLBuilder.scala +++ b/compiler/src/dotty/tools/dotc/parsing/xml/SymbolicXMLBuilder.scala @@ -59,12 +59,13 @@ class SymbolicXMLBuilder(parser: Parser, preserveWS: Boolean)(using Context) { val _plus: TermName = "&+" val _tmpscope: TermName = "$tmpscope" val _xml: TermName = "xml" + val _toVector: TermName = "toVector" } import xmltypes.{_Comment, _Elem, _EntityRef, _Group, _MetaData, _NamespaceBinding, _NodeBuffer, _PrefixedAttribute, _ProcInstr, _Text, _Unparsed, _UnprefixedAttribute} - import xmlterms.{_Null, __Elem, __Text, _buf, _md, _plus, _scope, _tmpscope, _xml} + import xmlterms.{_Null, __Elem, __Text, _buf, _md, _plus, _scope, _tmpscope, _xml, _toVector} // convenience methods private def LL[A](x: A*): List[List[A]] = List(x.toList) @@ -103,7 +104,7 @@ class SymbolicXMLBuilder(parser: Parser, preserveWS: Boolean)(using Context) { { def starArgs = if (children.isEmpty) Nil - else List(Typed(makeXMLseq(span, children), wildStar)) + else List(Typed(makeXMLseq(span, children, toVector = true), wildStar)) def pat = Apply(_scala_xml__Elem, List(pre, label, wild, wild) ::: convertToTextPat(children)) def nonpat = New(_scala_xml_Elem, List(List(pre, label, attrs, scope, if (empty) Literal(Constant(true)) else Literal(Constant(false))) ::: starArgs)) @@ -152,7 +153,7 @@ class SymbolicXMLBuilder(parser: Parser, preserveWS: Boolean)(using Context) { ts match { case Nil => TypedSplice(tpd.ref(defn.NilModule).withSpan(span)) case t :: Nil => t - case _ => makeXMLseq(span, ts) + case _ => makeXMLseq(span, ts, toVector = true) } } @@ -162,11 +163,12 @@ class SymbolicXMLBuilder(parser: Parser, preserveWS: Boolean)(using Context) { } /** could optimize if args.length == 0, args.length == 1 AND args(0) is <: Node. */ - def makeXMLseq(span: Span, args: collection.Seq[Tree]): Block = { + def makeXMLseq(span: Span, args: collection.Seq[Tree], toVector: Boolean): Block = { val buffer = ValDef(_buf, TypeTree(), New(_scala_xml_NodeBuffer, ListOfNil)) val applies = args filterNot isEmptyText map (t => Apply(Select(Ident(_buf), _plus), List(t))) - atSpan(span)(new XMLBlock(buffer :: applies.toList, Ident(_buf)) ) + val res = if (toVector) Select(Ident(_buf), _toVector) else Ident(_buf) + atSpan(span)(new XMLBlock(buffer :: applies.toList, res)) } /** Returns (Some(prefix) | None, rest) based on position of ':' */ @@ -177,7 +179,7 @@ class SymbolicXMLBuilder(parser: Parser, preserveWS: Boolean)(using Context) { /** Various node constructions. */ def group(span: Span, args: collection.Seq[Tree]): Tree = - atSpan(span)( New(_scala_xml_Group, LL(makeXMLseq(span, args))) ) + atSpan(span)( New(_scala_xml_Group, LL(makeXMLseq(span, args, toVector = true))) ) def unparsed(span: Span, str: String): Tree = atSpan(span)( New(_scala_xml_Unparsed, LL(const(str))) ) diff --git a/tests/run/xml.scala b/tests/run/xml.scala index a1ff9998ec93..ffcb24bdb412 100644 --- a/tests/run/xml.scala +++ b/tests/run/xml.scala @@ -1,12 +1,21 @@ object Test { - import scala.xml.NodeBuffer + import scala.xml._ def main(args: Array[String]): Unit = { val xml = world assert(xml.toString == "helloworld") - val nodeBuffer: NodeBuffer = - assert(nodeBuffer.mkString == "helloworld") + val sq: NodeBuffer = + assert(sq.mkString == "helloworld") + + val subSq: Node = + assert(subSq.child.toString == "Vector(b, c)") // implementation detail + + val attrSeq: Elem = + assert(attrSeq.attributes.asInstanceOf[UnprefixedAttribute].value.toString == "Vector(txt, &entityref;, txt)") + + val g: Group = + assert(g.nodes.toString == "Vector(a, b, c)") } } package scala.xml { @@ -20,7 +29,7 @@ package scala.xml { def child: Seq[Node] override def toString = label + child.mkString } - class Elem(prefix: String, val label: String, attributes1: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, val child: Node*) extends Node + class Elem(prefix: String, val label: String, val attributes: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, val child: Node*) extends Node class NodeBuffer extends Seq[Node] { val nodes = scala.collection.mutable.ArrayBuffer.empty[Node] def &+(o: Any): NodeBuffer = @@ -39,4 +48,12 @@ package scala.xml { def label = t.text def child = Nil } + + case class UnprefixedAttribute(key: String, value: Seq[Node], next: MetaData) + case class EntityRef(entityName: String) extends Node { + def label = s"&$entityName;" + def child = Nil + } + + case class Group(nodes: Seq[Node]) }