From 8be509f63d74c440c4cfe28d9feed196593b9f25 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 11 Jul 2023 17:45:55 +0100 Subject: [PATCH 1/3] Disallow naming the root package, except for selections --- .../dotty/tools/dotc/parsing/Parsers.scala | 15 +++-- .../src/dotty/tools/dotc/typer/Typer.scala | 12 ++-- tests/neg/i18020.scala | 57 +++++++++++++++++++ 3 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 tests/neg/i18020.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index cec7a72f3e8e..970902edc130 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1072,9 +1072,16 @@ object Parsers { } /** Accept identifier and return Ident with its name as a term name. */ - def termIdent(): Ident = + def rawTermIdent(): Ident = makeIdent(in.token, in.offset, ident()) + /** Call `rawTermIdent`, and check it isn't a root package name. */ + def termIdent(): Ident = + val ident = rawTermIdent() + if ident.name == nme.ROOTPKG then + syntaxError(em"Illegal use of root package name.") + ident + /** Accept identifier and return Ident with its name as a type name. */ def typeIdent(): Ident = makeIdent(in.token, in.offset, ident().toTypeName) @@ -1112,7 +1119,7 @@ object Parsers { * | [id ‘.’] ‘this’ * | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id */ - def simpleRef(): Tree = + def simpleRef(allowRoot: Boolean = true): Tree = val start = in.offset def handleThis(qual: Ident) = @@ -1129,7 +1136,7 @@ object Parsers { if in.token == THIS then handleThis(EmptyTypeIdent) else if in.token == SUPER then handleSuper(EmptyTypeIdent) else - val t = termIdent() + val t = if allowRoot then rawTermIdent() else termIdent() if in.token == DOT then def qual = cpy.Ident(t)(t.name.toTypeName) in.lookahead.token match @@ -2965,7 +2972,7 @@ object Parsers { */ def simplePattern(): Tree = in.token match { case IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER => - simpleRef() match + simpleRef(allowRoot = false) match case id @ Ident(nme.raw.MINUS) if isNumericLit => literal(startOffset(id)) case t => simplePatternRest(t) case USCORE => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ee8aa668296d..668489223e93 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -569,7 +569,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // optimization, it also avoids forcing imports thus potentially avoiding // cyclic references. if (name == nme.ROOTPKG) - return tree.withType(defn.RootPackage.termRef) + val tree2 = tree.withType(defn.RootPackage.termRef) + checkLegalValue(tree2, pt) + return tree2 val rawType = val saved1 = unimported @@ -581,13 +583,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if foundUnderScala2.exists && !(foundUnderScala2 =:= found) then report.migrationWarning( em"""Name resolution will change. - | currently selected : $foundUnderScala2 - | in the future, without -source 3.0-migration: $found""", tree.srcPos) + | currently selected : $foundUnderScala2 + | in the future, without -source 3.0-migration: $found""", tree.srcPos) foundUnderScala2 else found finally - unimported = saved1 - foundUnderScala2 = saved2 + unimported = saved1 + foundUnderScala2 = saved2 /** Normally, returns `ownType` except if `ownType` is a constructor proxy, * and there is another shadowed type accessible with the same name that is not: diff --git a/tests/neg/i18020.scala b/tests/neg/i18020.scala new file mode 100644 index 000000000000..bf85cff2cc15 --- /dev/null +++ b/tests/neg/i18020.scala @@ -0,0 +1,57 @@ +import _root_.scala.StringContext // ok + +class Test : + val Foo = 1 + def foo0: Unit = + val x = new _root_.scala.StringContext() // ok + val y: Option[_root_.scala.Serializable] = None // ok + val z: _root_.scala.None.type = None + val w = _root_.scala.None + val (_root_, other) = (1, 2) // error + val (Test.this.Foo, 1) = ??? + ??? match + case (Test.this.Foo, 1) => () + +def foo3 = + val _root_ = "abc" // error + +def foo1: Unit = + val _root_: String = "abc" // error // error + // _root_: is, technically, a legal name + // so then it tries to construct the infix op pattern + // "_root_ String .." and then throws in a null when it fails + // to find an argument + // then Typer rejects "String" as an infix extractor (like ::) + // which is the second error + +def foo2: Unit = // error + val _root_ : String = "abc" // error + +// i17757 +def fooVal: Unit = + val _root_ = "abc" // error + println(_root_.length) // error + println(_root_) // error + +def barVal: Unit = + _root_ // error + _root_.scala // error + println(_root_) // error + println(_root_.scala) // error + +// i18050 +package p { + package _root_ { // error + object X // error + } +} + +// i12508 +package _root_ { // error + class C { + val _root_ = 42 // error + } +} +package _root_.p { // error + class C +} From 12a545b6d8275229d21854008921d16a04789760 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 12 Jul 2023 09:55:05 +0100 Subject: [PATCH 2/3] Fix _root_ in patterns Also eliminate allowRoot and rawTermIdent, and fix 12508 reference. --- .../dotty/tools/dotc/parsing/Parsers.scala | 42 +++++++++---------- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +- tests/neg/i18020.scala | 7 +++- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 970902edc130..a5b72ba865fe 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1072,15 +1072,11 @@ object Parsers { } /** Accept identifier and return Ident with its name as a term name. */ - def rawTermIdent(): Ident = - makeIdent(in.token, in.offset, ident()) - - /** Call `rawTermIdent`, and check it isn't a root package name. */ def termIdent(): Ident = - val ident = rawTermIdent() - if ident.name == nme.ROOTPKG then + val t = makeIdent(in.token, in.offset, ident()) + if t.name == nme.ROOTPKG then syntaxError(em"Illegal use of root package name.") - ident + t /** Accept identifier and return Ident with its name as a type name. */ def typeIdent(): Ident = @@ -1119,7 +1115,7 @@ object Parsers { * | [id ‘.’] ‘this’ * | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id */ - def simpleRef(allowRoot: Boolean = true): Tree = + def simpleRef(): Tree = val start = in.offset def handleThis(qual: Ident) = @@ -1135,19 +1131,21 @@ object Parsers { if in.token == THIS then handleThis(EmptyTypeIdent) else if in.token == SUPER then handleSuper(EmptyTypeIdent) - else - val t = if allowRoot then rawTermIdent() else termIdent() - if in.token == DOT then - def qual = cpy.Ident(t)(t.name.toTypeName) - in.lookahead.token match - case THIS => - in.nextToken() - handleThis(qual) - case SUPER => - in.nextToken() - handleSuper(qual) - case _ => t - else t + else if in.lookahead.token == DOT then + val tok = in.token + val offset = in.offset + val name = ident() + def qual = makeIdent(tok, offset, name.toTypeName) + in.lookahead.token match + case THIS => + in.nextToken() + handleThis(qual) + case SUPER => + in.nextToken() + handleSuper(qual) + case _ => + makeIdent(tok, offset, name) + else termIdent() end simpleRef /** MixinQualifier ::= `[' id `]' @@ -2972,7 +2970,7 @@ object Parsers { */ def simplePattern(): Tree = in.token match { case IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER => - simpleRef(allowRoot = false) match + simpleRef() match case id @ Ident(nme.raw.MINUS) if isNumericLit => literal(startOffset(id)) case t => simplePatternRest(t) case USCORE => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 668489223e93..d09597b9776a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -583,8 +583,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if foundUnderScala2.exists && !(foundUnderScala2 =:= found) then report.migrationWarning( em"""Name resolution will change. - | currently selected : $foundUnderScala2 - | in the future, without -source 3.0-migration: $found""", tree.srcPos) + | currently selected : $foundUnderScala2 + | in the future, without -source 3.0-migration: $found""", tree.srcPos) foundUnderScala2 else found finally diff --git a/tests/neg/i18020.scala b/tests/neg/i18020.scala index bf85cff2cc15..b924c136d863 100644 --- a/tests/neg/i18020.scala +++ b/tests/neg/i18020.scala @@ -46,7 +46,7 @@ package p { } } -// i12508 +// scala/bug#12508 package _root_ { // error class C { val _root_ = 42 // error @@ -55,3 +55,8 @@ package _root_ { // error package _root_.p { // error class C } + +// from ScalaPB +def fromScalaPb(x: Option[String]) = x match + case _root_.scala.Some(s) => s + case _ => "" From ba1354db935849fa2c7152decc46e17e4a466de9 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 14 Jul 2023 11:30:45 +0100 Subject: [PATCH 3/3] Workaround Scanner.lookahead limitation --- 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 a5b72ba865fe..b77d86d24991 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1131,7 +1131,7 @@ object Parsers { if in.token == THIS then handleThis(EmptyTypeIdent) else if in.token == SUPER then handleSuper(EmptyTypeIdent) - else if in.lookahead.token == DOT then + else if in.token != INTERPOLATIONID && in.lookahead.token == DOT then val tok = in.token val offset = in.offset val name = ident()