Skip to content

Commit c2c76c8

Browse files
authored
Merge pull request scala#760 from lrytz/imm-child
Change Node.child and Node.attribute return type to immutable.Seq on 2.13+
2 parents 3c4a4af + d04f3c3 commit c2c76c8

27 files changed

+194
-95
lines changed

build.sbt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
1+
import com.typesafe.tools.mima.core._
2+
import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject}
23

34
publish / skip := true // root project
45

@@ -68,6 +69,51 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform)
6869
//import com.typesafe.tools.mima.core.{}
6970
//import com.typesafe.tools.mima.core.ProblemFilters
7071
Seq( // exclusions for all Scala versions
72+
// new method in `Node` with return type `immutable.Seq`
73+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Node.child"),
74+
75+
// these used to be declared methods, but are now bridges without generic signature
76+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.nonEmptyChildren"),
77+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Group.child"),
78+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.SpecialNode.child"),
79+
80+
// new methods with return type immutable.Seq
81+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Attribute.apply"),
82+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.Attribute.value"),
83+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.MetaData.apply"),
84+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.MetaData.value"),
85+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.xml.NodeSeq.theSeq"),
86+
87+
// Synthetic static accessors (for Java interop) have a changed return type
88+
ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Null.apply"),
89+
ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Null.value"),
90+
ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Utility.parseAttributeValue"),
91+
ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.xml.Utility.trimProper"),
92+
93+
// used to be a declared method, now a bridge without generic signature
94+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.MetaData.apply"),
95+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Null.apply"),
96+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Null.value"),
97+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.PrefixedAttribute.apply"),
98+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.PrefixedAttribute.value"),
99+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.UnprefixedAttribute.apply"),
100+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.UnprefixedAttribute.value"),
101+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Document.theSeq"),
102+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Group.theSeq"),
103+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.theSeq"),
104+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.TextBuffer.toText"),
105+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Utility.trimProper"),
106+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Utility.parseAttributeValue"),
107+
108+
// Option[c.Seq] => Option[i.Seq] results in a changed generic signature
109+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.MetaData.get"),
110+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Null.get"),
111+
ProblemFilters.exclude[IncompatibleSignatureProblem]("scala.xml.Node.attribute"),
112+
113+
// trait Attribute now extends trait ScalaVersionSpecificMetaData to ensure the previous signatures
114+
// with return type `collection.Seq` remain valid.
115+
// (trait Attribute extends MetaData, but that parent is not present in bytecode because it's a class.)
116+
ProblemFilters.exclude[InheritedNewAbstractMethodProblem]("scala.xml.Attribute.apply"),
71117
) ++ (CrossVersion.partialVersion(scalaVersion.value) match {
72118
case Some((3, _)) => Seq( // Scala 3-specific exclusions
73119
)

jvm/src/test/scala/scala/xml/SerializationTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class SerializationTest {
2525
def implicitConversion(): Unit = {
2626
val parent: Elem = <parent><child></child><child/></parent>
2727
val children: Seq[Node] = parent.child
28-
val asNodeSeq: NodeSeq = children
28+
val asNodeSeq: NodeSeq = children // implicit seqToNodeSeq
2929
assertEquals(asNodeSeq, JavaByteSerialization.roundTrip(asNodeSeq))
3030
}
3131
}

shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ private[xml] object ScalaVersionSpecific {
2222
override def apply(from: Coll): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder
2323
override def apply(): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder
2424
}
25-
type SeqNodeUnapplySeq = scala.collection.Seq[Node]
25+
type SeqOfNode = scala.collection.Seq[Node]
26+
type SeqOfText = scala.collection.Seq[Text]
2627
}
2728

2829
private[xml] trait ScalaVersionSpecificNodeSeq extends SeqLike[Node, NodeSeq] { self: NodeSeq =>
@@ -33,3 +34,11 @@ private[xml] trait ScalaVersionSpecificNodeSeq extends SeqLike[Node, NodeSeq] {
3334
private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer =>
3435
override def stringPrefix: String = "NodeBuffer"
3536
}
37+
38+
private[xml] trait ScalaVersionSpecificNode { self: Node => }
39+
40+
private[xml] trait ScalaVersionSpecificMetaData { self: MetaData => }
41+
42+
private[xml] trait ScalaVersionSpecificTextBuffer { self: TextBuffer => }
43+
44+
private[xml] trait ScalaVersionSpecificUtility { self: Utility.type => }

shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ private[xml] object ScalaVersionSpecific {
2424
def newBuilder(from: Coll): Builder[Node, NodeSeq] = NodeSeq.newBuilder
2525
def fromSpecific(from: Coll)(it: IterableOnce[Node]): NodeSeq = (NodeSeq.newBuilder ++= from).result()
2626
}
27-
type SeqNodeUnapplySeq = scala.collection.immutable.Seq[Node]
27+
type SeqOfNode = scala.collection.immutable.Seq[Node]
28+
type SeqOfText = scala.collection.immutable.Seq[Text]
2829
}
2930

3031
private[xml] trait ScalaVersionSpecificNodeSeq
@@ -48,8 +49,34 @@ private[xml] trait ScalaVersionSpecificNodeSeq
4849
fromSpecific(new View.Map(this, f))
4950
def flatMap(f: Node => IterableOnce[Node]): NodeSeq =
5051
fromSpecific(new View.FlatMap(this, f))
52+
53+
def theSeq: scala.collection.Seq[Node]
5154
}
5255

5356
private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer =>
5457
override def className: String = "NodeBuffer"
5558
}
59+
60+
private[xml] trait ScalaVersionSpecificNode { self: Node =>
61+
// These methods are overridden in Node with return type `immutable.Seq`. The declarations here result
62+
// in a bridge method in `Node` with result type `collection.Seq` which is needed for binary compatibility.
63+
def child: scala.collection.Seq[Node]
64+
def nonEmptyChildren: scala.collection.Seq[Node]
65+
}
66+
67+
private[xml] trait ScalaVersionSpecificMetaData { self: MetaData =>
68+
def apply(key: String): scala.collection.Seq[Node]
69+
def apply(namespace_uri: String, owner: Node, key: String): scala.collection.Seq[Node]
70+
def apply(namespace_uri: String, scp: NamespaceBinding, k: String): scala.collection.Seq[Node]
71+
72+
def value: scala.collection.Seq[Node]
73+
}
74+
75+
private[xml] trait ScalaVersionSpecificTextBuffer { self: TextBuffer =>
76+
def toText: scala.collection.Seq[Text]
77+
}
78+
79+
private[xml] trait ScalaVersionSpecificUtility { self: Utility.type =>
80+
def trimProper(x: Node): scala.collection.Seq[Node]
81+
def parseAttributeValue(value: String): scala.collection.Seq[Node]
82+
}

shared/src/main/scala-2/scala/xml/ScalaVersionSpecificReturnTypes.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ private[xml] object ScalaVersionSpecificReturnTypes { // should be
2828
type NullNext = scala.Null
2929
type NullKey = scala.Null
3030
type NullValue = scala.Null
31-
type NullApply1 = scala.collection.Seq[Node] // scala.Null
3231
type NullApply3 = scala.Null
3332
type NullRemove = Null.type
3433
type SpecialNodeChild = Nil.type

shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,17 @@ package scala.xml
1919
What should have been specified explicitly is given in the comments;
2020
next time we break binary compatibility the types should be changed in the code and this class removed.
2121
*/
22-
private[xml] object ScalaVersionSpecificReturnTypes { // should be
23-
type ExternalIDAttribute = MetaData // Null.type
24-
type NoExternalIDId = String // scala.Null
25-
type NodeNoAttributes = MetaData // Null.type
26-
type NullFilter = MetaData // Null.type
27-
type NullGetNamespace = String // scala.Null
28-
type NullNext = MetaData // scala.Null
29-
type NullKey = String // scala.Null
30-
type NullValue = scala.collection.Seq[Node] // scala.Null
31-
type NullApply1 = scala.collection.Seq[Node] // scala.Null
32-
type NullApply3 = scala.collection.Seq[Node] // scala.Null
33-
type NullRemove = MetaData // Null.type
34-
type SpecialNodeChild = scala.collection.Seq[Node] // Nil.type
35-
type GroupChild = scala.collection.Seq[Node] // Nothing
22+
private[xml] object ScalaVersionSpecificReturnTypes { // should be
23+
type ExternalIDAttribute = MetaData // Null.type
24+
type NoExternalIDId = String // scala.Null
25+
type NodeNoAttributes = MetaData // Null.type
26+
type NullFilter = MetaData // Null.type
27+
type NullGetNamespace = String // scala.Null
28+
type NullNext = MetaData // scala.Null
29+
type NullKey = String // scala.Null
30+
type NullValue = scala.collection.immutable.Seq[Node] // scala.Null
31+
type NullApply3 = scala.collection.immutable.Seq[Node] // scala.Null
32+
type NullRemove = MetaData // Null.type
33+
type SpecialNodeChild = scala.collection.immutable.Seq[Node] // Nil.type
34+
type GroupChild = scala.collection.immutable.Seq[Node] // Nothing
3635
}

shared/src/main/scala/scala/xml/Attribute.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ object Attribute {
5353
*
5454
* @author Burak Emir
5555
*/
56-
trait Attribute extends MetaData {
56+
trait Attribute extends MetaData with ScalaVersionSpecificMetaData {
5757
def pre: String // will be null if unprefixed
5858
override val key: String
59-
override val value: Seq[Node]
59+
override val value: ScalaVersionSpecific.SeqOfNode
6060
override val next: MetaData
6161

62-
override def apply(key: String): Seq[Node]
63-
override def apply(namespace: String, scope: NamespaceBinding, key: String): Seq[Node]
62+
override def apply(key: String): ScalaVersionSpecific.SeqOfNode
63+
override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecific.SeqOfNode
6464
override def copy(next: MetaData): Attribute
6565

6666
override def remove(key: String): MetaData =

shared/src/main/scala/scala/xml/Document.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Document extends NodeSeq with Serializable {
3636
* excluded. If there is a document type declaration, the list also
3737
* contains a document type declaration information item.
3838
*/
39-
var children: Seq[Node] = _
39+
var children: Seq[Node] = _ // effectively an `immutable.Seq`, not changed due to binary compatibility
4040

4141
/** The element information item corresponding to the document element. */
4242
var docElem: Node = _
@@ -96,7 +96,7 @@ class Document extends NodeSeq with Serializable {
9696

9797
// methods for NodeSeq
9898

99-
override def theSeq: Seq[Node] = this.docElem
99+
override def theSeq: ScalaVersionSpecific.SeqOfNode = this.docElem
100100

101101
override def canEqual(other: Any): Boolean = other match {
102102
case _: Document => true

shared/src/main/scala/scala/xml/Elem.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ object Elem {
2727
def apply(prefix: String, label: String, attributes: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, child: Node*): Elem =
2828
new Elem(prefix, label, attributes, scope, minimizeEmpty, child: _*)
2929

30-
def unapplySeq(n: Node): Option[(String, String, MetaData, NamespaceBinding, ScalaVersionSpecific.SeqNodeUnapplySeq)] =
30+
def unapplySeq(n: Node): Option[(String, String, MetaData, NamespaceBinding, ScalaVersionSpecific.SeqOfNode)] =
3131
n match {
3232
case _: SpecialNode | _: Group => None
33-
case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child.toSeq))
33+
case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child))
3434
}
3535
}
3636

@@ -104,7 +104,7 @@ class Elem(
104104
scope: NamespaceBinding = this.scope,
105105
minimizeEmpty: Boolean = this.minimizeEmpty,
106106
child: Seq[Node] = this.child
107-
): Elem = Elem(prefix, label, attributes, scope, minimizeEmpty, child: _*)
107+
): Elem = Elem(prefix, label, attributes, scope, minimizeEmpty, child.toSeq: _*)
108108

109109
/**
110110
* Returns concatenation of `text(n)` for each child `n`.

shared/src/main/scala/scala/xml/Group.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import scala.collection.Seq
2222
*/
2323
// Note: used by the Scala compiler.
2424
final case class Group(nodes: Seq[Node]) extends Node {
25-
override def theSeq: Seq[Node] = nodes
25+
// Ideally, the `immutable.Seq` would be stored as a field.
26+
// But evolving the case class and remaining binary compatible is very difficult
27+
// Since `Group` is used rarely, call `toSeq` on the field.
28+
// In practice, it should not matter - the `nodes` field anyway contains an `immutable.Seq`.
29+
override def theSeq: ScalaVersionSpecific.SeqOfNode = nodes.toSeq
2630

2731
override def canEqual(other: Any): Boolean = other match {
2832
case _: Group => true

0 commit comments

Comments
 (0)