From eeb49a3633abf925b37b2498cb7dbb090eb40ac6 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 19 May 2025 13:16:33 +0200 Subject: [PATCH 1/8] Change Node.child return type to immutable.Seq on 2.13+ Change the type of `Node.child` and `Node.nonEmptyChildren` from `collection.Seq` to `immutable.Seq` in 2.13+. A parent is added to `Node` which defines the old signatures for `child` / `nonEmptyChildren`. This (and the resulting bridge methods) ensures binary compatbility. --- build.sbt | 9 +++++- .../scala/xml/ScalaVersionSpecific.scala | 4 +++ .../scala/xml/ScalaVersionSpecific.scala | 9 ++++++ .../xml/ScalaVersionSpecificReturnTypes.scala | 28 +++++++++---------- shared/src/main/scala/scala/xml/Node.scala | 6 ++-- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/build.sbt b/build.sbt index 9ba74983..a2b78dbf 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,5 @@ -import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType} +import com.typesafe.tools.mima.core._ +import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} publish / skip := true // root project @@ -68,6 +69,12 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform) //import com.typesafe.tools.mima.core.{} //import com.typesafe.tools.mima.core.ProblemFilters Seq( // exclusions for all Scala versions + // new method in `Node` with return type `immutable.Seq` + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Node.child"), + // these used to be declared methods, but are now bridges without generic signature + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.nonEmptyChildren"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Group.child"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.SpecialNode.child"), ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { case Some((3, _)) => Seq( // Scala 3-specific exclusions ) diff --git a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala index b74e3183..d865ad84 100644 --- a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala @@ -23,6 +23,8 @@ private[xml] object ScalaVersionSpecific { override def apply(): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder } type SeqNodeUnapplySeq = scala.collection.Seq[Node] + + type ChildReturnType = scala.collection.Seq[Node] } private[xml] trait ScalaVersionSpecificNodeSeq extends SeqLike[Node, NodeSeq] { self: NodeSeq => @@ -33,3 +35,5 @@ private[xml] trait ScalaVersionSpecificNodeSeq extends SeqLike[Node, NodeSeq] { private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer => override def stringPrefix: String = "NodeBuffer" } + +private[xml] trait ScalaVersionSpecificNode {self: Node => } diff --git a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala index e08d9ad9..8441dcfa 100644 --- a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala @@ -25,6 +25,8 @@ private[xml] object ScalaVersionSpecific { def fromSpecific(from: Coll)(it: IterableOnce[Node]): NodeSeq = (NodeSeq.newBuilder ++= from).result() } type SeqNodeUnapplySeq = scala.collection.immutable.Seq[Node] + + type ChildReturnType = scala.collection.immutable.Seq[Node] } private[xml] trait ScalaVersionSpecificNodeSeq @@ -53,3 +55,10 @@ private[xml] trait ScalaVersionSpecificNodeSeq private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer => override def className: String = "NodeBuffer" } + +private[xml] trait ScalaVersionSpecificNode { self: Node => + // These methods are overridden in Node with return type `immutable.Seq`. The declarations here result + // in a bridge method in `Node` with result type `collection.Seq` which is needed for binary compatibility. + def child: scala.collection.Seq[Node] + def nonEmptyChildren: scala.collection.Seq[Node] +} diff --git a/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala b/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala index 8e7fcdfc..83bc29f8 100644 --- a/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala +++ b/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala @@ -19,18 +19,18 @@ package scala.xml What should have been specified explicitly is given in the comments; next time we break binary compatibility the types should be changed in the code and this class removed. */ -private[xml] object ScalaVersionSpecificReturnTypes { // should be - type ExternalIDAttribute = MetaData // Null.type - type NoExternalIDId = String // scala.Null - type NodeNoAttributes = MetaData // Null.type - type NullFilter = MetaData // Null.type - type NullGetNamespace = String // scala.Null - type NullNext = MetaData // scala.Null - type NullKey = String // scala.Null - type NullValue = scala.collection.Seq[Node] // scala.Null - type NullApply1 = scala.collection.Seq[Node] // scala.Null - type NullApply3 = scala.collection.Seq[Node] // scala.Null - type NullRemove = MetaData // Null.type - type SpecialNodeChild = scala.collection.Seq[Node] // Nil.type - type GroupChild = scala.collection.Seq[Node] // Nothing +private[xml] object ScalaVersionSpecificReturnTypes { // should be + type ExternalIDAttribute = MetaData // Null.type + type NoExternalIDId = String // scala.Null + type NodeNoAttributes = MetaData // Null.type + type NullFilter = MetaData // Null.type + type NullGetNamespace = String // scala.Null + type NullNext = MetaData // scala.Null + type NullKey = String // scala.Null + type NullValue = scala.collection.Seq[Node] // scala.Null + type NullApply1 = scala.collection.Seq[Node] // scala.Null + type NullApply3 = scala.collection.Seq[Node] // scala.Null + type NullRemove = MetaData // Null.type + type SpecialNodeChild = scala.collection.immutable.Seq[Node] // Nil.type + type GroupChild = scala.collection.immutable.Seq[Node] // Nothing } diff --git a/shared/src/main/scala/scala/xml/Node.scala b/shared/src/main/scala/scala/xml/Node.scala index ca1d6379..8f6de627 100755 --- a/shared/src/main/scala/scala/xml/Node.scala +++ b/shared/src/main/scala/scala/xml/Node.scala @@ -45,7 +45,7 @@ object Node { * * @author Burak Emir and others */ -abstract class Node extends NodeSeq { +abstract class Node extends NodeSeq with ScalaVersionSpecificNode { /** prefix of this node */ def prefix: String = null @@ -120,12 +120,12 @@ abstract class Node extends NodeSeq { * * @return all children of this node */ - def child: Seq[Node] + def child: ScalaVersionSpecific.ChildReturnType /** * Children which do not stringify to "" (needed for equality) */ - def nonEmptyChildren: Seq[Node] = child.filterNot(_.toString.isEmpty) + def nonEmptyChildren: ScalaVersionSpecific.ChildReturnType = child.filterNot(_.toString.isEmpty) /** * Descendant axis (all descendants of this node, not including node itself) From b1675758ae5c951847a1aa220565223c10812e53 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 20 May 2025 11:41:52 +0200 Subject: [PATCH 2/8] single SeqOfNode alias in ScalaVersionSpecific --- .../main/scala-2.12/scala/xml/ScalaVersionSpecific.scala | 4 +--- .../main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala | 4 +--- shared/src/main/scala/scala/xml/Elem.scala | 2 +- shared/src/main/scala/scala/xml/Node.scala | 6 +++--- shared/src/main/scala/scala/xml/QNode.scala | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala index d865ad84..b472ea39 100644 --- a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala @@ -22,9 +22,7 @@ private[xml] object ScalaVersionSpecific { override def apply(from: Coll): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder override def apply(): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder } - type SeqNodeUnapplySeq = scala.collection.Seq[Node] - - type ChildReturnType = scala.collection.Seq[Node] + type SeqOfNode = scala.collection.Seq[Node] } private[xml] trait ScalaVersionSpecificNodeSeq extends SeqLike[Node, NodeSeq] { self: NodeSeq => diff --git a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala index 8441dcfa..0853f72b 100644 --- a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala @@ -24,9 +24,7 @@ private[xml] object ScalaVersionSpecific { def newBuilder(from: Coll): Builder[Node, NodeSeq] = NodeSeq.newBuilder def fromSpecific(from: Coll)(it: IterableOnce[Node]): NodeSeq = (NodeSeq.newBuilder ++= from).result() } - type SeqNodeUnapplySeq = scala.collection.immutable.Seq[Node] - - type ChildReturnType = scala.collection.immutable.Seq[Node] + type SeqOfNode = scala.collection.immutable.Seq[Node] } private[xml] trait ScalaVersionSpecificNodeSeq diff --git a/shared/src/main/scala/scala/xml/Elem.scala b/shared/src/main/scala/scala/xml/Elem.scala index bd420e53..9dcc1835 100755 --- a/shared/src/main/scala/scala/xml/Elem.scala +++ b/shared/src/main/scala/scala/xml/Elem.scala @@ -27,7 +27,7 @@ object Elem { def apply(prefix: String, label: String, attributes: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, child: Node*): Elem = new Elem(prefix, label, attributes, scope, minimizeEmpty, child: _*) - def unapplySeq(n: Node): Option[(String, String, MetaData, NamespaceBinding, ScalaVersionSpecific.SeqNodeUnapplySeq)] = + def unapplySeq(n: Node): Option[(String, String, MetaData, NamespaceBinding, ScalaVersionSpecific.SeqOfNode)] = n match { case _: SpecialNode | _: Group => None case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child.toSeq)) diff --git a/shared/src/main/scala/scala/xml/Node.scala b/shared/src/main/scala/scala/xml/Node.scala index 8f6de627..76a6d739 100755 --- a/shared/src/main/scala/scala/xml/Node.scala +++ b/shared/src/main/scala/scala/xml/Node.scala @@ -28,7 +28,7 @@ object Node { /** the empty namespace */ val EmptyNamespace: String = "" - def unapplySeq(n: Node): Some[(String, MetaData, ScalaVersionSpecific.SeqNodeUnapplySeq)] = + def unapplySeq(n: Node): Some[(String, MetaData, ScalaVersionSpecific.SeqOfNode)] = Some((n.label, n.attributes, n.child.toSeq)) } @@ -120,12 +120,12 @@ abstract class Node extends NodeSeq with ScalaVersionSpecificNode { * * @return all children of this node */ - def child: ScalaVersionSpecific.ChildReturnType + def child: ScalaVersionSpecific.SeqOfNode /** * Children which do not stringify to "" (needed for equality) */ - def nonEmptyChildren: ScalaVersionSpecific.ChildReturnType = child.filterNot(_.toString.isEmpty) + def nonEmptyChildren: ScalaVersionSpecific.SeqOfNode = child.filterNot(_.toString.isEmpty) /** * Descendant axis (all descendants of this node, not including node itself) diff --git a/shared/src/main/scala/scala/xml/QNode.scala b/shared/src/main/scala/scala/xml/QNode.scala index c5128eb4..212554ac 100644 --- a/shared/src/main/scala/scala/xml/QNode.scala +++ b/shared/src/main/scala/scala/xml/QNode.scala @@ -20,6 +20,6 @@ package xml * @author Burak Emir */ object QNode { - def unapplySeq(n: Node): Some[(String, String, MetaData, ScalaVersionSpecific.SeqNodeUnapplySeq)] = + def unapplySeq(n: Node): Some[(String, String, MetaData, ScalaVersionSpecific.SeqOfNode)] = Some((n.scope.getURI(n.prefix), n.label, n.attributes, n.child.toSeq)) } From 3861cbd0bf60de4f7ebff465cec9b9c83c7c8451 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 20 May 2025 16:54:16 +0200 Subject: [PATCH 3/8] remove / clarify usages of seqToNodeSeq --- .../scala/scala/xml/SerializationTest.scala | 2 +- shared/src/main/scala/scala/xml/Elem.scala | 2 +- .../src/main/scala/scala/xml/MetaData.scala | 2 +- shared/src/main/scala/scala/xml/Utility.scala | 11 +++++----- .../scala/scala/xml/factory/XMLLoader.scala | 20 +++++++++---------- .../xml/parsing/NoBindingFactoryAdapter.scala | 2 +- .../xml/transform/BasicTransformer.scala | 6 +++--- .../test/scala/scala/xml/AttributeTest.scala | 4 ++-- .../test/scala/scala/xml/NodeSeqTest.scala | 9 ++++----- .../test/scala/scala/xml/ShouldCompile.scala | 2 +- shared/src/test/scala/scala/xml/XMLTest.scala | 2 +- 11 files changed, 31 insertions(+), 31 deletions(-) diff --git a/jvm/src/test/scala/scala/xml/SerializationTest.scala b/jvm/src/test/scala/scala/xml/SerializationTest.scala index e8c5c880..f795daae 100644 --- a/jvm/src/test/scala/scala/xml/SerializationTest.scala +++ b/jvm/src/test/scala/scala/xml/SerializationTest.scala @@ -25,7 +25,7 @@ class SerializationTest { def implicitConversion(): Unit = { val parent: Elem = val children: Seq[Node] = parent.child - val asNodeSeq: NodeSeq = children + val asNodeSeq: NodeSeq = children // implicit seqToNodeSeq assertEquals(asNodeSeq, JavaByteSerialization.roundTrip(asNodeSeq)) } } diff --git a/shared/src/main/scala/scala/xml/Elem.scala b/shared/src/main/scala/scala/xml/Elem.scala index 9dcc1835..90e41752 100755 --- a/shared/src/main/scala/scala/xml/Elem.scala +++ b/shared/src/main/scala/scala/xml/Elem.scala @@ -104,7 +104,7 @@ class Elem( scope: NamespaceBinding = this.scope, minimizeEmpty: Boolean = this.minimizeEmpty, child: Seq[Node] = this.child - ): Elem = Elem(prefix, label, attributes, scope, minimizeEmpty, child: _*) + ): Elem = Elem(prefix, label, attributes, scope, minimizeEmpty, child.toSeq: _*) /** * Returns concatenation of `text(n)` for each child `n`. diff --git a/shared/src/main/scala/scala/xml/MetaData.scala b/shared/src/main/scala/scala/xml/MetaData.scala index 97931133..f51c9567 100644 --- a/shared/src/main/scala/scala/xml/MetaData.scala +++ b/shared/src/main/scala/scala/xml/MetaData.scala @@ -183,7 +183,7 @@ abstract class MetaData * Returns a Map containing the attributes stored as key/value pairs. */ def asAttrMap: Map[String, String] = - iterator.map(x => (x.prefixedKey, x.value.text)).toMap + iterator.map(x => (x.prefixedKey, NodeSeq.fromSeq(x.value).text)).toMap /** returns Null or the next MetaData item */ def next: MetaData diff --git a/shared/src/main/scala/scala/xml/Utility.scala b/shared/src/main/scala/scala/xml/Utility.scala index ff8a2fb4..08b3ae59 100755 --- a/shared/src/main/scala/scala/xml/Utility.scala +++ b/shared/src/main/scala/scala/xml/Utility.scala @@ -17,6 +17,7 @@ import scala.annotation.tailrec import scala.collection.mutable import scala.language.implicitConversions import scala.collection.Seq +import scala.collection.immutable.{Seq => ISeq} /** * The `Utility` object provides utility functions for processing instances @@ -51,12 +52,12 @@ object Utility extends AnyRef with parsing.TokenTests { */ def trim(x: Node): Node = x match { case Elem(pre, lab, md, scp, child@_*) => - val children: Seq[Node] = combineAdjacentTextNodes(child).flatMap(trimProper) + val children = combineAdjacentTextNodes(child).flatMap(trimProper) Elem(pre, lab, md, scp, children.isEmpty, children: _*) } - private def combineAdjacentTextNodes(children: Seq[Node]): Seq[Node] = - children.foldRight(Seq.empty[Node]) { + private def combineAdjacentTextNodes(children: ScalaVersionSpecific.SeqOfNode): ScalaVersionSpecific.SeqOfNode = + children.foldRight(ISeq.empty[Node]) { case (Text(left), Text(right) +: nodes) => Text(left + right) +: nodes case (n, nodes) => n +: nodes } @@ -67,7 +68,7 @@ object Utility extends AnyRef with parsing.TokenTests { */ def trimProper(x: Node): Seq[Node] = x match { case Elem(pre, lab, md, scp, child@_*) => - val children: Seq[Node] = combineAdjacentTextNodes(child).flatMap(trimProper) + val children = combineAdjacentTextNodes(child).flatMap(trimProper) Elem(pre, lab, md, scp, children.isEmpty, children: _*) case Text(s) => new TextBuffer().append(s).toText @@ -89,7 +90,7 @@ object Utility extends AnyRef with parsing.TokenTests { */ def sort(n: Node): Node = n match { case Elem(pre, lab, md, scp, child@_*) => - val children: Seq[Node] = child.map(sort) + val children = child.map(sort) Elem(pre, lab, sort(md), scp, children.isEmpty, children: _*) case _ => n } diff --git a/shared/src/main/scala/scala/xml/factory/XMLLoader.scala b/shared/src/main/scala/scala/xml/factory/XMLLoader.scala index afe54330..251027c1 100644 --- a/shared/src/main/scala/scala/xml/factory/XMLLoader.scala +++ b/shared/src/main/scala/scala/xml/factory/XMLLoader.scala @@ -58,7 +58,7 @@ trait XMLLoader[T <: Node] { // TODO remove def loadXML(inputSource: InputSource, parser: SAXParser): T = getDocElem(adapter.loadDocument(inputSource, parser.getXMLReader)) - def loadXMLNodes(inputSource: InputSource, parser: SAXParser): Seq[Node] = adapter.loadDocument(inputSource, parser.getXMLReader).children + def loadXMLNodes(inputSource: InputSource, parser: SAXParser): Seq[Node] = adapter.loadDocument(inputSource, parser.getXMLReader).children.toSeq def adapter: parsing.FactoryAdapter = new parsing.NoBindingFactoryAdapter() /** Loads XML Document. */ @@ -85,13 +85,13 @@ trait XMLLoader[T <: Node] { def loadString(string: String): T = getDocElem(loadStringDocument(string)) /** Load XML nodes, including comments and processing instructions that precede and follow the root element. */ - def loadNodes(inputSource: InputSource): Seq[Node] = loadDocument(inputSource).children - def loadFileNodes(fileName: String): Seq[Node] = loadFileDocument(fileName).children - def loadFileNodes(file: File): Seq[Node] = loadFileDocument(file).children - def loadNodes(url: URL): Seq[Node] = loadDocument(url).children - def loadNodes(sysId: String): Seq[Node] = loadDocument(sysId).children - def loadFileNodes(fileDescriptor: FileDescriptor): Seq[Node] = loadFileDocument(fileDescriptor).children - def loadNodes(inputStream: InputStream): Seq[Node] = loadDocument(inputStream).children - def loadNodes(reader: Reader): Seq[Node] = loadDocument(reader).children - def loadStringNodes(string: String): Seq[Node] = loadStringDocument(string).children + def loadNodes(inputSource: InputSource): Seq[Node] = loadDocument(inputSource).children.toSeq + def loadFileNodes(fileName: String): Seq[Node] = loadFileDocument(fileName).children.toSeq + def loadFileNodes(file: File): Seq[Node] = loadFileDocument(file).children.toSeq + def loadNodes(url: URL): Seq[Node] = loadDocument(url).children.toSeq + def loadNodes(sysId: String): Seq[Node] = loadDocument(sysId).children.toSeq + def loadFileNodes(fileDescriptor: FileDescriptor): Seq[Node] = loadFileDocument(fileDescriptor).children.toSeq + def loadNodes(inputStream: InputStream): Seq[Node] = loadDocument(inputStream).children.toSeq + def loadNodes(reader: Reader): Seq[Node] = loadDocument(reader).children.toSeq + def loadStringNodes(string: String): Seq[Node] = loadStringDocument(string).children.toSeq } diff --git a/shared/src/main/scala/scala/xml/parsing/NoBindingFactoryAdapter.scala b/shared/src/main/scala/scala/xml/parsing/NoBindingFactoryAdapter.scala index bd57b40c..fcc522a6 100644 --- a/shared/src/main/scala/scala/xml/parsing/NoBindingFactoryAdapter.scala +++ b/shared/src/main/scala/scala/xml/parsing/NoBindingFactoryAdapter.scala @@ -27,7 +27,7 @@ class NoBindingFactoryAdapter extends FactoryAdapter with NodeFactory[Elem] { /** From NodeFactory. Constructs an instance of scala.xml.Elem -- TODO: deprecate as in Elem */ override protected def create(pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, children: Seq[Node]): Elem = - Elem(pre, label, attrs, scope, children.isEmpty, children: _*) + Elem(pre, label, attrs, scope, children.isEmpty, children.toSeq: _*) /** From FactoryAdapter. Creates a node. never creates the same node twice, using hash-consing. TODO: deprecate as in Elem, or forward to create?? */ diff --git a/shared/src/main/scala/scala/xml/transform/BasicTransformer.scala b/shared/src/main/scala/scala/xml/transform/BasicTransformer.scala index 7ad543b4..14484916 100644 --- a/shared/src/main/scala/scala/xml/transform/BasicTransformer.scala +++ b/shared/src/main/scala/scala/xml/transform/BasicTransformer.scala @@ -46,11 +46,11 @@ abstract class BasicTransformer extends (Node => Node) { if (n.doTransform) n match { case Group(xs) => Group(transform(xs)) // un-group the hack Group tag case _ => - val ch: Seq[Node] = n.child - val nch: Seq[Node] = transform(ch) + val ch = n.child + val nch = transform(ch) if (ch.eq(nch)) n - else Elem(n.prefix, n.label, n.attributes, n.scope, nch.isEmpty, nch: _*) + else Elem(n.prefix, n.label, n.attributes, n.scope, nch.isEmpty, nch.toSeq: _*) } else n diff --git a/shared/src/test/scala/scala/xml/AttributeTest.scala b/shared/src/test/scala/scala/xml/AttributeTest.scala index 5f1a6134..90ea3b0a 100644 --- a/shared/src/test/scala/scala/xml/AttributeTest.scala +++ b/shared/src/test/scala/scala/xml/AttributeTest.scala @@ -167,7 +167,7 @@ class AttributeTest { @Test(expected=classOf[IllegalArgumentException]) def invalidAttributeFailForMany(): Unit = { - .child \ "@" + .child \ "@" // implicit seqToNodeSeq } @Test(expected=classOf[IllegalArgumentException]) @@ -177,6 +177,6 @@ class AttributeTest { @Test(expected=classOf[IllegalArgumentException]) def invalidEmptyAttributeFailForMany(): Unit = { - .child \@ "" + .child \@ "" // implicit seqToNodeSeq } } diff --git a/shared/src/test/scala/scala/xml/NodeSeqTest.scala b/shared/src/test/scala/scala/xml/NodeSeqTest.scala index 558a77a7..a5808e79 100644 --- a/shared/src/test/scala/scala/xml/NodeSeqTest.scala +++ b/shared/src/test/scala/scala/xml/NodeSeqTest.scala @@ -1,6 +1,5 @@ package scala.xml -import scala.xml.NodeSeq.seqToNodeSeq import org.junit.Test import org.junit.Assert.assertEquals import org.junit.Assert.fail @@ -30,7 +29,7 @@ class NodeSeqTest { case res: Seq[Node] => assertEquals(2, res.size.toLong) case _: NodeSeq => fail("Should be Seq[Node] was NodeSeq") // Unreachable code? } - val res: NodeSeq = a :+ b + val res: NodeSeq = a :+ b // implicit seqToNodeSeq val exp: NodeSeq = NodeSeq.fromSeq(Seq(Hello, Hi)) assertEquals(exp, res) } @@ -59,7 +58,7 @@ class NodeSeqTest { case res: Seq[Node] => assertEquals(3, res.size.toLong) case _: NodeSeq => fail("Should be Seq[Node] was NodeSeq") // Unreachable code? } - val res: NodeSeq = a ++: b ++: c + val res: NodeSeq = a ++: b ++: c // implicit seqToNodeSeq val exp: NodeSeq = NodeSeq.fromSeq(Seq(Hello, Hi, Hey)) assertEquals(exp, res) } @@ -67,7 +66,7 @@ class NodeSeqTest { @Test def testMap(): Unit = { val a: NodeSeq = Hello - val exp: NodeSeq = Seq(Hi) + val exp: NodeSeq = Seq(Hi) // implicit seqToNodeSeq assertEquals(exp, a.map(_ => Hi)) assertEquals(exp, for { _ <- a } yield { Hi }) } @@ -75,7 +74,7 @@ class NodeSeqTest { @Test def testFlatMap(): Unit = { val a: NodeSeq = Hello - val exp: NodeSeq = Seq(Hi) + val exp: NodeSeq = Seq(Hi) // implicit seqToNodeSeq assertEquals(exp, a.flatMap(_ => Seq(Hi))) assertEquals(exp, for { b <- a; _ <- b } yield { Hi }) assertEquals(exp, for { b <- a; c <- b; _ <- c } yield { Hi }) diff --git a/shared/src/test/scala/scala/xml/ShouldCompile.scala b/shared/src/test/scala/scala/xml/ShouldCompile.scala index df8a237d..63dd91fc 100644 --- a/shared/src/test/scala/scala/xml/ShouldCompile.scala +++ b/shared/src/test/scala/scala/xml/ShouldCompile.scala @@ -70,7 +70,7 @@ class Floozy { object guardedMatch { // SI-3705 // guard caused verifyerror in oldpatmat -- TODO: move this to compiler test suite - def updateNodes(ns: Seq[Node]): Seq[Node] = + def updateNodes(ns: ScalaVersionSpecific.SeqOfNode): ScalaVersionSpecific.SeqOfNode = for (subnode <- ns) yield subnode match { case { _ } if true => abc case Elem(prefix, label, attribs, scope, children @ _*) => diff --git a/shared/src/test/scala/scala/xml/XMLTest.scala b/shared/src/test/scala/scala/xml/XMLTest.scala index 989ca02e..746d58d6 100644 --- a/shared/src/test/scala/scala/xml/XMLTest.scala +++ b/shared/src/test/scala/scala/xml/XMLTest.scala @@ -521,7 +521,7 @@ Ours is the portal of hope, come as you are." @UnitTest def i1976(): Unit = { val node: Elem = { "whatever " } - assertEquals("whatever ", node.child.text) + assertEquals("whatever ", node.child.text) // implicit seqToNodeSeq } @UnitTest From 4d58e59260ec9a82035d4a83d6edf5c7c85e0c0f Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 20 May 2025 17:00:58 +0200 Subject: [PATCH 4/8] Don't pass NodeBuffer as Seq[Node] --- shared/src/main/scala/scala/xml/Document.scala | 2 +- shared/src/main/scala/scala/xml/Utility.scala | 3 ++- shared/src/main/scala/scala/xml/parsing/MarkupParser.scala | 7 +++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/main/scala/scala/xml/Document.scala b/shared/src/main/scala/scala/xml/Document.scala index e242794f..b1cd23f2 100644 --- a/shared/src/main/scala/scala/xml/Document.scala +++ b/shared/src/main/scala/scala/xml/Document.scala @@ -36,7 +36,7 @@ class Document extends NodeSeq with Serializable { * excluded. If there is a document type declaration, the list also * contains a document type declaration information item. */ - var children: Seq[Node] = _ + var children: Seq[Node] = _ // effectively an `immutable.Seq`, not changed due to binary compatibility /** The element information item corresponding to the document element. */ var docElem: Node = _ diff --git a/shared/src/main/scala/scala/xml/Utility.scala b/shared/src/main/scala/scala/xml/Utility.scala index 08b3ae59..3a3d3654 100755 --- a/shared/src/main/scala/scala/xml/Utility.scala +++ b/shared/src/main/scala/scala/xml/Utility.scala @@ -354,6 +354,7 @@ object Utility extends AnyRef with parsing.TokenTests { null } + // unused, untested def parseAttributeValue(value: String): Seq[Node] = { val sb: StringBuilder = new StringBuilder var rfb: StringBuilder = null @@ -398,7 +399,7 @@ object Utility extends AnyRef with parsing.TokenTests { else nb += x } - nb + nb.toVector } /** diff --git a/shared/src/main/scala/scala/xml/parsing/MarkupParser.scala b/shared/src/main/scala/scala/xml/parsing/MarkupParser.scala index 2ca4773c..618133a2 100755 --- a/shared/src/main/scala/scala/xml/parsing/MarkupParser.scala +++ b/shared/src/main/scala/scala/xml/parsing/MarkupParser.scala @@ -242,7 +242,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests { } nextch() // is prolog ? - var children: NodeSeq = null + var children: Seq[Node] = null if ('?' == ch) { nextch() info_prolog = prolog() @@ -255,7 +255,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests { val ts: NodeBuffer = new NodeBuffer() content1(TopScope, ts) // DTD handled as side effect ts &+ content(TopScope) - children = NodeSeq.fromSeq(ts) + children = ts.toVector } //println("[MarkupParser::document] children now: "+children.toList) var elemCount: Int = 0 @@ -451,8 +451,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests { def content(pscope: NamespaceBinding): NodeSeq = { val ts: NodeBuffer = new NodeBuffer var exit: Boolean = eof - // todo: optimize seq repr. - def done: NodeSeq = NodeSeq.fromSeq(ts.toList) + def done: NodeSeq = NodeSeq.fromSeq(ts.toVector) while (!exit) { tmppos = pos From 4c20a1799a867bcc8e65deb33baad2688e34db22 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 May 2025 10:19:58 +0200 Subject: [PATCH 5/8] Use immtuable.Seq for attributes --- build.sbt | 30 +++++++++++++++++++ .../scala/xml/ScalaVersionSpecific.scala | 4 ++- .../scala/xml/ScalaVersionSpecific.scala | 8 +++++ .../xml/ScalaVersionSpecificReturnTypes.scala | 1 - .../xml/ScalaVersionSpecificReturnTypes.scala | 5 ++-- .../src/main/scala/scala/xml/Attribute.scala | 8 ++--- shared/src/main/scala/scala/xml/Elem.scala | 2 +- .../src/main/scala/scala/xml/MetaData.scala | 15 +++++----- shared/src/main/scala/scala/xml/Node.scala | 6 ++-- shared/src/main/scala/scala/xml/Null.scala | 2 +- .../scala/scala/xml/PrefixedAttribute.scala | 8 +++-- shared/src/main/scala/scala/xml/QNode.scala | 2 +- .../scala/scala/xml/UnprefixedAttribute.scala | 8 +++-- 13 files changed, 71 insertions(+), 28 deletions(-) diff --git a/build.sbt b/build.sbt index a2b78dbf..20167efd 100644 --- a/build.sbt +++ b/build.sbt @@ -71,10 +71,40 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform) Seq( // exclusions for all Scala versions // new method in `Node` with return type `immutable.Seq` ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Node.child"), + // these used to be declared methods, but are now bridges without generic signature ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.nonEmptyChildren"), ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Group.child"), ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.SpecialNode.child"), + + // new methods with return type immutable.Seq + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Attribute.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Attribute.value"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.MetaData.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.MetaData.value"), + + // Synthetic static accessors (for Java interop) have a changed return type + ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Null.apply"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Null.value"), + + // used to be a declared method, now a bridge without generic signature + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.MetaData.apply"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Null.apply"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Null.value"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.PrefixedAttribute.apply"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.PrefixedAttribute.value"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.UnprefixedAttribute.apply"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.UnprefixedAttribute.value"), + + // Option[c.Seq] => Option[i.Seq] results in a changed generic signature + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.MetaData.get"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Null.get"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.attribute"), + + // trait Attribute now extends trait ScalaVersionSpecificMetaData to ensure the previous signatures + // with return type `collection.Seq` remain valid. + // (trait Attribute extends MetaData, but that parent is not present in bytecode because it's a class.) + ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("scala.xml.Attribute.apply"), ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { case Some((3, _)) => Seq( // Scala 3-specific exclusions ) diff --git a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala index b472ea39..f90feadd 100644 --- a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala @@ -34,4 +34,6 @@ private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer => override def stringPrefix: String = "NodeBuffer" } -private[xml] trait ScalaVersionSpecificNode {self: Node => } +private[xml] trait ScalaVersionSpecificNode { self: Node => } + +private[xml] trait ScalaVersionSpecificMetaData { self: MetaData => } diff --git a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala index 0853f72b..5a4d4e72 100644 --- a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala @@ -60,3 +60,11 @@ private[xml] trait ScalaVersionSpecificNode { self: Node => def child: scala.collection.Seq[Node] def nonEmptyChildren: scala.collection.Seq[Node] } + +private[xml] trait ScalaVersionSpecificMetaData { self: MetaData => + def apply(key: String): scala.collection.Seq[Node] + def apply(namespace_uri: String, owner: Node, key: String): scala.collection.Seq[Node] + def apply(namespace_uri: String, scp: NamespaceBinding, k: String): scala.collection.Seq[Node] + + def value: scala.collection.Seq[Node] +} diff --git a/shared/src/main/scala-2/scala/xml/ScalaVersionSpecificReturnTypes.scala b/shared/src/main/scala-2/scala/xml/ScalaVersionSpecificReturnTypes.scala index 22820dea..6d40d40f 100644 --- a/shared/src/main/scala-2/scala/xml/ScalaVersionSpecificReturnTypes.scala +++ b/shared/src/main/scala-2/scala/xml/ScalaVersionSpecificReturnTypes.scala @@ -28,7 +28,6 @@ private[xml] object ScalaVersionSpecificReturnTypes { // should be type NullNext = scala.Null type NullKey = scala.Null type NullValue = scala.Null - type NullApply1 = scala.collection.Seq[Node] // scala.Null type NullApply3 = scala.Null type NullRemove = Null.type type SpecialNodeChild = Nil.type diff --git a/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala b/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala index 83bc29f8..e6dce41c 100644 --- a/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala +++ b/shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala @@ -27,9 +27,8 @@ private[xml] object ScalaVersionSpecificReturnTypes { // should be type NullGetNamespace = String // scala.Null type NullNext = MetaData // scala.Null type NullKey = String // scala.Null - type NullValue = scala.collection.Seq[Node] // scala.Null - type NullApply1 = scala.collection.Seq[Node] // scala.Null - type NullApply3 = scala.collection.Seq[Node] // scala.Null + type NullValue = scala.collection.immutable.Seq[Node] // scala.Null + type NullApply3 = scala.collection.immutable.Seq[Node] // scala.Null type NullRemove = MetaData // Null.type type SpecialNodeChild = scala.collection.immutable.Seq[Node] // Nil.type type GroupChild = scala.collection.immutable.Seq[Node] // Nothing diff --git a/shared/src/main/scala/scala/xml/Attribute.scala b/shared/src/main/scala/scala/xml/Attribute.scala index 97457ab7..1c7c3575 100644 --- a/shared/src/main/scala/scala/xml/Attribute.scala +++ b/shared/src/main/scala/scala/xml/Attribute.scala @@ -53,14 +53,14 @@ object Attribute { * * @author Burak Emir */ -trait Attribute extends MetaData { +trait Attribute extends MetaData with ScalaVersionSpecificMetaData { def pre: String // will be null if unprefixed override val key: String - override val value: Seq[Node] + override val value: ScalaVersionSpecific.SeqOfNode override val next: MetaData - override def apply(key: String): Seq[Node] - override def apply(namespace: String, scope: NamespaceBinding, key: String): Seq[Node] + override def apply(key: String): ScalaVersionSpecific.SeqOfNode + override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecific.SeqOfNode override def copy(next: MetaData): Attribute override def remove(key: String): MetaData = diff --git a/shared/src/main/scala/scala/xml/Elem.scala b/shared/src/main/scala/scala/xml/Elem.scala index 90e41752..6b77f164 100755 --- a/shared/src/main/scala/scala/xml/Elem.scala +++ b/shared/src/main/scala/scala/xml/Elem.scala @@ -30,7 +30,7 @@ object Elem { def unapplySeq(n: Node): Option[(String, String, MetaData, NamespaceBinding, ScalaVersionSpecific.SeqOfNode)] = n match { case _: SpecialNode | _: Group => None - case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child.toSeq)) + case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child)) } } diff --git a/shared/src/main/scala/scala/xml/MetaData.scala b/shared/src/main/scala/scala/xml/MetaData.scala index f51c9567..6bdd080f 100644 --- a/shared/src/main/scala/scala/xml/MetaData.scala +++ b/shared/src/main/scala/scala/xml/MetaData.scala @@ -85,6 +85,7 @@ abstract class MetaData with Iterable[MetaData] with Equality with Serializable + with ScalaVersionSpecificMetaData { private[xml] def isNull: Boolean = this.eq(Null) @@ -106,7 +107,7 @@ abstract class MetaData * @param key * @return value as Seq[Node] if key is found, null otherwise */ - def apply(key: String): Seq[Node] + def apply(key: String): ScalaVersionSpecific.SeqOfNode /** * convenience method, same as `apply(namespace, owner.scope, key)`. @@ -115,7 +116,7 @@ abstract class MetaData * @param owner the element owning this attribute list * @param key the attribute key */ - final def apply(namespace_uri: String, owner: Node, key: String): Seq[Node] = + final def apply(namespace_uri: String, owner: Node, key: String): ScalaVersionSpecific.SeqOfNode = apply(namespace_uri, owner.scope, key) /** @@ -126,7 +127,7 @@ abstract class MetaData * @param k to be looked for * @return value as Seq[Node] if key is found, null otherwise */ - def apply(namespace_uri: String, scp: NamespaceBinding, k: String): Seq[Node] + def apply(namespace_uri: String, scp: NamespaceBinding, k: String): ScalaVersionSpecific.SeqOfNode /** * returns a copy of this MetaData item with next field set to argument. @@ -168,7 +169,7 @@ abstract class MetaData def key: String /** returns value of this MetaData item */ - def value: Seq[Node] + def value: ScalaVersionSpecific.SeqOfNode /** * Returns a String containing "prefix:key" if the first key is @@ -194,10 +195,10 @@ abstract class MetaData * @param key * @return value in Some(Seq[Node]) if key is found, None otherwise */ - final def get(key: String): Option[Seq[Node]] = Option(apply(key)) + final def get(key: String): Option[ScalaVersionSpecific.SeqOfNode] = Option(apply(key)) /** same as get(uri, owner.scope, key) */ - final def get(uri: String, owner: Node, key: String): Option[Seq[Node]] = + final def get(uri: String, owner: Node, key: String): Option[ScalaVersionSpecific.SeqOfNode] = get(uri, owner.scope, key) /** @@ -208,7 +209,7 @@ abstract class MetaData * @param key to be looked fore * @return value as `Some[Seq[Node]]` if key is found, None otherwise */ - final def get(uri: String, scope: NamespaceBinding, key: String): Option[Seq[Node]] = + final def get(uri: String, scope: NamespaceBinding, key: String): Option[ScalaVersionSpecific.SeqOfNode] = Option(apply(uri, scope, key)) protected def toString1: String = sbToString(toString1) diff --git a/shared/src/main/scala/scala/xml/Node.scala b/shared/src/main/scala/scala/xml/Node.scala index 76a6d739..d44dbd4e 100755 --- a/shared/src/main/scala/scala/xml/Node.scala +++ b/shared/src/main/scala/scala/xml/Node.scala @@ -29,7 +29,7 @@ object Node { val EmptyNamespace: String = "" def unapplySeq(n: Node): Some[(String, MetaData, ScalaVersionSpecific.SeqOfNode)] = - Some((n.label, n.attributes, n.child.toSeq)) + Some((n.label, n.attributes, n.child)) } /** @@ -92,7 +92,7 @@ abstract class Node extends NodeSeq with ScalaVersionSpecificNode { * @return value of `UnprefixedAttribute` with given key * in attributes, if it exists, otherwise `null`. */ - final def attribute(key: String): Option[Seq[Node]] = attributes.get(key) + final def attribute(key: String): Option[ScalaVersionSpecific.SeqOfNode] = attributes.get(key) /** * Convenience method, looks up a prefixed attribute in attributes of this node. @@ -103,7 +103,7 @@ abstract class Node extends NodeSeq with ScalaVersionSpecificNode { * @return value of `PrefixedAttribute` with given namespace * and given key, otherwise `'''null'''`. */ - final def attribute(uri: String, key: String): Option[Seq[Node]] = + final def attribute(uri: String, key: String): Option[ScalaVersionSpecific.SeqOfNode] = attributes.get(uri, this, key) /** diff --git a/shared/src/main/scala/scala/xml/Null.scala b/shared/src/main/scala/scala/xml/Null.scala index 5613a462..c8e35640 100644 --- a/shared/src/main/scala/scala/xml/Null.scala +++ b/shared/src/main/scala/scala/xml/Null.scala @@ -49,7 +49,7 @@ case object Null extends MetaData { override protected def basisForHashCode: Seq[Any] = Nil override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecificReturnTypes.NullApply3 = null - override def apply(key: String): ScalaVersionSpecificReturnTypes.NullApply1 = + override def apply(key: String): ScalaVersionSpecific.SeqOfNode = if (Utility.isNameStart(key.head)) null else throw new IllegalArgumentException(s"not a valid attribute name '$key', so can never match !") diff --git a/shared/src/main/scala/scala/xml/PrefixedAttribute.scala b/shared/src/main/scala/scala/xml/PrefixedAttribute.scala index 3a3985fe..fcd60c49 100644 --- a/shared/src/main/scala/scala/xml/PrefixedAttribute.scala +++ b/shared/src/main/scala/scala/xml/PrefixedAttribute.scala @@ -27,11 +27,13 @@ import scala.collection.Seq class PrefixedAttribute( override val pre: String, override val key: String, - override val value: Seq[Node], + _value: Seq[Node], val next1: MetaData ) extends Attribute { + override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value.toSeq + override val next: MetaData = if (value != null) next1 else next1.remove(key) /** same as this(pre, key, Text(value), next), or no attribute if value is null */ @@ -53,12 +55,12 @@ class PrefixedAttribute( owner.getNamespace(pre) /** forwards the call to next (because caller looks for unprefixed attribute */ - override def apply(key: String): Seq[Node] = next(key) + override def apply(key: String): ScalaVersionSpecific.SeqOfNode = next(key) /** * gets attribute value of qualified (prefixed) attribute with given key */ - override def apply(namespace: String, scope: NamespaceBinding, key: String): Seq[Node] = + override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecific.SeqOfNode = if (key == this.key && scope.getURI(pre) == namespace) value else diff --git a/shared/src/main/scala/scala/xml/QNode.scala b/shared/src/main/scala/scala/xml/QNode.scala index 212554ac..ddf7818c 100644 --- a/shared/src/main/scala/scala/xml/QNode.scala +++ b/shared/src/main/scala/scala/xml/QNode.scala @@ -21,5 +21,5 @@ package xml */ object QNode { def unapplySeq(n: Node): Some[(String, String, MetaData, ScalaVersionSpecific.SeqOfNode)] = - Some((n.scope.getURI(n.prefix), n.label, n.attributes, n.child.toSeq)) + Some((n.scope.getURI(n.prefix), n.label, n.attributes, n.child)) } diff --git a/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala b/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala index 0f044680..870833ab 100644 --- a/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala +++ b/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala @@ -23,11 +23,13 @@ import scala.collection.Seq // Note: used by the Scala compiler. class UnprefixedAttribute( override val key: String, - override val value: Seq[Node], + _value: Seq[Node], next1: MetaData ) extends Attribute { + override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value.toSeq + final override val pre: scala.Null = null override val next: MetaData = if (value != null) next1 else next1.remove(key) @@ -50,7 +52,7 @@ class UnprefixedAttribute( * @param key * @return value as Seq[Node] if key is found, null otherwise */ - override def apply(key: String): Seq[Node] = + override def apply(key: String): ScalaVersionSpecific.SeqOfNode = if (key == this.key) value else next(key) /** @@ -61,7 +63,7 @@ class UnprefixedAttribute( * @param key * @return .. */ - override def apply(namespace: String, scope: NamespaceBinding, key: String): Seq[Node] = + override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecific.SeqOfNode = next(namespace, scope, key) } object UnprefixedAttribute { From 0023e9395b2924a0fc5f523c8f324a1a427d39ea Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 May 2025 12:03:18 +0200 Subject: [PATCH 6/8] Change theSeq to immtuable.Seq --- build.sbt | 4 ++++ .../src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala | 2 ++ shared/src/main/scala/scala/xml/Document.scala | 2 +- shared/src/main/scala/scala/xml/Group.scala | 2 +- shared/src/main/scala/scala/xml/Node.scala | 2 +- shared/src/main/scala/scala/xml/NodeSeq.scala | 4 ++-- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 20167efd..d58433ef 100644 --- a/build.sbt +++ b/build.sbt @@ -82,6 +82,7 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform) ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Attribute.value"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.MetaData.apply"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.MetaData.value"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.NodeSeq.theSeq"), // Synthetic static accessors (for Java interop) have a changed return type ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Null.apply"), @@ -95,6 +96,9 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform) ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.PrefixedAttribute.value"), ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.UnprefixedAttribute.apply"), ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.UnprefixedAttribute.value"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Document.theSeq"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Group.theSeq"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.theSeq"), // Option[c.Seq] => Option[i.Seq] results in a changed generic signature ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.MetaData.get"), diff --git a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala index 5a4d4e72..5c5aa272 100644 --- a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala @@ -48,6 +48,8 @@ private[xml] trait ScalaVersionSpecificNodeSeq fromSpecific(new View.Map(this, f)) def flatMap(f: Node => IterableOnce[Node]): NodeSeq = fromSpecific(new View.FlatMap(this, f)) + + def theSeq: scala.collection.Seq[Node] } private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer => diff --git a/shared/src/main/scala/scala/xml/Document.scala b/shared/src/main/scala/scala/xml/Document.scala index b1cd23f2..8fe1c6ae 100644 --- a/shared/src/main/scala/scala/xml/Document.scala +++ b/shared/src/main/scala/scala/xml/Document.scala @@ -96,7 +96,7 @@ class Document extends NodeSeq with Serializable { // methods for NodeSeq - override def theSeq: Seq[Node] = this.docElem + override def theSeq: ScalaVersionSpecific.SeqOfNode = this.docElem override def canEqual(other: Any): Boolean = other match { case _: Document => true diff --git a/shared/src/main/scala/scala/xml/Group.scala b/shared/src/main/scala/scala/xml/Group.scala index 4c8fd7ba..3abc7222 100644 --- a/shared/src/main/scala/scala/xml/Group.scala +++ b/shared/src/main/scala/scala/xml/Group.scala @@ -22,7 +22,7 @@ import scala.collection.Seq */ // Note: used by the Scala compiler. final case class Group(nodes: Seq[Node]) extends Node { - override def theSeq: Seq[Node] = nodes + override def theSeq: ScalaVersionSpecific.SeqOfNode = nodes.toSeq override def canEqual(other: Any): Boolean = other match { case _: Group => true diff --git a/shared/src/main/scala/scala/xml/Node.scala b/shared/src/main/scala/scala/xml/Node.scala index d44dbd4e..d9c41dd1 100755 --- a/shared/src/main/scala/scala/xml/Node.scala +++ b/shared/src/main/scala/scala/xml/Node.scala @@ -166,7 +166,7 @@ abstract class Node extends NodeSeq with ScalaVersionSpecificNode { /** * returns a sequence consisting of only this node */ - override def theSeq: Seq[Node] = this :: Nil + override def theSeq: ScalaVersionSpecific.SeqOfNode = this :: Nil /** * String representation of this node diff --git a/shared/src/main/scala/scala/xml/NodeSeq.scala b/shared/src/main/scala/scala/xml/NodeSeq.scala index bdf316f9..7e37369c 100644 --- a/shared/src/main/scala/scala/xml/NodeSeq.scala +++ b/shared/src/main/scala/scala/xml/NodeSeq.scala @@ -26,7 +26,7 @@ import scala.collection.Seq object NodeSeq { final val Empty: NodeSeq = fromSeq(Nil) def fromSeq(s: Seq[Node]): NodeSeq = new NodeSeq { - override def theSeq: Seq[Node] = s + override def theSeq: ScalaVersionSpecific.SeqOfNode = s.toSeq } // --- @@ -48,7 +48,7 @@ object NodeSeq { * @author Burak Emir */ abstract class NodeSeq extends AbstractSeq[Node] with immutable.Seq[Node] with ScalaVersionSpecificNodeSeq with Equality with Serializable { - def theSeq: Seq[Node] + def theSeq: ScalaVersionSpecific.SeqOfNode override def length: Int = theSeq.length override def iterator: Iterator[Node] = theSeq.iterator From e044d5d53feaf52d36c9f60b20754a704a8c18d1 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 21 May 2025 13:28:46 +0200 Subject: [PATCH 7/8] Use immutable.Seq in utilities --- build.sbt | 5 +++++ .../scala-2.12/scala/xml/ScalaVersionSpecific.scala | 5 +++++ .../scala-2.13+/scala/xml/ScalaVersionSpecific.scala | 10 ++++++++++ shared/src/main/scala/scala/xml/TextBuffer.scala | 7 ++++--- shared/src/main/scala/scala/xml/Utility.scala | 6 +++--- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index d58433ef..a2019981 100644 --- a/build.sbt +++ b/build.sbt @@ -87,6 +87,8 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform) // Synthetic static accessors (for Java interop) have a changed return type ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Null.apply"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Null.value"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Utility.parseAttributeValue"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Utility.trimProper"), // used to be a declared method, now a bridge without generic signature ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.MetaData.apply"), @@ -99,6 +101,9 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform) ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Document.theSeq"), ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Group.theSeq"), ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.theSeq"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.TextBuffer.toText"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Utility.trimProper"), + ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Utility.parseAttributeValue"), // Option[c.Seq] => Option[i.Seq] results in a changed generic signature ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.MetaData.get"), diff --git a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala index f90feadd..71f13003 100644 --- a/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala @@ -23,6 +23,7 @@ private[xml] object ScalaVersionSpecific { override def apply(): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder } type SeqOfNode = scala.collection.Seq[Node] + type SeqOfText = scala.collection.Seq[Text] } private[xml] trait ScalaVersionSpecificNodeSeq extends SeqLike[Node, NodeSeq] { self: NodeSeq => @@ -37,3 +38,7 @@ private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer => private[xml] trait ScalaVersionSpecificNode { self: Node => } private[xml] trait ScalaVersionSpecificMetaData { self: MetaData => } + +private[xml] trait ScalaVersionSpecificTextBuffer { self: TextBuffer => } + +private[xml] trait ScalaVersionSpecificUtility { self: Utility.type => } diff --git a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala index 5c5aa272..49da95b8 100644 --- a/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala +++ b/shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala @@ -25,6 +25,7 @@ private[xml] object ScalaVersionSpecific { def fromSpecific(from: Coll)(it: IterableOnce[Node]): NodeSeq = (NodeSeq.newBuilder ++= from).result() } type SeqOfNode = scala.collection.immutable.Seq[Node] + type SeqOfText = scala.collection.immutable.Seq[Text] } private[xml] trait ScalaVersionSpecificNodeSeq @@ -70,3 +71,12 @@ private[xml] trait ScalaVersionSpecificMetaData { self: MetaData => def value: scala.collection.Seq[Node] } + +private[xml] trait ScalaVersionSpecificTextBuffer { self: TextBuffer => + def toText: scala.collection.Seq[Text] +} + +private[xml] trait ScalaVersionSpecificUtility { self: Utility.type => + def trimProper(x: Node): scala.collection.Seq[Node] + def parseAttributeValue(value: String): scala.collection.Seq[Node] +} diff --git a/shared/src/main/scala/scala/xml/TextBuffer.scala b/shared/src/main/scala/scala/xml/TextBuffer.scala index a56fde47..5492790a 100644 --- a/shared/src/main/scala/scala/xml/TextBuffer.scala +++ b/shared/src/main/scala/scala/xml/TextBuffer.scala @@ -14,6 +14,7 @@ package scala package xml import scala.collection.Seq +import scala.collection.immutable.{Seq => ISeq} import Utility.isSpace object TextBuffer { @@ -26,7 +27,7 @@ object TextBuffer { * appended with the `append` method will be replaced by a single space * character, and leading and trailing space will be removed completely. */ -class TextBuffer { +class TextBuffer extends ScalaVersionSpecificTextBuffer { val sb: StringBuilder = new StringBuilder() /** @@ -45,8 +46,8 @@ class TextBuffer { * * @return the text without whitespaces. */ - def toText: Seq[Text] = sb.toString.trim match { + def toText: ScalaVersionSpecific.SeqOfText = sb.toString.trim match { case "" => Nil - case s => Seq(Text(s)) + case s => ISeq(Text(s)) } } diff --git a/shared/src/main/scala/scala/xml/Utility.scala b/shared/src/main/scala/scala/xml/Utility.scala index 3a3d3654..cf85be14 100755 --- a/shared/src/main/scala/scala/xml/Utility.scala +++ b/shared/src/main/scala/scala/xml/Utility.scala @@ -25,7 +25,7 @@ import scala.collection.immutable.{Seq => ISeq} * * @author Burak Emir */ -object Utility extends AnyRef with parsing.TokenTests { +object Utility extends AnyRef with parsing.TokenTests with ScalaVersionSpecificUtility { final val SU: Char = '\u001A' // [Martin] This looks dubious. We don't convert StringBuilders to @@ -66,7 +66,7 @@ object Utility extends AnyRef with parsing.TokenTests { * trim a child of an element. `Attribute` values and `Atom` nodes that * are not `Text` nodes are unaffected. */ - def trimProper(x: Node): Seq[Node] = x match { + def trimProper(x: Node): ScalaVersionSpecific.SeqOfNode = x match { case Elem(pre, lab, md, scp, child@_*) => val children = combineAdjacentTextNodes(child).flatMap(trimProper) Elem(pre, lab, md, scp, children.isEmpty, children: _*) @@ -355,7 +355,7 @@ object Utility extends AnyRef with parsing.TokenTests { } // unused, untested - def parseAttributeValue(value: String): Seq[Node] = { + def parseAttributeValue(value: String): ScalaVersionSpecific.SeqOfNode = { val sb: StringBuilder = new StringBuilder var rfb: StringBuilder = null val nb: NodeBuffer = new NodeBuffer() From 1399695869771abf5123a8f54b3ea494d00eb45e Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 23 May 2025 10:40:58 +0200 Subject: [PATCH 8/8] Use Vector for non-immutable collecitons --- shared/src/main/scala/scala/xml/NodeSeq.scala | 5 ++++- shared/src/main/scala/scala/xml/PrefixedAttribute.scala | 5 ++++- shared/src/main/scala/scala/xml/UnprefixedAttribute.scala | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/shared/src/main/scala/scala/xml/NodeSeq.scala b/shared/src/main/scala/scala/xml/NodeSeq.scala index 7e37369c..07cbb8a6 100644 --- a/shared/src/main/scala/scala/xml/NodeSeq.scala +++ b/shared/src/main/scala/scala/xml/NodeSeq.scala @@ -26,7 +26,10 @@ import scala.collection.Seq object NodeSeq { final val Empty: NodeSeq = fromSeq(Nil) def fromSeq(s: Seq[Node]): NodeSeq = new NodeSeq { - override def theSeq: ScalaVersionSpecific.SeqOfNode = s.toSeq + override def theSeq: ScalaVersionSpecific.SeqOfNode = s match { + case ns: ScalaVersionSpecific.SeqOfNode => ns + case _ => s.toVector + } } // --- diff --git a/shared/src/main/scala/scala/xml/PrefixedAttribute.scala b/shared/src/main/scala/scala/xml/PrefixedAttribute.scala index fcd60c49..1dc6ba12 100644 --- a/shared/src/main/scala/scala/xml/PrefixedAttribute.scala +++ b/shared/src/main/scala/scala/xml/PrefixedAttribute.scala @@ -32,7 +32,10 @@ class PrefixedAttribute( ) extends Attribute { - override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value.toSeq + override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value match { + case ns: ScalaVersionSpecific.SeqOfNode => ns + case _ => _value.toVector + } override val next: MetaData = if (value != null) next1 else next1.remove(key) diff --git a/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala b/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala index 870833ab..877a59b2 100644 --- a/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala +++ b/shared/src/main/scala/scala/xml/UnprefixedAttribute.scala @@ -28,7 +28,10 @@ class UnprefixedAttribute( ) extends Attribute { - override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value.toSeq + override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value match { + case ns: ScalaVersionSpecific.SeqOfNode => ns + case _ => _value.toVector + } final override val pre: scala.Null = null override val next: MetaData = if (value != null) next1 else next1.remove(key)