Skip to content

Commit c2c76c8

Browse files
authored
Merge pull request #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

shared/src/main/scala/scala/xml/MetaData.scala

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ abstract class MetaData
8585
with Iterable[MetaData]
8686
with Equality
8787
with Serializable
88+
with ScalaVersionSpecificMetaData
8889
{
8990
private[xml] def isNull: Boolean = this.eq(Null)
9091

@@ -106,7 +107,7 @@ abstract class MetaData
106107
* @param key
107108
* @return value as Seq[Node] if key is found, null otherwise
108109
*/
109-
def apply(key: String): Seq[Node]
110+
def apply(key: String): ScalaVersionSpecific.SeqOfNode
110111

111112
/**
112113
* convenience method, same as `apply(namespace, owner.scope, key)`.
@@ -115,7 +116,7 @@ abstract class MetaData
115116
* @param owner the element owning this attribute list
116117
* @param key the attribute key
117118
*/
118-
final def apply(namespace_uri: String, owner: Node, key: String): Seq[Node] =
119+
final def apply(namespace_uri: String, owner: Node, key: String): ScalaVersionSpecific.SeqOfNode =
119120
apply(namespace_uri, owner.scope, key)
120121

121122
/**
@@ -126,7 +127,7 @@ abstract class MetaData
126127
* @param k to be looked for
127128
* @return value as Seq[Node] if key is found, null otherwise
128129
*/
129-
def apply(namespace_uri: String, scp: NamespaceBinding, k: String): Seq[Node]
130+
def apply(namespace_uri: String, scp: NamespaceBinding, k: String): ScalaVersionSpecific.SeqOfNode
130131

131132
/**
132133
* returns a copy of this MetaData item with next field set to argument.
@@ -168,7 +169,7 @@ abstract class MetaData
168169
def key: String
169170

170171
/** returns value of this MetaData item */
171-
def value: Seq[Node]
172+
def value: ScalaVersionSpecific.SeqOfNode
172173

173174
/**
174175
* Returns a String containing "prefix:key" if the first key is
@@ -183,7 +184,7 @@ abstract class MetaData
183184
* Returns a Map containing the attributes stored as key/value pairs.
184185
*/
185186
def asAttrMap: Map[String, String] =
186-
iterator.map(x => (x.prefixedKey, x.value.text)).toMap
187+
iterator.map(x => (x.prefixedKey, NodeSeq.fromSeq(x.value).text)).toMap
187188

188189
/** returns Null or the next MetaData item */
189190
def next: MetaData
@@ -194,10 +195,10 @@ abstract class MetaData
194195
* @param key
195196
* @return value in Some(Seq[Node]) if key is found, None otherwise
196197
*/
197-
final def get(key: String): Option[Seq[Node]] = Option(apply(key))
198+
final def get(key: String): Option[ScalaVersionSpecific.SeqOfNode] = Option(apply(key))
198199

199200
/** same as get(uri, owner.scope, key) */
200-
final def get(uri: String, owner: Node, key: String): Option[Seq[Node]] =
201+
final def get(uri: String, owner: Node, key: String): Option[ScalaVersionSpecific.SeqOfNode] =
201202
get(uri, owner.scope, key)
202203

203204
/**
@@ -208,7 +209,7 @@ abstract class MetaData
208209
* @param key to be looked fore
209210
* @return value as `Some[Seq[Node]]` if key is found, None otherwise
210211
*/
211-
final def get(uri: String, scope: NamespaceBinding, key: String): Option[Seq[Node]] =
212+
final def get(uri: String, scope: NamespaceBinding, key: String): Option[ScalaVersionSpecific.SeqOfNode] =
212213
Option(apply(uri, scope, key))
213214

214215
protected def toString1: String = sbToString(toString1)

shared/src/main/scala/scala/xml/Node.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ object Node {
2828
/** the empty namespace */
2929
val EmptyNamespace: String = ""
3030

31-
def unapplySeq(n: Node): Some[(String, MetaData, ScalaVersionSpecific.SeqNodeUnapplySeq)] =
32-
Some((n.label, n.attributes, n.child.toSeq))
31+
def unapplySeq(n: Node): Some[(String, MetaData, ScalaVersionSpecific.SeqOfNode)] =
32+
Some((n.label, n.attributes, n.child))
3333
}
3434

3535
/**
@@ -45,7 +45,7 @@ object Node {
4545
*
4646
* @author Burak Emir and others
4747
*/
48-
abstract class Node extends NodeSeq {
48+
abstract class Node extends NodeSeq with ScalaVersionSpecificNode {
4949

5050
/** prefix of this node */
5151
def prefix: String = null
@@ -92,7 +92,7 @@ abstract class Node extends NodeSeq {
9292
* @return value of `UnprefixedAttribute` with given key
9393
* in attributes, if it exists, otherwise `null`.
9494
*/
95-
final def attribute(key: String): Option[Seq[Node]] = attributes.get(key)
95+
final def attribute(key: String): Option[ScalaVersionSpecific.SeqOfNode] = attributes.get(key)
9696

9797
/**
9898
* Convenience method, looks up a prefixed attribute in attributes of this node.
@@ -103,7 +103,7 @@ abstract class Node extends NodeSeq {
103103
* @return value of `PrefixedAttribute` with given namespace
104104
* and given key, otherwise `'''null'''`.
105105
*/
106-
final def attribute(uri: String, key: String): Option[Seq[Node]] =
106+
final def attribute(uri: String, key: String): Option[ScalaVersionSpecific.SeqOfNode] =
107107
attributes.get(uri, this, key)
108108

109109
/**
@@ -120,12 +120,12 @@ abstract class Node extends NodeSeq {
120120
*
121121
* @return all children of this node
122122
*/
123-
def child: Seq[Node]
123+
def child: ScalaVersionSpecific.SeqOfNode
124124

125125
/**
126126
* Children which do not stringify to "" (needed for equality)
127127
*/
128-
def nonEmptyChildren: Seq[Node] = child.filterNot(_.toString.isEmpty)
128+
def nonEmptyChildren: ScalaVersionSpecific.SeqOfNode = child.filterNot(_.toString.isEmpty)
129129

130130
/**
131131
* Descendant axis (all descendants of this node, not including node itself)
@@ -166,7 +166,7 @@ abstract class Node extends NodeSeq {
166166
/**
167167
* returns a sequence consisting of only this node
168168
*/
169-
override def theSeq: Seq[Node] = this :: Nil
169+
override def theSeq: ScalaVersionSpecific.SeqOfNode = this :: Nil
170170

171171
/**
172172
* String representation of this node

0 commit comments

Comments
 (0)