From 1fc27dffa74ef1878082728777084c8dc39b9296 Mon Sep 17 00:00:00 2001 From: i10416 Date: Thu, 25 Jan 2024 23:35:35 +0900 Subject: [PATCH 1/5] fix(#16459): cannot parse text in xml literal with newline The parser could not parse `if expr` that contains single-quoted text(s) inside XML literal with newline(s) because `followedByToken`, which is used to detect `do` or `then` token after `if`, unintentionally consumed XMLSTART symbol, which prevented the parser from delegating parse to XML parser. --- .../dotty/tools/dotc/parsing/Parsers.scala | 1 + tests/run/i16459.scala | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/run/i16459.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 18294a28b4a1..d1e077275d82 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -906,6 +906,7 @@ object Parsers { var braces = 0 while (true) { val token = lookahead.token + if (token == XMLSTART) return false if (braces == 0) { if (token == query) return true if (stopScanTokens.contains(token) || lookahead.isNestedEnd) return false diff --git a/tests/run/i16459.scala b/tests/run/i16459.scala new file mode 100644 index 000000000000..627efc554de9 --- /dev/null +++ b/tests/run/i16459.scala @@ -0,0 +1,65 @@ +object Test { + import scala.xml.* + def main(args: Array[String]): Unit = { + + val xml = if(true) { + + } else
empty
+ + assert( + xml match + case elm: Elem if + elm.label == "script" + && elm.child.length == 1 + && elm.child(0) == Atom(Text("\n 'location.reload()'\n 'foo bar'\n ")) + => true + case _ => false + , + xml + ) + } +} + +package scala.xml { + type MetaData = AnyRef + + class UnprefixedAttribute( + val key: String, + val value: Text, + next1: MetaData + ) extends MetaData + + trait NamespaceBinding + object TopScope extends NamespaceBinding + object Null + abstract class Node { + def label: String + def child: Seq[Node] + override def toString = label + child.mkString + } + class Comment(commentText: String) extends Node{ + def label = commentText + def child = Nil + } + class Elem(prefix: String, val label: String, attributes1: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, val child: Node*) extends Node + class NodeBuffer extends Seq[Node] { + val nodes = scala.collection.mutable.ArrayBuffer.empty[Node] + def &+(o: Any): NodeBuffer = o match { + case n: Node => nodes.addOne(n) ; this + case t: Text => nodes.addOne(Atom(t)) ; this + } + // Members declared in scala.collection.IterableOnce + def iterator: Iterator[scala.xml.Node] = nodes.iterator + // Members declared in scala.collection.SeqOps + def apply(i: Int): scala.xml.Node = nodes(i) + def length: Int = nodes.length + } + case class Text(text: String) + case class Atom(t: Text) extends Node { + def label = t.text + def child = Nil + } +} \ No newline at end of file From c8d7ca1952f2adaa3bfc95bfee778a89c3c264b2 Mon Sep 17 00:00:00 2001 From: i10416 Date: Fri, 26 Jan 2024 02:41:21 +0900 Subject: [PATCH 2/5] tryfix(16459): xml literal in for comprehension xml literal may appear in LHS of for comprehension. The previous fix caused a syntax error for the following code. ```scala val actual: List[Node] = for (case t @ Blabla <- NodeSeq.fromSeq(books.child).toList) yield t ``` --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d1e077275d82..49689715cc5f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -906,7 +906,7 @@ object Parsers { var braces = 0 while (true) { val token = lookahead.token - if (token == XMLSTART) return false + if (query != LARROW && token == XMLSTART) return false if (braces == 0) { if (token == query) return true if (stopScanTokens.contains(token) || lookahead.isNestedEnd) return false From 04238627da1405ca0e17d48b4ee49687fd58e25a Mon Sep 17 00:00:00 2001 From: i10416 Date: Fri, 26 Jan 2024 02:54:13 +0900 Subject: [PATCH 3/5] tryfix(16459): add more test cases I need to come up with good solution for pattern match with `if` guard. --- tests/run/i16459.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/run/i16459.scala b/tests/run/i16459.scala index 627efc554de9..dd4cf14fbc88 100644 --- a/tests/run/i16459.scala +++ b/tests/run/i16459.scala @@ -2,12 +2,18 @@ object Test { import scala.xml.* def main(args: Array[String]): Unit = { - val xml = if(true) { + val singleQuotedTextCase = if(true) { } else
empty
+ + val casePatMatch = for (case t @ FooBar <- Seq(xml)) + yield t + // TODO: This fails + val casePatMatchWithCond = for (case t @ FooBar if true <- Seq(xml)) + yield t assert( xml match @@ -45,6 +51,9 @@ package scala.xml { def child = Nil } class Elem(prefix: String, val label: String, attributes1: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, val child: Node*) extends Node + object Elem { + def unapply(e:Elem):Option[(String,String,Any,Text,Any)] = Some(("dummy","dummy",null,null,null)) + } class NodeBuffer extends Seq[Node] { val nodes = scala.collection.mutable.ArrayBuffer.empty[Node] def &+(o: Any): NodeBuffer = o match { From 4b0fff55a0d0c2e2e24d064c8257b205d262ecec Mon Sep 17 00:00:00 2001 From: i10416 Date: Fri, 26 Jan 2024 04:30:36 +0900 Subject: [PATCH 4/5] tryfix(16459): workaround LARROW, but find a corner-case Confusing LARROW just after XML pattern breaks the parser. --- tests/run/i16459.scala | 53 +++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/tests/run/i16459.scala b/tests/run/i16459.scala index dd4cf14fbc88..2eee132ecd2c 100644 --- a/tests/run/i16459.scala +++ b/tests/run/i16459.scala @@ -2,18 +2,12 @@ object Test { import scala.xml.* def main(args: Array[String]): Unit = { - val singleQuotedTextCase = if(true) { + val xml = if(true) { } else
empty
- - val casePatMatch = for (case t @ FooBar <- Seq(xml)) - yield t - // TODO: This fails - val casePatMatchWithCond = for (case t @ FooBar if true <- Seq(xml)) - yield t assert( xml match @@ -26,7 +20,47 @@ object Test { , xml ) + // Scala 3 syntax + val auxiliary0 = if true then { + + } else
empty
+ + val auxiliary1 = if true then + + else
empty
+ + val auxiliary2 = if true then
A
else
B
+ + // Note: + // This does not pass in Scala 2.12.18 and 2.13.12 + // due to "Sequence argument type annotation `: _*` cannot be used here:" + val auxiliary3 = if(true)
A
else
B
+ + // Note: This passes in Scala 2.12.18 and 2.13.12 too. + val auxiliary4 = if(true)
A
else
B
+ + // Pattern match without guard. + // Note: This passes in Scala 2.12.18 and 2.13.12 too. + val auxiliary5 = for (case _ @
empty
<- Seq(xml)) yield () + // Note: These pass in Scala 2.12.18 and 2.13.12. + val auxiliary6 = for (case _ @
empty
<- Seq(xml)) yield () + val auxiliary7 = for (case _ @
empty
<-Seq(xml)) yield () + // Pattern match with if guard. + // Note: This passes in Scala 2.12.18 and 2.13.12 too. + val auxiliary8 = for (case _ @ FooBar <- Seq(xml) if true) + // Note: These pass in Scala 2.12.18 and 2.13.12. + val auxiliary9 = for (case _ @ FooBar<- Seq(xml) if true) + val auxiliary10 = for (case _ @ FooBar<-Seq(xml) if true) + yield () + } + } package scala.xml { @@ -46,10 +80,7 @@ package scala.xml { def child: Seq[Node] override def toString = label + child.mkString } - class Comment(commentText: String) extends Node{ - def label = commentText - def child = Nil - } + class Elem(prefix: String, val label: String, attributes1: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, val child: Node*) extends Node object Elem { def unapply(e:Elem):Option[(String,String,Any,Text,Any)] = Some(("dummy","dummy",null,null,null)) From 3d156b6257ae7dba6085528a636e58179a7e9b14 Mon Sep 17 00:00:00 2001 From: i10416 Date: Fri, 26 Jan 2024 06:06:42 +0900 Subject: [PATCH 5/5] fix(16459): add patch to syntax error Add a patch to cover the cornercase where xml pattern in parens confuse the parser. Before this commit, the following code compiles, ```scala for (case _ @
empty
<- Seq(xml)) yield () ``` but the following resulted in syntax error. ```scala for (case _ @
empty
<-Seq(xml)) yield () ``` Because `followingIsEnclosedGenerators` always comes after `for` and `(`, I beleive it would not break the parser to early-exit when `XMLSTART` is found. --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 1 + tests/run/i16459.scala | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 49689715cc5f..41e9eeabe370 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -928,6 +928,7 @@ object Parsers { lookahead.nextToken() while (parens != 0 && lookahead.token != EOF) { val token = lookahead.token + if (token == XMLSTART) return true if (token == LPAREN) parens += 1 else if (token == RPAREN) parens -= 1 lookahead.nextToken() diff --git a/tests/run/i16459.scala b/tests/run/i16459.scala index 2eee132ecd2c..2964928d522c 100644 --- a/tests/run/i16459.scala +++ b/tests/run/i16459.scala @@ -54,10 +54,12 @@ object Test { // Pattern match with if guard. // Note: This passes in Scala 2.12.18 and 2.13.12 too. val auxiliary8 = for (case _ @ FooBar <- Seq(xml) if true) + yield () // Note: These pass in Scala 2.12.18 and 2.13.12. val auxiliary9 = for (case _ @ FooBar<- Seq(xml) if true) + yield () val auxiliary10 = for (case _ @ FooBar<-Seq(xml) if true) - yield () + yield () }