From 8da523c79614a5456588dcabbe64d17bfd9777c6 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 1 Apr 2021 17:03:48 +0200 Subject: [PATCH] Allow soft keywords `open` and `infix` under -Xsource:3 Since everything is open and can be used infix by default in Scala 2, these keywords are no-op, but they're useful for cross-compiling with a future version of Scala 3 where they will be required in some cases (with Scala 3.0 they're only required to avoid warnings under `-source future`). See https://dotty.epfl.ch/docs/reference/changed-features/operators.html and http://dotty.epfl.ch/docs/reference/other-new-features/open-classes.html for details. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 47 ++++++++++++++----- .../scala/tools/nsc/ast/parser/Scanners.scala | 2 + .../scala/reflect/internal/StdNames.scala | 4 ++ test/files/neg/open-infix-future.check | 22 +++++++++ test/files/neg/open-infix-future.scala | 17 +++++++ test/files/pos/open-infix-future.scala | 36 ++++++++++++++ 6 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 test/files/neg/open-infix-future.check create mode 100644 test/files/neg/open-infix-future.scala create mode 100644 test/files/pos/open-infix-future.scala diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 358c3188206b..016575d5bda4 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -674,6 +674,24 @@ self => case _ => false } + def isSoftModifier: Boolean = + currentRun.isScala3 && in.token == IDENTIFIER && softModifierNames.contains(in.name) + + /** Is the current token a soft modifier in a position where such a modifier is allowed? */ + def isValidSoftModifier: Boolean = + isSoftModifier && { + val mod = in.name + lookingAhead { + while (in.token == NEWLINE || isModifier || isSoftModifier) in.nextToken() + + in.token match { + case CLASS | CASECLASS => true + case DEF | TRAIT | TYPE => mod == nme.infix + case _ => false + } + } + } + def isAnnotation: Boolean = in.token == AT def isLocalModifier: Boolean = in.token match { @@ -719,12 +737,13 @@ self => } def isLiteral = isLiteralToken(in.token) - def isExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match { - case IDENTIFIER | BACKQUOTED_IDENT | - THIS | SUPER | IF | FOR | NEW | USCORE | TRY | WHILE | - DO | RETURN | THROW | LPAREN | LBRACE | XMLSTART => true - case _ => false - }) + def isExprIntroToken(token: Token): Boolean = + !isValidSoftModifier && (isLiteralToken(token) || (token match { + case IDENTIFIER | BACKQUOTED_IDENT | + THIS | SUPER | IF | FOR | NEW | USCORE | TRY | WHILE | + DO | RETURN | THROW | LPAREN | LBRACE | XMLSTART => true + case _ => false + })) def isExprIntro: Boolean = isExprIntroToken(in.token) @@ -2265,8 +2284,11 @@ self => */ def accessModifierOpt(): Modifiers = normalizeModifiers { in.token match { - case m @ (PRIVATE | PROTECTED) => in.nextToken() ; accessQualifierOpt(Modifiers(flagTokens(m))) - case _ => NoMods + case m @ (PRIVATE | PROTECTED) => + in.nextToken() + accessQualifierOpt(Modifiers(flagTokens(m))) + case _ => + NoMods } } @@ -2288,7 +2310,10 @@ self => in.nextToken() loop(mods) case _ => - mods + if (isValidSoftModifier) { + in.nextToken() + loop(mods) + } else mods } loop(NoMods) } @@ -3221,7 +3246,7 @@ self => case IMPORT => in.flushDoc() importClause() - case _ if isAnnotation || isTemplateIntro || isModifier => + case _ if isAnnotation || isTemplateIntro || isModifier || isValidSoftModifier => joinComment(topLevelTmplDef :: Nil) } @@ -3271,7 +3296,7 @@ self => case IMPORT => in.flushDoc() importClause() - case _ if isDefIntro || isModifier || isAnnotation => + case _ if isDefIntro || isModifier || isAnnotation || isValidSoftModifier => joinComment(nonLocalDefOrDcl) case _ if isExprIntro => in.flushDoc() diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 2ba2f1c87b97..9d1f7b55a91e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -1438,6 +1438,8 @@ trait Scanners extends ScannersCommon { final val token2name = (allKeywords map (_.swap)).toMap + final val softModifierNames = Set(nme.open, nme.infix) + // Token representation ---------------------------------------------------- /** Returns the string representation of given token. */ diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index fc8581847966..66dee512f7bd 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -661,6 +661,10 @@ trait StdNames { // Scala 3 import syntax val as: NameType = nameType("as") + // Scala 3 soft keywords + val infix: NameType = nameType("infix") + val open: NameType = nameType("open") + // Compiler utilized names val AnnotatedType: NameType = nameType("AnnotatedType") diff --git a/test/files/neg/open-infix-future.check b/test/files/neg/open-infix-future.check new file mode 100644 index 000000000000..15515fc2ef50 --- /dev/null +++ b/test/files/neg/open-infix-future.check @@ -0,0 +1,22 @@ +open-infix-future.scala:4: error: expected class or object definition +open trait A // error +^ +open-infix-future.scala:5: error: expected class or object definition +open object B // error +^ +open-infix-future.scala:8: error: ';' expected but 'val' found. + infix val a: Int = 1 // error + ^ +open-infix-future.scala:9: error: ';' expected but 'var' found. + infix var b: Int = 1 // error + ^ +open-infix-future.scala:11: error: ';' expected but 'type' found. + open type D // error + ^ +open-infix-future.scala:14: error: illegal start of statement + open class E // error + ^ +open-infix-future.scala:15: error: ';' expected but 'def' found. + open def bla(y: Int) = y // error + ^ +7 errors diff --git a/test/files/neg/open-infix-future.scala b/test/files/neg/open-infix-future.scala new file mode 100644 index 000000000000..2a250f3b006e --- /dev/null +++ b/test/files/neg/open-infix-future.scala @@ -0,0 +1,17 @@ +// scalac: -Xsource:3 +// + +open trait A // error +open object B // error + +class C { + infix val a: Int = 1 // error + infix var b: Int = 1 // error + + open type D // error + + def foo: Unit = { + open class E // error + open def bla(y: Int) = y // error + } +} diff --git a/test/files/pos/open-infix-future.scala b/test/files/pos/open-infix-future.scala new file mode 100644 index 000000000000..8fee778d40cb --- /dev/null +++ b/test/files/pos/open-infix-future.scala @@ -0,0 +1,36 @@ +// scalac: -Xsource:3 +// + +open class A +infix class B[T, S] + +open infix class C[T, S] +open infix case class CC[T, S](x: Int) +infix open class D[T, S] +infix trait DT[T, S] + +open +infix +private +class E + +class F { + open infix class C1[T, S] + infix type X + + infix def foo(x: Int): Int = x +} + +object G { + open infix class C2[T, S] +} + +object Test { + val infix: Int = 1 + infix + 1 + val open: Int => Int = x => x + open(1) + open { + 2 + } +}