Skip to content

Commit

Permalink
Merge pull request #9589 from smarter/backport-scala3-syntax
Browse files Browse the repository at this point in the history
[backport] Support for Scala 3 syntax under `-Xsource:3`
  • Loading branch information
lrytz committed Apr 23, 2021
2 parents 2945fc4 + 97ccdff commit 44a2b2f
Show file tree
Hide file tree
Showing 26 changed files with 385 additions and 58 deletions.
6 changes: 6 additions & 0 deletions src/compiler/scala/tools/nsc/Global.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,12 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
val profiler: Profiler = Profiler(settings)
keepPhaseStack = settings.log.isSetByUser

// We hit these checks regularly. They shouldn't change inside the same run, so cache the comparisons here.
val isScala211: Boolean = settings.isScala211
val isScala212: Boolean = settings.isScala212
val isScala213: Boolean = settings.isScala213
val isScala3: Boolean = settings.isScala3

// used in sbt
def uncheckedWarnings: List[(Position, String)] = reporting.uncheckedWarnings
// used in sbt
Expand Down
151 changes: 109 additions & 42 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,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 {
Expand Down Expand Up @@ -704,6 +722,10 @@ self =>
def isRawBar = isRawIdent && in.name == raw.BAR
def isRawIdent = in.token == IDENTIFIER

def isWildcardType =
in.token == USCORE ||
currentRun.isScala3 && isRawIdent && in.name == raw.QMARK

def isIdent = in.token == IDENTIFIER || in.token == BACKQUOTED_IDENT
def isMacro = in.token == IDENTIFIER && in.name == nme.MACROkw

Expand All @@ -723,12 +745,13 @@ self =>

def isSimpleExprIntro: Boolean = isExprIntroToken(in.token)

def isExprIntroToken(token: Token): Boolean = isLiteralToken(token) || (token match {
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)

Expand Down Expand Up @@ -870,6 +893,16 @@ self =>
}
}

/** Is current ident a `*`, and is it followed by a `)` or `, )`? */
def followingIsScala3Vararg(): Boolean =
currentRun.isScala3 && isRawStar && lookingAhead {
in.token == RPAREN ||
in.token == COMMA && {
in.nextToken()
in.token == RPAREN
}
}

/* --------- OPERAND/OPERATOR STACK --------------------------------------- */

/** Modes for infix types. */
Expand Down Expand Up @@ -1042,12 +1075,14 @@ self =>
val start = in.offset
simpleTypeRest(in.token match {
case LPAREN => atPos(start)(makeSafeTupleType(inParens(types()), start))
case USCORE => wildcardType(in.skipToken())
case _ =>
path(thisOK = false, typeOK = true) match {
case r @ SingletonTypeTree(_) => r
case r => convertToTypeId(r)
}
if (isWildcardType)
wildcardType(in.skipToken())
else
path(thisOK = false, typeOK = true) match {
case r @ SingletonTypeTree(_) => r
case r => convertToTypeId(r)
}
})
}

Expand Down Expand Up @@ -1648,7 +1683,7 @@ self =>
val start = in.offset
val base = opstack

def loop(top: Tree): Tree = if (!isIdent) top else {
def loop(top: Tree): Tree = if (!isIdent || followingIsScala3Vararg()) top else {
pushOpInfo(reduceExprStack(base, top))
newLineOptWhenFollowing(isExprIntroToken)
if (isExprIntro)
Expand All @@ -1659,7 +1694,12 @@ self =>
else finishPostfixOp(start, base, popOpInfo())
}

reduceExprStack(base, loop(prefixExpr()))
val expr = reduceExprStack(base, loop(prefixExpr()))
if (followingIsScala3Vararg())
atPos(expr.pos.start) {
Typed(expr, atPos(in.skipToken()) { Ident(tpnme.WILDCARD_STAR) })
}
else expr
}

/** {{{
Expand Down Expand Up @@ -1860,6 +1900,12 @@ self =>
*/
def generator(eqOK: Boolean, allowNestedIf: Boolean = true): List[Tree] = {
val start = in.offset
val hasCase = in.token == CASE
if (hasCase) {
if (!currentRun.isScala3) syntaxError(in.offset, s"`case` keyword in for comprehension requires the -Xsource:3 flag.")
in.skipCASE()
}

val hasVal = in.token == VAL
if (hasVal)
in.nextToken()
Expand All @@ -1873,7 +1919,7 @@ self =>
else syntaxError(in.offset, "val in for comprehension must be followed by assignment")
}

if (hasEq && eqOK) in.nextToken()
if (hasEq && eqOK && !hasCase) in.nextToken()
else accept(LARROW)
val rhs = expr()

Expand Down Expand Up @@ -1909,18 +1955,16 @@ self =>
def functionArgType(): Tree = argType()
def argType(): Tree = {
val start = in.offset
in.token match {
case USCORE =>
if (isWildcardType) {
in.nextToken()
if (in.token == SUBTYPE || in.token == SUPERTYPE) wildcardType(start)
else atPos(start) { Bind(tpnme.WILDCARD, EmptyTree) }
case _ =>
typ() match {
case Ident(name: TypeName) if nme.isVariableName(name) =>
atPos(start) { Bind(name, EmptyTree) }
case t => t
}
}
} else
typ() match {
case Ident(name: TypeName) if nme.isVariableName(name) =>
atPos(start) { Bind(name, EmptyTree) }
case t => t
}
}

/** {{{
Expand Down Expand Up @@ -2010,7 +2054,12 @@ self =>
if (isCloseDelim) atPos(top.pos.start, in.prev.offset)(Star(stripParens(top)))
else EmptyTree
)
case _ => EmptyTree
case Ident(name) if isSequenceOK && followingIsScala3Vararg() =>
atPos(top.pos.start) {
Bind(name, atPos(in.skipToken()) { Star(Ident(nme.WILDCARD)) })
}
case _ =>
EmptyTree
}
def loop(top: Tree): Tree = reducePatternStack(base, top) match {
case next if isIdent && !isRawBar => pushOpInfo(next) ; loop(simplePattern(badPattern3))
Expand Down Expand Up @@ -2213,7 +2262,10 @@ self =>
in.nextToken()
loop(mods)
case _ =>
mods
if (isValidSoftModifier) {
in.nextToken()
loop(mods)
} else mods
}
loop(NoMods)
}
Expand Down Expand Up @@ -2298,7 +2350,7 @@ self =>
if (vds.isEmpty)
syntaxError(start, s"case classes must have a parameter list; try 'case class $name()' or 'case object $name'")
else if (vds.head.nonEmpty && vds.head.head.mods.isImplicit) {
if (settings.isScala213)
if (currentRun.isScala213)
syntaxError(start, s"case classes must have a non-implicit parameter list; try 'case class $name()$elliptical'")
else {
deprecationWarning(start, s"case classes should have a non-implicit parameter list; adapting to 'case class $name()$elliptical'", "2.12.2")
Expand Down Expand Up @@ -2503,19 +2555,27 @@ self =>
def loop(expr: Tree): Tree = {
expr setPos expr.pos.makeTransparent
val selectors: List[ImportSelector] = in.token match {
case USCORE => List(importSelector()) // import foo.bar._;
case LBRACE => importSelectors() // import foo.bar.{ x, y, z }
case _ =>
val nameOffset = in.offset
val name = ident()
if (in.token == DOT) {
// import foo.bar.ident.<unknown> and so create a select node and recurse.
val t = atPos(start, if (name == nme.ERROR) in.offset else nameOffset)(Select(expr, name))
in.nextToken()
return loop(t)
case USCORE =>
List(importSelector()) // import foo.bar._
case IDENTIFIER if currentRun.isScala3 && in.name == raw.STAR =>
List(importSelector()) // import foo.bar.*
case LBRACE =>
importSelectors() // import foo.bar.{ x, y, z }
case _ =>
if (settings.isScala3 && lookingAhead { isRawIdent && in.name == nme.as })
List(importSelector()) // import foo.bar as baz
else {
val nameOffset = in.offset
val name = ident()
if (in.token == DOT) {
// import foo.bar.ident.<unknown> and so create a select node and recurse.
val t = atPos(start, if (name == nme.ERROR) in.offset else nameOffset)(Select(expr, name))
in.nextToken()
return loop(t)
}
// import foo.bar.Baz;
else List(makeImportSelector(name, nameOffset))
}
// import foo.bar.Baz;
else List(makeImportSelector(name, nameOffset))
}
// reaching here means we're done walking.
atPos(start)(Import(expr, selectors))
Expand Down Expand Up @@ -2558,18 +2618,25 @@ self =>
*/
def importSelector(): ImportSelector = {
val start = in.offset
val name = wildcardOrIdent()
val name =
if (currentRun.isScala3 && isRawIdent && in.name == raw.STAR) {
in.nextToken()
nme.WILDCARD
}
else wildcardOrIdent()
var renameOffset = -1
val rename = in.token match {
case ARROW =>
val rename =
if (in.token == ARROW || (currentRun.isScala3 && isRawIdent && in.name == nme.as)) {
in.nextToken()
renameOffset = in.offset
wildcardOrIdent()
case _ if name == nme.WILDCARD => null
case _ =>
}
else if (name == nme.WILDCARD) null
else {
renameOffset = start
name
}
}

ImportSelector(name, start, rename, renameOffset)
}

Expand Down Expand Up @@ -3117,7 +3184,7 @@ self =>
case IMPORT =>
in.flushDoc
importClause()
case _ if isAnnotation || isTemplateIntro || isModifier =>
case _ if isAnnotation || isTemplateIntro || isModifier || isValidSoftModifier =>
joinComment(topLevelTmplDef :: Nil)
}

Expand Down Expand Up @@ -3167,7 +3234,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
Expand Down
14 changes: 13 additions & 1 deletion src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,16 @@ trait Scanners extends ScannersCommon {
}
}

/** Advance beyond a case token without marking the CASE in sepRegions.
* This method should be called to skip beyond CASE tokens that are
* not part of matches, i.e. no ARROW is expected after them.
*/
def skipCASE(): Unit = {
assert(token == CASE, s"Internal error: skipCASE() called on non-case token $token")
nextToken()
sepRegions = sepRegions.tail
}

/** Produce next token, filling TokenData fields of Scanner.
*/
def nextToken(): Unit = {
Expand Down Expand Up @@ -610,7 +620,7 @@ trait Scanners extends ScannersCommon {
val isEmptyCharLit = (ch == '\'')
getLitChar()
if (ch == '\'') {
if (isEmptyCharLit && settings.isScala213)
if (isEmptyCharLit && currentRun.isScala213)
syntaxError("empty character literal (use '\\'' for single quote)")
else {
if (isEmptyCharLit)
Expand Down Expand Up @@ -1282,6 +1292,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. */
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ trait ScalaSettings extends StandardScalaSettings with Warnings { _: MutableSett
def isScala212: Boolean = source.value >= version212
private[this] val version213 = ScalaVersion("2.13.0")
def isScala213: Boolean = source.value >= version213
private[this] val version3 = ScalaVersion("3.0.0")
def isScala3: Boolean = source.value >= version3

/**
* -X "Advanced" settings
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/transform/UnCurry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ abstract class UnCurry extends InfoTransform
(sym ne null) && sym.elisionLevel.exists { level =>
if (sym.isMethod) level < settings.elidebelow.value
else {
if (settings.isScala213) reporter.error(sym.pos, s"${sym.name}: Only methods can be marked @elidable!")
if (currentRun.isScala213) reporter.error(sym.pos, s"${sym.name}: Only methods can be marked @elidable!")
false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ trait ContextErrors {
def issueTypeError(err: AbsTypeError)(implicit context: Context) { context.issue(err) }

def typeErrorMsg(context: Context, found: Type, req: Type) =
if (context.openImplicits.nonEmpty && !settings.XlogImplicits.value && settings.isScala213)
if (context.openImplicits.nonEmpty && !settings.XlogImplicits.value && currentRun.isScala213)
// OPT: avoid error string creation for errors that won't see the light of day, but predicate
// this on -Xsource:2.13 for bug compatibility with https://github.com/scala/scala/pull/7147#issuecomment-418233611
"type mismatch"
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ trait Contexts { self: Analyzer =>
isAccessible(sym, pre) &&
!(imported && {
val e = scope.lookupEntry(name)
(e ne null) && (e.owner == scope) && (!settings.isScala212 || e.sym.exists)
(e ne null) && (e.owner == scope) && (!currentRun.isScala212 || e.sym.exists)
})

/** Do something with the symbols with name `name` imported via the import in `imp`,
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/scala/tools/nsc/typechecker/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,7 @@ trait Implicits {
if(isView || wildPtNotInstantiable || matchesPtInst(firstPending))
typedImplicit(firstPending, ptChecked = true, isLocalToCallsite)
else SearchFailure
if (typedFirstPending.isFailure && settings.isScala213)
if (typedFirstPending.isFailure && currentRun.isScala213)
undoLog.undoTo(mark) // Don't accumulate constraints from typechecking or type error message creation for failed candidates

// Pass the errors to `DivergentImplicitRecovery` so that it can note
Expand Down Expand Up @@ -1214,7 +1214,7 @@ trait Implicits {
* bound, the implicits infos which are members of these companion objects.
*/
private def companionImplicitMap(tp: Type): InfoMap = {
val isScala213 = settings.isScala213
val isScala213 = currentRun.isScala213

/* Populate implicit info map by traversing all parts of type `tp`.
* Parameters as for `getParts`.
Expand Down Expand Up @@ -1626,9 +1626,9 @@ trait Implicits {
val outSym = out.typeSymbol

val fail =
if (out.annotations.isEmpty && (outSym == ObjectClass || (settings.isScala211 && outSym == AnyValClass)))
if (out.annotations.isEmpty && (outSym == ObjectClass || (currentRun.isScala211 && outSym == AnyValClass)))
maybeInvalidConversionError(s"the result type of an implicit conversion must be more specific than $out")
else if (settings.isScala211 && in.annotations.isEmpty && in.typeSymbol == NullClass)
else if (currentRun.isScala211 && in.annotations.isEmpty && in.typeSymbol == NullClass)
maybeInvalidConversionError("an expression of type Null is ineligible for implicit conversion")
else false

Expand Down

0 comments on commit 44a2b2f

Please sign in to comment.