Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Change Node.child and Node.attribute return type to immutable.Seq on 2.13+ #760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
lrytz merged 9 commits into scala:main from lrytz:imm-child
May 27, 2025

Conversation

Copy link
Member

@lrytz lrytz commented May 19, 2025
edited
Loading

XML values in Scala are immutable

Change the type of Node.child / Node.nonEmptyChildren and Node.attribute 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.

@lrytz lrytz force-pushed the imm-child branch 3 times, most recently from 6436666 to 08e6c71 Compare May 19, 2025 11:46
Copy link
Member Author

lrytz commented May 19, 2025

If we go ahead with this PR we'll need a new minor release (2.4.0).

There is more API with return type collection.Seq that probably always returns an immutable.Seq, e.g.

  • Metadata.value
  • NodeSeq.theSeq
  • MetaData.apply / MetaData.get / Node.attribute
  • TextBuffer.toText
  • BasicTransformer.transform

I'm happy take a look at those as well; I only did child for now because that's what is affecting the 2.13 migration I'm working on.

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.
Copy link
Member Author

lrytz commented May 19, 2025

For the record, there is an implicit conversion collection.Seq[Node] => NodeSeq and NodeSeq is a subclass of immutable.Seq on 2.13.

So the following works already without this PR

scala> val n: Node = <a><b/></a>
val n: scala.xml.Node = <a><b/></a>
scala> val c: collection.immutable.Seq[Node] = n.child
val c: Seq[scala.xml.Node] = Seq(<b/>)

But this fails:

scala> val c: collection.immutable.Seq[String] = n.child.map(_.toString)
 ^
 error: type mismatch;
 found : Seq[String] (in scala.collection)
 required: Seq[String] (in scala.collection.immutable)

@retronym retronym self-requested a review May 19, 2025 14:10
Copy link
Member Author

lrytz commented May 20, 2025

probably always returns an immutable.Seq

I took a closer look.

MarkupParser passes a NodeBuffer – I'll change that.

The compiler uses a NodeBuffer for XML literals and passes it directly to Elem($buf). That worked on 2.12, so child is actually a mutable collection. On 2.13 the implicit seqToNodeSeq makes it compile. Details here: scala/scala#11065

@lrytz lrytz force-pushed the imm-child branch 4 times, most recently from 68e70b7 to 80c3a0d Compare May 21, 2025 08:48
@lrytz lrytz changed the title (削除) Change Node.child return type to immutable.Seq on 2.13+ (削除ここまで) (追記) Change Node.child and Node.attribute return type to immutable.Seq on 2.13+ (追記ここまで) May 21, 2025
@lrytz lrytz force-pushed the imm-child branch 2 times, most recently from 8631ddb to c60701e Compare May 21, 2025 11:29
Copy link
Member Author

lrytz commented May 21, 2025

I also changed Node.attribute and the related MetaData.apply / MetaData.value / MetaData.get methods to return immutable.Seq.

And I changed NodeSeq.theSeq, even though this method is probably not used often as part of the public API. The reason is that NodeSeq extends immutable.Seq, and if theSeq is not immutable, it is essentially an unsafe immutable wrapper around a generic collection:

scala> val b = new NodeBuffer() += <b/>
scala> val x: NodeSeq = Elem(null, "a", Null, TopScope, true, NodeSeq.fromSeq(b): _*)
val x: scala.xml.NodeSeq = <a><b/></a>
scala> b += <c/>
scala> x
val res0: scala.xml.NodeSeq = <a><b/><c/></a>

I didn't change BasicTransformer.transform because that API is expected to be subclassed, changes would likely cause source incompatibilities.

Copy link
Member Author

lrytz commented May 21, 2025
edited
Loading

This is ready, if you'd like to take a look @dubinsky.

Besides mima, I also used jardiff to compare the changes and it looks as expected.

Copy link
Member

retronym commented May 23, 2025
edited by lrytz
Loading

One minor comment -- it might be more efficient to use VectorBuilder rather than NodeBuffer (which extends ArrayBuffer adding the utility method &+).

Copy link
Member Author

lrytz commented May 23, 2025

Right.. We have to keep NodeBuffer extends ArrayBuffer though, because of source and binary compatibility. So the code emitted by the compiler for literals will keep using ArrayBuffer.

We could use a VectorBuilder within scala-xml on 2.13, but it would involve changing public signatures, e.g. MarkupParser.content1(NamespaceBinding, NodeBuffer): Unit.

So it's probably not worth the effort...

Copy link
Member

Agreed. If we were maximising performance from XML literals we'd be better to use an immutable ArraySeq builder with a size hint as the compiler knows how many elements there are. But maximising performance isn't really a design goal of scala-xml IMO, so the small overhead of ArrayBuffer => Vector is okay.

@lrytz lrytz merged commit c2c76c8 into scala:main May 27, 2025
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@retronym retronym retronym approved these changes

@dubinsky dubinsky dubinsky approved these changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /