diff --git a/community-build/community-projects/intent b/community-build/community-projects/intent index 049555611f5e..466662fb36ed 160000 --- a/community-build/community-projects/intent +++ b/community-build/community-projects/intent @@ -1 +1 @@ -Subproject commit 049555611f5e9cc597943da032b7775ef179b66c +Subproject commit 466662fb36ed38d1f045449682bdc109496c6b2d diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala index d7178bd411e7..03d319ce6e70 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala @@ -179,7 +179,7 @@ object JavaScanners { nextChar() case ':' => - token = COLON + token = COLONop nextChar() case '@' => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3b7d11d16fd0..bdf19ac7d013 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -43,6 +43,7 @@ object Parsers { enum Location(val inParens: Boolean, val inPattern: Boolean, val inArgs: Boolean): case InParens extends Location(true, false, false) case InArgs extends Location(true, false, true) + case InColonArg extends Location(false, false, true) case InPattern extends Location(false, true, false) case InGuard extends Location(false, false, false) case InPatternArgs extends Location(false, true, true) // InParens not true, since it might be an alternative @@ -310,8 +311,8 @@ object Parsers { def acceptColon(): Int = val offset = in.offset - if in.isColon() then { in.nextToken(); offset } - else accept(COLON) + if in.isColon then { in.nextToken(); offset } + else accept(COLONop) /** semi = nl {nl} | `;' * nl = `\n' // where allowed @@ -431,7 +432,7 @@ object Parsers { convertToParam(t, mods) :: Nil case Tuple(ts) => ts.map(convertToParam(_, mods)) - case t: Typed => + case t @ Typed(Ident(_), _) => report.errorOrMigrationWarning( em"parentheses are required around the parameter of a lambda${rewriteNotice()}", in.sourcePos(), from = `3.0`) @@ -444,18 +445,22 @@ object Parsers { /** Convert tree to formal parameter */ - def convertToParam(tree: Tree, mods: Modifiers, expected: String = "formal parameter"): ValDef = tree match { - case id @ Ident(name) => - makeParameter(name.asTermName, TypeTree(), mods, isBackquoted = isBackquoted(id)).withSpan(tree.span) - case Typed(_, tpt: TypeBoundsTree) => - syntaxError(s"not a legal $expected", tree.span) - makeParameter(nme.ERROR, tree, mods) - case Typed(id @ Ident(name), tpt) => - makeParameter(name.asTermName, tpt, mods, isBackquoted = isBackquoted(id)).withSpan(tree.span) - case _ => - syntaxError(s"not a legal $expected", tree.span) + def convertToParam(tree: Tree, mods: Modifiers): ValDef = + def fail() = + syntaxError(s"not a legal formal parameter for a function literal", tree.span) makeParameter(nme.ERROR, tree, mods) - } + tree match + case param: ValDef => + param.withMods(param.mods | mods.flags) + case id @ Ident(name) => + makeParameter(name.asTermName, TypeTree(), mods, isBackquoted = isBackquoted(id)).withSpan(tree.span) + // the following three cases are needed only for 2.x parameters without enclosing parentheses + case Typed(_, tpt: TypeBoundsTree) => + fail() + case Typed(id @ Ident(name), tpt) => + makeParameter(name.asTermName, tpt, mods, isBackquoted = isBackquoted(id)).withSpan(tree.span) + case _ => + fail() /** Convert (qual)ident to type identifier */ @@ -883,12 +888,22 @@ object Parsers { lookahead.nextToken() skipParams() skipParams() - lookahead.isColon() + lookahead.isColon def followingIsExtension() = val next = in.lookahead.token next == LBRACKET || next == LPAREN + def followingIsSelfType() = + val lookahead = in.LookaheadScanner(allowIndent = true) + lookahead.nextToken() + lookahead.token == COLONfollow + && { + lookahead.observeColonEOL(inTemplate = false) + lookahead.nextToken() + canStartTypeTokens.contains(lookahead.token) + } + /** Is current ident a `*`, and is it followed by a `)`, `, )`, `,EOF`? The latter two are not syntactically valid, but we need to include them here for error recovery. */ def followingIsVararg(): Boolean = @@ -903,6 +918,43 @@ object Parsers { } } + /** When encountering a `:`, is that in the binding of a lambda? + * @pre location of the enclosing expression is `InParens`, so there is an open `(`. + */ + def followingIsLambdaParams() = + val lookahead = in.LookaheadScanner() + lookahead.nextToken() + while lookahead.token != RPAREN && lookahead.token != EOF do + if lookahead.token == LPAREN then lookahead.skipParens() + else lookahead.nextToken() + lookahead.token == RPAREN + && { + lookahead.nextToken() + lookahead.isArrow + } + + /** Is the token sequence following the current `:` token classified as a lambda? + * This is the case if the input starts with an identifier, a wildcard, or + * something enclosed in (...) or [...], and this is followed by a `=>` or `?=>` + * and an INDENT. + */ + def followingIsLambdaAfterColon(): Boolean = + val lookahead = in.LookaheadScanner(allowIndent = true) + def isArrowIndent() = + lookahead.isArrow + && { + lookahead.nextToken() + lookahead.token == INDENT + } + lookahead.nextToken() + if lookahead.isIdent || lookahead.token == USCORE then + lookahead.nextToken() + isArrowIndent() + else if lookahead.token == LPAREN || lookahead.token == LBRACKET then + lookahead.skipParens() + isArrowIndent() + else false + /* --------- OPERAND/OPERATOR STACK --------------------------------------- */ var opStack: List[OpInfo] = Nil @@ -932,6 +984,13 @@ object Parsers { recur(top) } + /** True if we are seeing a lambda argument after a colon of the form: + * : (params) => + * body + */ + def isColonLambda = + in.fewerBracesEnabled && in.token == COLONfollow && followingIsLambdaAfterColon() + /** operand { infixop operand | MatchClause } [postfixop], * * respecting rules of associativity and precedence. @@ -946,24 +1005,25 @@ object Parsers { val base = opStack def recur(top: Tree): Tree = - val isType = kind == ParseKind.Type - if (isIdent && isOperator) { + def isType = kind == ParseKind.Type + def maybePostfix = kind == ParseKind.Expr && in.postfixOpsEnabled + if isIdent && isOperator then val op = if isType then typeIdent() else termIdent() val top1 = reduceStack(base, top, precedence(op.name), !op.name.isRightAssocOperatorName, op.name, isType) opStack = OpInfo(top1, op, in.offset) :: opStack colonAtEOLOpt() newLineOptWhenFollowing(canStartOperand) - val maybePostfix = kind == ParseKind.Expr && in.postfixOpsEnabled - if (maybePostfix && !canStartOperand(in.token)) { + if isColonLambda then + in.nextToken() + recur(expr(Location.InColonArg)) + else if maybePostfix && !canStartOperand(in.token) then val topInfo = opStack.head opStack = opStack.tail val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType) atSpan(startOffset(od), topInfo.offset) { PostfixOp(od, topInfo.operator) } - } else recur(operand(location)) - } else val t = reduceStack(base, top, minPrec, leftAssoc = true, in.name, isType) if !isType && in.token == MATCH then recurAtMinPrec(matchClause(t)) @@ -1259,7 +1319,8 @@ object Parsers { def colonAtEOLOpt(): Unit = { possibleColonOffset = in.lastOffset - if in.token == COLONEOL then in.nextToken() + in.observeColonEOL(inTemplate = false) + if in.token == COLONeol then in.nextToken() } def argumentStart(): Unit = @@ -1275,8 +1336,8 @@ object Parsers { patch(source, Span(in.offset), " ") def possibleTemplateStart(isNew: Boolean = false): Unit = - in.observeColonEOL() - if in.token == COLONEOL then + in.observeColonEOL(inTemplate = true) + if in.token == COLONeol then if in.lookahead.token == END then in.token = NEWLINE else in.nextToken() @@ -1405,7 +1466,7 @@ object Parsers { val paramStart = in.offset val ts = in.currentRegion.withCommasExpected { funArgType() match - case Ident(name) if name != tpnme.WILDCARD && in.isColon() => + case Ident(name) if name != tpnme.WILDCARD && in.isColon => isValParamList = true commaSeparatedRest( typedFunParam(paramStart, name.toTermName, imods), @@ -1823,7 +1884,7 @@ object Parsers { } def contextBounds(pname: TypeName): List[Tree] = - if in.isColon() then + if in.isColon then atSpan(in.skipToken()) { AppliedTypeTree(toplevelTyp(), Ident(pname)) } :: contextBounds(pname) @@ -1838,7 +1899,7 @@ object Parsers { Nil def typedOpt(): Tree = - if in.isColon() then { in.nextToken(); toplevelTyp() } + if in.isColon then { in.nextToken(); toplevelTyp() } else TypeTree().withSpan(Span(in.lastOffset)) def typeDependingOn(location: Location): Tree = @@ -2066,8 +2127,8 @@ object Parsers { else expr1Rest(postfixExpr(location), location) end expr1 - def expr1Rest(t: Tree, location: Location): Tree = in.token match - case EQUALS => + def expr1Rest(t: Tree, location: Location): Tree = + if in.token == EQUALS then t match case Ident(_) | Select(_, _) | Apply(_, _) | PrefixOp(_, _) => atSpan(startOffset(t), in.skipToken()) { @@ -2076,12 +2137,11 @@ object Parsers { } case _ => t - case COLON => + else if in.isColon then in.nextToken() ascription(t, location) - case _ => + else t - end expr1Rest def ascription(t: Tree, location: Location): Tree = atSpan(startOffset(t)) { in.token match { @@ -2165,7 +2225,7 @@ object Parsers { val start = in.offset val name = bindingName() val t = - if (in.token == COLON && location == Location.InBlock) { + if ((in.token == COLONop || in.token == COLONfollow) && location == Location.InBlock) { report.errorOrMigrationWarning( s"This syntax is no longer supported; parameter needs to be enclosed in (...)${rewriteNotice(`future-migration`)}", source.atSpan(Span(start, in.lastOffset)), @@ -2208,7 +2268,11 @@ object Parsers { in.nextToken() else accept(ARROW) - Function(params, if (location == Location.InBlock) block() else expr()) + val body = + if location == Location.InBlock then block() + else if location == Location.InColonArg && in.token == INDENT then blockExpr() + else expr() + Function(params, body) } /** PostfixExpr ::= InfixExpr [id [nl]] @@ -2258,9 +2322,11 @@ object Parsers { * | SimpleExpr `.` MatchClause * | SimpleExpr (TypeArgs | NamedTypeArgs) * | SimpleExpr1 ArgumentExprs - * | SimpleExpr1 `:` IndentedExpr -- under language.experimental.fewerBraces - * | SimpleExpr1 FunParams (‘=>’ | ‘?=>’) IndentedExpr -- under language.experimental.fewerBraces - * IndentedExpr ::= indent (CaseClauses | Block) outdent + * | SimpleExpr1 `:` ColonArgument -- under language.experimental.fewerBraces + * ColonArgument ::= indent (CaseClauses | Block) outdent + * | FunParams (‘=>’ | ‘?=>’) ColonArgBody + * | HkTypeParamClause ‘=>’ ColonArgBody + * ColonArgBody ::= indent (CaseClauses | Block) outdent * Quoted ::= ‘'’ ‘{’ Block ‘}’ * | ‘'’ ‘[’ Type ‘]’ */ @@ -2282,7 +2348,7 @@ object Parsers { placeholderParams = param :: placeholderParams atSpan(start) { Ident(pname) } case LPAREN => - atSpan(in.offset) { makeTupleOrParens(inParens(exprsInParensOpt())) } + atSpan(in.offset) { makeTupleOrParens(inParens(exprsInParensOrBindings())) } case LBRACE | INDENT => canApply = false blockExpr() @@ -2304,7 +2370,7 @@ object Parsers { case _ => if isLiteral then literal() - else if in.isColon() then + else if in.isColon then syntaxError(IllegalStartSimpleExpr(tokenString(in.token))) in.nextToken() simpleExpr(location) @@ -2316,55 +2382,38 @@ object Parsers { simpleExprRest(t, location, canApply) } - def simpleExprRest(t: Tree, location: Location, canApply: Boolean = true): Tree = { + def simpleExprRest(t: Tree, location: Location, canApply: Boolean = true): Tree = if (canApply) argumentStart() - in.token match { + in.token match case DOT => in.nextToken() simpleExprRest(selectorOrMatch(t), location, canApply = true) case LBRACKET => val tapp = atSpan(startOffset(t), in.offset) { TypeApply(t, typeArgs(namedOK = true, wildOK = false)) } simpleExprRest(tapp, location, canApply = true) - case LPAREN if canApply => - val app = atSpan(startOffset(t), in.offset) { - val argExprs @ (args, isUsing) = argumentExprs() - if !isUsing && in.isArrow && location != Location.InGuard && in.fewerBracesEnabled then - val params = convertToParams(Tuple(args)) - if params.forall(_.name != nme.ERROR) then - applyToClosure(t, in.offset, params) - else - mkApply(t, argExprs) - else - mkApply(t, argExprs) - } - simpleExprRest(app, location, canApply = true) - case LBRACE | INDENT if canApply => + case LPAREN | LBRACE | INDENT if canApply => val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) } simpleExprRest(app, location, canApply = true) case USCORE => - if in.lookahead.isArrow && location != Location.InGuard && in.fewerBracesEnabled then - val app = applyToClosure(t, in.offset, convertToParams(wildcardIdent())) - simpleExprRest(app, location, canApply = true) - else - atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) } - case IDENTIFIER - if !in.isOperator && in.lookahead.isArrow && location != Location.InGuard && in.fewerBracesEnabled => - val app = applyToClosure(t, in.offset, convertToParams(termIdent())) - simpleExprRest(app, location, canApply = true) + atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) } case _ => - t - } - } - - def applyToClosure(t: Tree, start: Offset, params: List[ValDef]): Tree = - atSpan(startOffset(t), in.offset) { - val arg = atSpan(start, in.skipToken()) { - if in.token != INDENT then - syntaxErrorOrIncomplete(i"indented expression expected, ${in} found") - Function(params, blockExpr()) - } - Apply(t, arg) - } + if in.isColon && location == Location.InParens && followingIsLambdaParams() then + t match + case id @ Ident(name) => + if name.is(WildcardParamName) then + assert(name == placeholderParams.head.name) + placeholderParams = placeholderParams.tail + atSpan(startOffset(id)) { + makeParameter(name.asTermName, typedOpt(), Modifiers(), isBackquoted = isBackquoted(id)) + } + case _ => t + else if isColonLambda then + val app = atSpan(startOffset(t), in.skipToken()) { + Apply(t, expr(Location.InColonArg) :: Nil) + } + simpleExprRest(app, location, canApply = true) + else t + end simpleExprRest /** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody] * | ‘new’ TemplateBody @@ -2376,7 +2425,6 @@ object Parsers { val parents = if in.isNestedStart then Nil else constrApps(exclude = COMMA) - colonAtEOLOpt() possibleTemplateStart(isNew = true) parents match { case parent :: Nil if !in.isNestedStart => @@ -2386,9 +2434,20 @@ object Parsers { } /** ExprsInParens ::= ExprInParens {`,' ExprInParens} + * Bindings ::= Binding {`,' Binding} */ - def exprsInParensOpt(): List[Tree] = - if (in.token == RPAREN) Nil else commaSeparated(exprInParens) + def exprsInParensOrBindings(): List[Tree] = + if in.token == RPAREN then Nil + else in.currentRegion.withCommasExpected { + var isFormalParams = false + def exprOrBinding() = + if isFormalParams then binding(Modifiers()) + else + val t = exprInParens() + if t.isInstanceOf[ValDef] then isFormalParams = true + t + commaSeparatedRest(exprOrBinding(), exprOrBinding) + } /** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)' * | `(' [ExprsInParens `,'] PostfixExpr `*' ')' @@ -2442,7 +2501,7 @@ object Parsers { !fn.isInstanceOf[Trees.Apply[?]] // allow one () as annotation argument else if lookahead.token == IDENTIFIER then lookahead.nextToken() - !lookahead.isColon() + !lookahead.isColon else in.canStartExprTokens.contains(lookahead.token) } } @@ -2675,7 +2734,7 @@ object Parsers { */ def pattern1(location: Location = Location.InPattern): Tree = val p = pattern2() - if in.token == COLON then + if in.isColon then in.nextToken() ascription(p, location) else p @@ -2876,7 +2935,7 @@ object Parsers { if allowed.contains(in.token) || in.isSoftModifier && localModifierTokens.subsetOf(allowed) // soft modifiers are admissible everywhere local modifiers are - && !in.lookahead.isColon() + && !in.lookahead.isColon then val isAccessMod = accessModifierTokens contains in.token val mods1 = addModifier(mods) @@ -3083,7 +3142,7 @@ object Parsers { val isParams = !impliedMods.is(Given) || startParamTokens.contains(in.token) - || isIdent && (in.name == nme.inline || in.lookahead.isColon()) + || isIdent && (in.name == nme.inline || in.lookahead.isColon) if isParams then commaSeparated(() => param()) else contextTypes(ofClass, nparams, impliedMods) checkVarArgsRules(clause) @@ -3696,7 +3755,7 @@ object Parsers { isUsingClause(extParams) do () leadParamss ++= paramClauses(givenOnly = true, numLeadParams = nparams) - if in.isColon() then + if in.isColon then syntaxError("no `:` expected here") in.nextToken() val methods: List[Tree] = @@ -3785,7 +3844,7 @@ object Parsers { val parents = if (in.token == EXTENDS) { in.nextToken() - if (in.token == LBRACE || in.token == COLONEOL) { + if (in.token == LBRACE || in.token == COLONeol) { report.errorOrMigrationWarning( "`extends` must be followed by at least one parent", in.sourcePos(), from = `3.0`) @@ -3901,7 +3960,37 @@ object Parsers { stats.toList } - /** TemplateStatSeq ::= [id [`:' Type] `=>'] TemplateStat {semi TemplateStat} + /** SelfType ::= id [‘:’ InfixType] ‘=>’ + * | ‘this’ ‘:’ InfixType ‘=>’ + */ + def selfType(): ValDef = + if (in.isIdent || in.token == THIS) + && in.lookahead.isColon && followingIsSelfType() + || in.lookahead.token == ARROW + then + atSpan(in.offset) { + val selfName = + if in.token == THIS then + in.nextToken() + nme.WILDCARD + else ident() + val selfTpt = + if in.isColon then + in.nextToken() + infixType() + else + if selfName == nme.WILDCARD then accept(COLONfollow) + TypeTree() + if in.token == ARROW then + in.token = SELFARROW // suppresses INDENT insertion after `=>` + in.nextToken() + else + syntaxError("`=>` expected after self type") + makeSelfDef(selfName, selfTpt) + } + else EmptyValDef + + /** TemplateStatSeq ::= [SelfType] TemplateStat {semi TemplateStat} * TemplateStat ::= Import * | Export * | Annotations Modifiers Def @@ -3913,25 +4002,8 @@ object Parsers { * | Annotations Modifiers EnumCase */ def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders { - var self: ValDef = EmptyValDef val stats = new ListBuffer[Tree] - if isExprIntro && !isDefIntro(modifierTokens) then - val first = expr1() - if in.token == ARROW then - first match { - case Typed(tree @ This(EmptyTypeIdent), tpt) => - self = makeSelfDef(nme.WILDCARD, tpt).withSpan(first.span) - case _ => - val ValDef(name, tpt, _) = convertToParam(first, EmptyModifiers, "self type clause") - if (name != nme.ERROR) - self = makeSelfDef(name, tpt).withSpan(first.span) - } - in.token = SELFARROW // suppresses INDENT insertion after `=>` - in.nextToken() - else - stats += first - statSepOrEnd(stats) - end if + val self = selfType() while var empty = false if (in.token == IMPORT) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 0718cc4b8748..93ce23cbc103 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -75,13 +75,8 @@ object Scanners { def isNestedStart = token == LBRACE || token == INDENT def isNestedEnd = token == RBRACE || token == OUTDENT - /** Is token a COLON, after having converted COLONEOL to COLON? - * The conversion means that indentation is not significant after `:` - * anymore. So, warning: this is a side-effecting operation. - */ - def isColon() = - if token == COLONEOL then token = COLON - token == COLON + def isColon = + token == COLONop || token == COLONfollow || token == COLONeol /** Is current token first one after a newline? */ def isAfterLineEnd: Boolean = lineOffset >= 0 @@ -189,7 +184,10 @@ object Scanners { val indentSyntax = ((if (Config.defaultIndent) !noindentSyntax else ctx.settings.indent.value) || rewriteNoIndent) - && !isInstanceOf[LookaheadScanner] + && { this match + case self: LookaheadScanner => self.allowIndent + case _ => true + } if (rewrite) { val s = ctx.settings @@ -206,12 +204,22 @@ object Scanners { def featureEnabled(name: TermName) = Feature.enabled(name)(using languageImportContext) def erasedEnabled = featureEnabled(Feature.erasedDefinitions) + private inline val fewerBracesByDefault = false + // turn on to study impact on codebase if `fewerBraces` was the default + private var fewerBracesEnabledCache = false private var fewerBracesEnabledCtx: Context = NoContext def fewerBracesEnabled = if fewerBracesEnabledCtx ne myLanguageImportContext then - fewerBracesEnabledCache = featureEnabled(Feature.fewerBraces) + fewerBracesEnabledCache = + featureEnabled(Feature.fewerBraces) + || fewerBracesByDefault && indentSyntax && !migrateTo3 + // ensure that fewer braces is not the default for 3.0-migration since + // { x: T => + // expr + // } + // would be ambiguous fewerBracesEnabledCtx = myLanguageImportContext fewerBracesEnabledCache @@ -386,10 +394,11 @@ object Scanners { */ def nextToken(): Unit = val lastToken = token + val lastName = name adjustSepRegions(lastToken) getNextToken(lastToken) if isAfterLineEnd then handleNewLine(lastToken) - postProcessToken() + postProcessToken(lastToken, lastName) printState() final def printState() = @@ -420,7 +429,7 @@ object Scanners { && { // Is current lexeme assumed to start an expression? // This is the case if the lexime is one of the tokens that - // starts an expression or it is a COLONEOL. Furthermore, if + // starts an expression or it is a COLONeol. Furthermore, if // the previous token is in backticks, the lexeme may not be a binary operator. // I.e. in // @@ -431,7 +440,7 @@ object Scanners { // in backticks and is a binary operator. Hence, `x` is not classified as a // leading infix operator. def assumeStartsExpr(lexeme: TokenData) = - (canStartExprTokens.contains(lexeme.token) || lexeme.token == COLONEOL) + (canStartExprTokens.contains(lexeme.token) || lexeme.token == COLONeol) && (!lexeme.isOperator || nme.raw.isUnary(lexeme.name)) val lookahead = LookaheadScanner() lookahead.allowLeadingInfixOperators = false @@ -607,12 +616,11 @@ object Scanners { currentRegion match case r: Indented => insert(OUTDENT, offset) - if next.token != COLON then - handleNewIndentWidth(r.enclosing, ir => - errorButContinue( - i"""The start of this line does not match any of the previous indentation widths. - |Indentation width of current line : $nextWidth - |This falls between previous widths: ${ir.width} and $lastWidth""")) + handleNewIndentWidth(r.enclosing, ir => + errorButContinue( + i"""The start of this line does not match any of the previous indentation widths. + |Indentation width of current line : $nextWidth + |This falls between previous widths: ${ir.width} and $lastWidth""")) case r => if skipping then if r.enclosing.isClosedByUndentAt(nextWidth) then @@ -629,7 +637,7 @@ object Scanners { currentRegion.knownWidth = nextWidth else if (lastWidth != nextWidth) errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth)) - if token != OUTDENT || next.token == COLON then + if token != OUTDENT then handleNewIndentWidth(currentRegion, _.otherIndentWidths += nextWidth) end handleNewLine @@ -638,19 +646,24 @@ object Scanners { |Previous indent : $lastWidth |Latest indent : $nextWidth""" - def observeColonEOL(): Unit = - if token == COLON then + def observeColonEOL(inTemplate: Boolean): Unit = + val enabled = + if token == COLONop && inTemplate then + report.deprecationWarning(em"`:` after symbolic operator is deprecated; use backticks around operator instead", sourcePos(offset)) + true + else token == COLONfollow && (inTemplate || fewerBracesEnabled) + if enabled then lookAhead() val atEOL = isAfterLineEnd || token == EOF reset() - if atEOL then token = COLONEOL + if atEOL then token = COLONeol def observeIndented(): Unit = if indentSyntax && isNewLine then val nextWidth = indentWidth(next.offset) val lastWidth = currentRegion.indentWidth if lastWidth < nextWidth then - currentRegion = Indented(nextWidth, COLONEOL, currentRegion) + currentRegion = Indented(nextWidth, COLONeol, currentRegion) offset = next.offset token = INDENT end observeIndented @@ -683,10 +696,10 @@ object Scanners { case _ => /** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT - * SEMI + ELSE => ELSE, COLON + => COLONEOL + * SEMI + ELSE => ELSE, COLON following id/)/] => COLONfollow * - Insert missing OUTDENTs at EOF */ - def postProcessToken(): Unit = { + def postProcessToken(lastToken: Token, lastName: SimpleName): Unit = { def fuse(tok: Int) = { token = tok offset = prev.offset @@ -721,8 +734,10 @@ object Scanners { reset() case END => if !isEndMarker then token = IDENTIFIER - case COLON => - if fewerBracesEnabled then observeColonEOL() + case COLONop => + if lastToken == IDENTIFIER && lastName != null && isIdentifierStart(lastName.head) + || colonEOLPredecessors.contains(lastToken) + then token = COLONfollow case RBRACE | RPAREN | RBRACKET => closeIndented() case EOF => @@ -1067,7 +1082,7 @@ object Scanners { reset() next - class LookaheadScanner() extends Scanner(source, offset) { + class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset) { override def languageImportContext = Scanner.this.languageImportContext } @@ -1179,7 +1194,7 @@ object Scanners { isSoftModifier && inModifierPosition() def isSoftModifierInParamModifierPosition: Boolean = - isSoftModifier && lookahead.token != COLON + isSoftModifier && !lookahead.isColon def isErased: Boolean = isIdent(nme.erased) && erasedEnabled @@ -1518,7 +1533,9 @@ object Scanners { case NEWLINE => ";" case NEWLINES => ";;" case COMMA => "," - case _ => showToken(token) + case COLONfollow | COLONeol => "':'" + case _ => + if debugTokenStream then showTokenDetailed(token) else showToken(token) } /* Resume normal scanning after XML */ diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 1f414e7e912c..82a5297faf97 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -16,13 +16,6 @@ abstract class TokensCommon { def tokenRange(lo: Int, hi: Int): TokenSet = BitSet(lo to hi: _*) - def showTokenDetailed(token: Int): String = debugString(token) - - def showToken(token: Int): String = { - val str = tokenString(token) - if (isKeyword(token)) s"'$str'" else str - } - val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) def enter(token: Int, str: String, debugStr: String = ""): Unit = { @@ -107,7 +100,7 @@ abstract class TokensCommon { /** special keywords */ //inline val USCORE = 73; enter(USCORE, "_") - inline val COLON = 74; enter(COLON, ":") + inline val COLONop = 74; enter(COLONop, ":") // a stand-alone `:`, see also COLONfollow inline val EQUALS = 75; enter(EQUALS, "=") //inline val LARROW = 76; enter(LARROW, "<-") //inline val ARROW = 77; enter(ARROW, "=>") @@ -204,8 +197,11 @@ object Tokens extends TokensCommon { inline val QUOTE = 87; enter(QUOTE, "'") - inline val COLONEOL = 88; enter(COLONEOL, ":", ": at eol") - inline val SELFARROW = 89; enter(SELFARROW, "=>") // reclassified ARROW following self-type + inline val COLONfollow = 88; enter(COLONfollow, ":") + // A `:` following an alphanumeric identifier or one of the tokens in colonEOLPredecessors + inline val COLONeol = 89; enter(COLONeol, ":", ": at eol") + // A `:` recognized as starting an indentation block + inline val SELFARROW = 90; enter(SELFARROW, "=>") // reclassified ARROW following self-type /** XML mode */ inline val XMLSTART = 99; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate @@ -233,7 +229,7 @@ object Tokens extends TokensCommon { final val canStartExprTokens2: TokenSet = canStartExprTokens3 | BitSet(DO) final val canStartTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet( - THIS, SUPER, USCORE, LPAREN, AT) + THIS, SUPER, USCORE, LPAREN, LBRACE, AT) final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) @@ -276,7 +272,7 @@ object Tokens extends TokensCommon { final val closingRegionTokens = BitSet(RBRACE, RPAREN, RBRACKET, CASE) | statCtdTokens final val canStartIndentTokens: BitSet = - statCtdTokens | BitSet(COLONEOL, WITH, EQUALS, ARROW, CTXARROW, LARROW, WHILE, TRY, FOR, IF, THROW, RETURN) + statCtdTokens | BitSet(COLONeol, WITH, EQUALS, ARROW, CTXARROW, LARROW, WHILE, TRY, FOR, IF, THROW, RETURN) /** Faced with the choice between a type and a formal parameter, the following * tokens determine it's a formal parameter. @@ -287,7 +283,16 @@ object Tokens extends TokensCommon { final val endMarkerTokens = identifierTokens | BitSet(IF, WHILE, FOR, MATCH, TRY, NEW, THROW, GIVEN, VAL, THIS) + final val colonEOLPredecessors = BitSet(RPAREN, RBRACKET, BACKQUOTED_IDENT, THIS, SUPER, NEW) + final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE) final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix) + + def showTokenDetailed(token: Int): String = debugString(token) + + def showToken(token: Int): String = { + val str = tokenString(token) + if isKeyword(token) || token == COLONfollow || token == COLONeol then s"'$str'" else str + } } diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 55b098e91849..35ada397a0d3 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -3,6 +3,20 @@ layout: doc-page title: "Scala 3 Syntax Summary" --- + + The following description of Scala tokens uses literal characters `‘c’` when referring to the ASCII fragment `\u0000` – `\u007F`. @@ -88,7 +102,6 @@ nl ::= “new line character” semi ::= ‘;’ | nl {nl} ``` - ## Optional Braces The lexical analyzer also inserts `indent` and `outdent` tokens that represent regions of indented code [at certain points](../reference/other-new-features/indentation.md) @@ -96,14 +109,17 @@ The lexical analyzer also inserts `indent` and `outdent` tokens that represent r In the context-free productions below we use the notation `<<< ts >>>` to indicate a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent`. Analogously, the notation `:<<< ts >>>` indicates a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent` that follows -a `:` at the end of a line. +a `colon` token. +A `colon` token reads as the standard colon "`:`" but is generated instead of it where `colon` is legal according to the context free syntax, but only if the previous token +is an alphanumeric identifier, a backticked identifier, or one of the tokens `this`, `super`, `new`, "`)`", and "`]`". ``` +colon ::= ':' -- with side conditions explained above <<< ts >>> ::= ‘{’ ts ‘}’ | indent ts outdent :<<< ts >>> ::= [nl] ‘{’ ts ‘}’ - | `:` indent ts outdent + | colon indent ts outdent ``` ## Keywords @@ -124,7 +140,7 @@ type val var while with yield ### Soft keywords ``` -as derives end extension infix inline opaque open transparent using | * + - +as derives end extension infix inline opaque open throws transparent using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -197,7 +213,7 @@ FunArgTypes ::= FunArgType { ‘,’ FunArgType } ParamType ::= [‘=>’] ParamValueType ParamValueType ::= Type [‘*’] PostfixOp(t, "*") TypeArgs ::= ‘[’ Types ‘]’ ts -Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ds +Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> ds TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi) TypeParamBounds ::= TypeBounds {‘:’ Type} ContextBounds(typeBounds, tps) Types ::= Type {‘,’ Type} @@ -231,13 +247,13 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[ Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) Catches ::= ‘catch’ (Expr | ExprCaseClause) -PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) +PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) -- only if language.postfixOperators is enabled InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) - | InfixExpr id ‘:’ IndentedExpr + | InfixExpr id ColonArgument | InfixExpr MatchClause MatchClause ::= ‘match’ <<< CaseClauses >>> Match(expr, cases) -PrefixExpr ::= [PrefixOperator] SimpleExpr PrefixOp(expr, op) +PrefixExpr ::= [PrefixOperator] SimpleExpr PrefixOp(expr, op) PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’ SimpleExpr ::= SimpleRef | Literal @@ -253,11 +269,13 @@ SimpleExpr ::= SimpleRef | SimpleExpr ‘.’ MatchClause | SimpleExpr TypeArgs TypeApply(expr, args) | SimpleExpr ArgumentExprs Apply(expr, args) - | SimpleExpr ‘:’ IndentedExpr -- under language.experimental.fewerBraces - | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedExpr -- under language.experimental.fewerBraces + | SimpleExpr ColonArgument -- under language.experimental.fewerBraces | SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped) - | XmlExpr -- to be dropped -IndentedExpr ::= indent CaseClauses | Block outdent + | XmlExpr -- to be dropped +ColonArgument ::= colon [LambdaStart] + indent (CaseClauses | Block) outdent +LambdaStart ::= FunParams (‘=>’ | ‘?=>’) + | HkTypeParamClause ‘=>’ Quoted ::= ‘'’ ‘{’ Block ‘}’ | ‘'’ ‘[’ Type ‘]’ ExprSplice ::= spliceId -- if inside quoted block @@ -300,7 +318,7 @@ TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) -Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) +Pattern2 ::= [id ‘@’] InfixPattern [‘*’] Bind(name, pat) InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) SimplePattern ::= PatVar Ident(wildcard) | Literal Bind(name, Ident(wildcard)) diff --git a/docs/_docs/reference/experimental/fewer-braces.md b/docs/_docs/reference/experimental/fewer-braces.md index a6275e73a413..eb454886ad03 100644 --- a/docs/_docs/reference/experimental/fewer-braces.md +++ b/docs/_docs/reference/experimental/fewer-braces.md @@ -4,80 +4,4 @@ title: "Fewer Braces" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/fewer-braces.html --- -By and large, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is one exception, though: Arguments to function can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile. - -To allow such arguments to be written without braces, a variant of the indentation scheme is implemented under language import -```scala -import language.experimental.fewerBraces -``` -Alternatively, it can be enabled with command line option `-language:experimental.fewerBraces`. - -This variant is more contentious and less stable than the rest of the significant indentation scheme. It allows to replace a function argument in braces by a `:` at the end of a line and indented code, similar to the convention for class bodies. It also allows to leave out braces around arguments that are multi-line function values. - -## Using `:` At End Of Line - - -Similar to what is done for classes and objects, a `:` that follows a function reference at the end of a line means braces can be omitted for function arguments. Example: -```scala -times(10): - println("ah") - println("ha") -``` - -The colon can also follow an infix operator: - -```scala -credentials ++ : - val file = Path.userHome / ".credentials" - if file.exists - then Seq(Credentials(file)) - else Seq() -``` - -Function calls that take multiple argument lists can also be handled this way: - -```scala -val firstLine = files.get(fileName).fold: - val fileNames = files.values - s"""no file named $fileName found among - |${values.mkString(\n)}""".stripMargin - : - f => - val lines = f.iterator.map(_.readLine) - lines.mkString("\n) -``` - - -## Lambda Arguments Without Braces - -Braces can also be omitted around multiple line function value arguments: -```scala -val xs = elems.map x => - val y = x - 1 - y * y -xs.foldLeft (x, y) => - x + y -``` -Braces can be omitted if the lambda starts with a parameter list and `=>` or `=>?` at the end of one line and it has an indented body on the following lines. - -## Syntax Changes - -``` -SimpleExpr ::= ... - | SimpleExpr `:` IndentedArgument - | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument -InfixExpr ::= ... - | InfixExpr id `:` IndentedArgument -IndentedArgument ::= indent (CaseClauses | Block) outdent -``` - -Note that a lambda argument must have the `=>` at the end of a line for braces -to be optional. For instance, the following would also be incorrect: - -```scala - xs.map x => x + 1 // error: braces or parentheses are required -``` -The lambda has to be enclosed in braces or parentheses: -```scala - xs.map(x => x + 1) // ok -``` +The documentation contained in this file is now part of [./indentation.html]. \ No newline at end of file diff --git a/docs/_docs/reference/other-new-features/indentation.md b/docs/_docs/reference/other-new-features/indentation.md index 377869c622c9..299a01eb32e0 100644 --- a/docs/_docs/reference/other-new-features/indentation.md +++ b/docs/_docs/reference/other-new-features/indentation.md @@ -61,7 +61,7 @@ There are two rules: - after the leading parameters of an `extension`, or - after a `with` in a given instance, or - - after a ": at end of line" token (see below) + - after a `:` at the start of a template body (see discussion of `` below), or - after one of the following tokens: ``` @@ -134,12 +134,14 @@ is parsed as `if x then a + b + c else d`. The Scala grammar uses the term _template body_ for the definitions of a class, trait, or object that are normally enclosed in braces. The braces around a template body can also be omitted by means of the following rule. -If at the point where a template body can start there is a `:` that occurs at the end -of a line, and that is followed by at least one indented statement, the recognized -token is changed from ":" to ": at end of line". The latter token is one of the tokens -that can start an indentation region. The Scala grammar is changed so an optional ": at end of line" is allowed in front of a template body. +A template body can alternatively consist of a colon followed by one or more indented statements. To this purpose we introduce a new `` token that reads as +the standard colon "`:`" but is generated instead of it where `` +is legal according to the context free syntax, but only if the previous token +is an alphanumeric identifier, a backticked identifier, or one of the tokens `this`, `super`, "`)`", and "`]`". -Analogous rules apply for enum bodies and local packages containing nested definitions. +An indentation region can start after a ``. A template body may be either enclosed in braces, or it may start with +` ` and end with ``. +Analogous rules apply for enum bodies, type refinements, and local packages containing nested definitions. With these new rules, the following constructs are all valid: @@ -170,17 +172,19 @@ In each case, the `:` at the end of line can be replaced without change of meani The syntax changes allowing this are as follows: +Define for an arbitrary sequence of tokens or non-terminals `TS`: + ``` -Template ::= InheritClauses [colonEol] [TemplateBody] -EnumDef ::= id ClassConstr InheritClauses [colonEol] EnumBody -Packaging ::= ‘package’ QualId [nl | colonEol] ‘{’ TopStatSeq ‘}’ -SimpleExpr ::= ‘new’ ConstrApp {‘with’ ConstrApp} [[colonEol] TemplateBody] +:<<< TS >>> ::= ‘{’ TS ‘}’ + | +``` +Then the grammar changes as follows: +``` +TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>> +EnumBody ::= :<<< [SelfType] EnumStat {semi EnumStat} >>> +Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> +Packaging ::= ‘package’ QualId :<<< TopStats >>> ``` - -Here, `colonEol` stands for ": at end of line", as described above. -The lexical analyzer is modified so that a `:` at the end of a line -is reported as `colonEol` if the parser is at a point where a `colonEol` is -valid as next token. ### Spaces vs Tabs @@ -444,15 +448,15 @@ indented regions where possible. When invoked with options `-rewrite -no-indent` The `-indent` option only works on [new-style syntax](./control-syntax.md). So to go from old-style syntax to new-style indented code one has to invoke the compiler twice, first with options `-rewrite -new-syntax`, then again with options `-rewrite -indent`. To go in the opposite direction, from indented code to old-style syntax, it's `-rewrite -no-indent`, followed by `-rewrite -old-syntax`. -### Variant: Indentation Marker `:` +### Variant: Indentation Marker `:` for Arguments -Generally, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is one exception, though: Arguments to function can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile. +Generally, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is one exception, though: Arguments to functions can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile. To allow such arguments to be written without braces, a variant of the indentation scheme is implemented under language import ```scala import language.experimental.fewerBraces ``` -This variant is more contentious and less stable than the rest of the significant indentation scheme. In this variant, a colon `:` at the end of a line is also one of the possible tokens that opens an indentation region. Examples: +In this variant, a `` token is also recognized where function argument would be expected. Examples: ```scala times(10): @@ -462,24 +466,44 @@ times(10): or +```scala +credentials `++`: + val file = Path.userHome / ".credentials" + if file.exists + then Seq(Credentials(file)) + else Seq() +``` + +or + ```scala xs.map: x => val y = x - 1 y * y ``` - -The colon is usable not only for lambdas and by-name parameters, but -also even for ordinary parameters: +What's more, a `:` in these settings can also be followed on the same line by the parameter part and arrow of a lambda. So the last example could be compressed to this: ```scala -credentials ++ : - val file = Path.userHome / ".credentials" - if file.exists - then Seq(Credentials(file)) - else Seq() +xs.map: x => + val y = x - 1 + y * y +``` +and the following would also be legal: +```scala +xs.foldLeft: (x, y) => + x + y ``` -How does this syntax variant work? Colons at the end of lines are their own token, distinct from normal `:`. -The Scala grammar is changed so that colons at end of lines are accepted at all points -where an opening brace enclosing an argument is legal. Special provisions are taken so that method result types can still use a colon on the end of a line, followed by the actual type on the next. +The grammar changes for this variant are as follows. + +``` +SimpleExpr ::= ... + | SimpleExpr ColonArgument +InfixExpr ::= ... + | InfixExpr id ColonArgument +ColonArgument ::= colon [LambdaStart] + indent (CaseClauses | Block) outdent +LambdaStart ::= FunParams (‘=>’ | ‘?=>’) + | HkTypeParamClause ‘=>’ +``` \ No newline at end of file diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index cbc570bb4e30..c26354bd6a25 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -4,6 +4,20 @@ title: "Scala 3 Syntax Summary" nightlyOf: https://docs.scala-lang.org/scala3/reference/syntax.html --- + + The following description of Scala tokens uses literal characters `‘c’` when referring to the ASCII fragment `\u0000` – `\u007F`. @@ -28,12 +42,12 @@ upper ::= ‘A’ | … | ‘Z’ | ‘\$’ | ‘_’ “… and U lower ::= ‘a’ | … | ‘z’ “… and Unicode category Ll” letter ::= upper | lower “… and Unicode categories Lo, Lt, Nl” digit ::= ‘0’ | … | ‘9’ -paren ::= ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’ | ‘'(’ | ‘'[’ | ‘'{’ +paren ::= ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’ delim ::= ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’ -opchar ::= “printableChar not matched by (whiteSpace | upper | - lower | letter | digit | paren | delim | opchar | - Unicode_Sm | Unicode_So)” -printableChar ::= “all characters in [\u0020, \u007F] inclusive” +opchar ::= ‘!’ | ‘#’ | ‘%’ | ‘&’ | ‘*’ | ‘+’ | ‘-’ | ‘/’ | ‘:’ | + ‘<’ | ‘=’ | ‘>’ | ‘?’ | ‘@’ | ‘\’ | ‘^’ | ‘|’ | ‘~’ + “… and Unicode categories Sm, So” +printableChar ::= “all characters in [\u0020, \u007E] inclusive” charEscapeSeq ::= ‘\’ (‘b’ | ‘t’ | ‘n’ | ‘f’ | ‘r’ | ‘"’ | ‘'’ | ‘\’) op ::= opchar {opchar} @@ -46,6 +60,7 @@ id ::= plainid | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ idrest ::= {letter | digit} [‘_’ op] quoteId ::= ‘'’ alphaid +spliceId ::= ‘$’ alphaid ; integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] decimalNumeral ::= ‘0’ | nonZeroDigit [{digit | ‘_’} digit] @@ -95,13 +110,17 @@ The lexical analyzer also inserts `indent` and `outdent` tokens that represent r In the context-free productions below we use the notation `<<< ts >>>` to indicate a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent`. Analogously, the notation `:<<< ts >>>` indicates a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent` that follows -a `:` at the end of a line. +a `colon` token. + +A `colon` token reads as the standard colon "`:`" but is generated instead of it where `colon` is legal according to the context free syntax, but only if the previous token +is an alphanumeric identifier, a backticked identifier, or one of the tokens `this`, `super`, `new`, "`)`", and "`]`". ``` +colon ::= ':' -- with side conditions explained above <<< ts >>> ::= ‘{’ ts ‘}’ | indent ts outdent :<<< ts >>> ::= [nl] ‘{’ ts ‘}’ - | `:` indent ts outdent + | colon indent ts outdent ``` ## Keywords @@ -115,15 +134,14 @@ given if implicit import lazy match new null object override package private protected return sealed super then throw trait true try type val var while with yield -: = <- => <: :> # +: = <- => <: >: # @ =>> ?=> ``` ### Soft keywords ``` -as derives end extension infix inline opaque open throws -transparent using | * + - +as derives end extension infix inline opaque open transparent using | * + - ``` See the [separate section on soft keywords](./soft-modifier.md) for additional @@ -182,8 +200,6 @@ SimpleType ::= SimpleLiteral | Singleton ‘.’ ‘type’ | ‘(’ Types ‘)’ | Refinement - | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern - | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern | SimpleType1 TypeArgs | SimpleType1 ‘#’ id Singleton ::= SimpleRef @@ -196,7 +212,7 @@ FunArgTypes ::= FunArgType { ‘,’ FunArgType } ParamType ::= [‘=>’] ParamValueType ParamValueType ::= Type [‘*’] TypeArgs ::= ‘[’ Types ‘]’ -Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ +Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeParamBounds ::= TypeBounds {‘:’ Type} Types ::= Type {‘,’ Type} @@ -241,8 +257,7 @@ SimpleExpr ::= SimpleRef | Literal | ‘_’ | BlockExpr - | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern - | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern + | ExprSplice | Quoted | quoteId -- only inside splices | ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] @@ -254,6 +269,9 @@ SimpleExpr ::= SimpleRef | SimpleExpr ArgumentExprs Quoted ::= ‘'’ ‘{’ Block ‘}’ | ‘'’ ‘[’ Type ‘]’ +ExprSplice ::= spliceId -- if inside quoted block + | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern + | ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted pattern ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type | Expr @@ -284,7 +302,7 @@ CaseClauses ::= CaseClause { CaseClause } CaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Block ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } -TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [semi] +TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] Pattern ::= Pattern1 { ‘|’ Pattern1 } Pattern1 ::= Pattern2 [‘:’ RefinedType] diff --git a/docs/sidebar.yml b/docs/sidebar.yml index c7edc92c1530..963949ec9d8f 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -138,7 +138,6 @@ subsection: directory: experimental index: reference/experimental/overview.md subsection: - - page: reference/experimental/fewer-braces.md - page: reference/experimental/canthrow.md - page: reference/experimental/erased-defs.md - page: reference/experimental/erased-defs-spec.md diff --git a/tests/init/pos/Parsers.scala b/tests/init/pos/ParserLocation.scala similarity index 95% rename from tests/init/pos/Parsers.scala rename to tests/init/pos/ParserLocation.scala index 2fa3f16839ba..c908fc4cb8e2 100644 --- a/tests/init/pos/Parsers.scala +++ b/tests/init/pos/ParserLocation.scala @@ -1,4 +1,4 @@ -object Parsers { +object ParserLocation { enum Location(val inParens: Boolean, val inPattern: Boolean, val inArgs: Boolean): case InParens extends Location(true, false, false) case InArgs extends Location(true, false, true) diff --git a/tests/neg-custom-args/nowarn/nowarn.check b/tests/neg-custom-args/nowarn/nowarn.check index 5fd085624254..232ea1a3a05f 100644 --- a/tests/neg-custom-args/nowarn/nowarn.check +++ b/tests/neg-custom-args/nowarn/nowarn.check @@ -63,7 +63,7 @@ Matching filters for @nowarn or -Wconf: | ^ | method f is deprecated -- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:47:10 ------------------------------------------------ -47 |def t7c = f: // warning (deprecation) +47 |def t7c = f // warning (deprecation) | ^ | method f is deprecated -- Unchecked Warning: tests/neg-custom-args/nowarn/nowarn.scala:53:7 --------------------------------------------------- @@ -78,10 +78,10 @@ Matching filters for @nowarn or -Wconf: 40 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) |^^^^^^^^^^^^^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg-custom-args/nowarn/nowarn.scala:48:3 --------------------------------------------------------------- -48 | @nowarn("msg=fish") // error (unused nowarn) - | ^^^^^^^^^^^^^^^^^^^ - | @nowarn annotation does not suppress any warnings +-- Error: tests/neg-custom-args/nowarn/nowarn.scala:48:5 --------------------------------------------------------------- +48 | : @nowarn("msg=fish") // error (unused nowarn) + | ^^^^^^^^^^^^^^^^^^^ + | @nowarn annotation does not suppress any warnings -- Error: tests/neg-custom-args/nowarn/nowarn.scala:60:0 --------------------------------------------------------------- 60 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) |^^^^^^^ diff --git a/tests/neg-custom-args/nowarn/nowarn.scala b/tests/neg-custom-args/nowarn/nowarn.scala index 252838767e30..f5d10a5f262a 100644 --- a/tests/neg-custom-args/nowarn/nowarn.scala +++ b/tests/neg-custom-args/nowarn/nowarn.scala @@ -42,10 +42,10 @@ def t6a = f // warning (refchecks, deprecation) @nowarn def t6f = f def t7a = f: @nowarn("cat=deprecation") -def t7b = f: - @nowarn("msg=deprecated") -def t7c = f: // warning (deprecation) - @nowarn("msg=fish") // error (unused nowarn) +def t7b = f + : @nowarn("msg=deprecated") +def t7c = f // warning (deprecation) + : @nowarn("msg=fish") // error (unused nowarn) def t7d = f: @nowarn("") def t7e = f: @nowarn diff --git a/tests/neg/closure-args.scala b/tests/neg/closure-args.scala index 9b59de205d86..def85f5924a1 100644 --- a/tests/neg/closure-args.scala +++ b/tests/neg/closure-args.scala @@ -1,9 +1,23 @@ import language.experimental.fewerBraces -val x = List().map (x: => Int) => // error +val x = List(1).map: (x: => Int) => // error ??? -val y = List() map x => // error - x + 1 // error -val z = List() map + => // error +val z = List(1).map: + => // ok ??? +val xs = List(1) +val b: Int = xs // error + .map: x => x * x // error + .filter: y => y > 0 // error + (0) +val d = xs // error + .map: x => x.toString + xs.dropWhile: + y => y > 0 + +val c = List(xs.map: y => y + y) // error // error +val d2: String = xs // error + .map: x => x.toString + xs.dropWhile: y => y > 0 // error // error + .filter: z => !z.isEmpty // error + (0) + +val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error // error diff --git a/tests/neg/cycles.scala b/tests/neg/cycles.scala index b77d253bd527..b24a367c8377 100644 --- a/tests/neg/cycles.scala +++ b/tests/neg/cycles.scala @@ -38,6 +38,6 @@ class T2 { type U = X | Int } object T12 { - ??? : (T1 {})#U // old-error: conflicting bounds + val _ : (T1 {})#U = ??? // old-error: conflicting bounds ??? : (T2 {})#U // old-error: conflicting bounds } diff --git a/tests/neg/i13769.check b/tests/neg/i13769.check index 3d7af1bd06a1..8291a84fc899 100644 --- a/tests/neg/i13769.check +++ b/tests/neg/i13769.check @@ -1,10 +1,6 @@ --- Error: tests/neg/i13769.scala:2:18 ---------------------------------------------------------------------------------- -2 |val te = tup.map((x: _ <: Int) => List(x)) // error // error - | ^^^^^^^^^^^ - | not a legal formal parameter --- [E006] Not Found Error: tests/neg/i13769.scala:2:39 ----------------------------------------------------------------- -2 |val te = tup.map((x: _ <: Int) => List(x)) // error // error - | ^ - | Not found: x +-- [E035] Syntax Error: tests/neg/i13769.scala:2:21 -------------------------------------------------------------------- +2 |val te = tup.map((x: _ <: Int) => List(x)) // error + | ^^^^^^^^ + | Unbound wildcard type | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i13769.scala b/tests/neg/i13769.scala index 67575e821334..e4e66fe2cef2 100644 --- a/tests/neg/i13769.scala +++ b/tests/neg/i13769.scala @@ -1,2 +1,2 @@ val tup = (1, "s") -val te = tup.map((x: _ <: Int) => List(x)) // error // error +val te = tup.map((x: _ <: Int) => List(x)) // error diff --git a/tests/neg/i1424.scala b/tests/neg/i1424.scala index 8eba3284211b..deffd671761e 100644 --- a/tests/neg/i1424.scala +++ b/tests/neg/i1424.scala @@ -1,3 +1,3 @@ class Test { - (x: Int) => x // error: not a legal self type clause // error: not found x + (x: Int) => x // error: not a legal self type clause } diff --git a/tests/neg/i4373b.scala b/tests/neg/i4373b.scala index 297fcd76ff08..45b60a46c721 100644 --- a/tests/neg/i4373b.scala +++ b/tests/neg/i4373b.scala @@ -1,5 +1,5 @@ // ==> 05bef7805687ba94da37177f7568e3ba7da1f91c.scala <== class x0 { - x1: // error + x1: x0 | _ // error // error \ No newline at end of file diff --git a/tests/neg/i7751.scala b/tests/neg/i7751.scala index ed33723a152d..4c835a533704 100644 --- a/tests/neg/i7751.scala +++ b/tests/neg/i7751.scala @@ -1,3 +1,3 @@ import language.experimental.fewerBraces -val a = Some(a=a,)=> // error // error // error +val a = Some(a=a,)=> // error // error val a = Some(x=y,)=> diff --git a/tests/neg/i7818.scala b/tests/neg/i7818.scala index 1dc243cfeedc..78cbee506784 100644 --- a/tests/neg/i7818.scala +++ b/tests/neg/i7818.scala @@ -1 +1 @@ -def foo = (x: @) => () // error // error \ No newline at end of file +def foo = (x: @) => () // error \ No newline at end of file diff --git a/tests/neg/indent-colons.check b/tests/neg/indent-colons.check new file mode 100644 index 000000000000..06bd7a31b079 --- /dev/null +++ b/tests/neg/indent-colons.check @@ -0,0 +1,61 @@ +-- Error: tests/neg/indent-colons.scala:6:4 ---------------------------------------------------------------------------- +6 | : // error + | ^ + | end of statement expected but ':' found +-- Error: tests/neg/indent-colons.scala:12:2 --------------------------------------------------------------------------- +12 | : // error + | ^ + | end of statement expected but ':' found +-- Error: tests/neg/indent-colons.scala:19:2 --------------------------------------------------------------------------- +19 | : // error + | ^ + | end of statement expected but ':' found +-- [E018] Syntax Error: tests/neg/indent-colons.scala:26:14 ------------------------------------------------------------ +26 | val y = 1 + : // error + | ^ + | expression expected but : found + | + | longer explanation available when compiling with `-explain` +-- [E018] Syntax Error: tests/neg/indent-colons.scala:30:27 ------------------------------------------------------------ +30 | val all = credentials ++ : // error + | ^ + | expression expected but : found + | + | longer explanation available when compiling with `-explain` +-- [E134] Type Error: tests/neg/indent-colons.scala:23:12 -------------------------------------------------------------- +23 | val x = 1.+ : // error + | ^^^ + | None of the overloaded alternatives of method + in class Int with types + | (x: Double): Double + | (x: Float): Float + | (x: Long): Long + | (x: Int): Int + | (x: Char): Int + | (x: Short): Int + | (x: Byte): Int + | (x: String): String + | match expected type (2 : Int) +-- [E006] Not Found Error: tests/neg/indent-colons.scala:32:7 ---------------------------------------------------------- +32 | if file.isEmpty // error + | ^^^^ + | Not found: file + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/indent-colons.scala:34:13 --------------------------------------------------------- +34 | else Seq(file) // error + | ^^^^ + | Not found: file + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/indent-colons.scala:4:2 ---------------------------------------------------------------------------- +4 | tryEither: // error + | ^^^^^^^^^ + | missing arguments for method tryEither +-- Error: tests/neg/indent-colons.scala:10:2 --------------------------------------------------------------------------- +10 | tryEither: // error + | ^^^^^^^^^ + | missing arguments for method tryEither +-- Error: tests/neg/indent-colons.scala:17:2 --------------------------------------------------------------------------- +17 | Some(3).fold: // error + | ^^^^^^^^^^^^ + | missing arguments for method fold in class Option diff --git a/tests/neg/indent-colons.scala b/tests/neg/indent-colons.scala new file mode 100644 index 000000000000..5364713dd4aa --- /dev/null +++ b/tests/neg/indent-colons.scala @@ -0,0 +1,34 @@ +def tryEither[T](x: T)(y: Int => T): T = ??? + +def test1 = + tryEither: // error + "hello" + : // error + y => y.toString + +def test2 = + tryEither: // error + "hello" + : // error + _.toString + + +val o = + Some(3).fold: // error + "nothing" + : // error + x => x.toString + +object Test23: + val x = 1.+ : // error + 2 + + val y = 1 + : // error + x + + val credentials = List("OK") + val all = credentials ++ : // error + val file = "file" + if file.isEmpty // error + then Seq("none") + else Seq(file) // error \ No newline at end of file diff --git a/tests/neg/parser-stability-16.scala b/tests/neg/parser-stability-16.scala index 9ba58d0219f2..25fb38374c45 100644 --- a/tests/neg/parser-stability-16.scala +++ b/tests/neg/parser-stability-16.scala @@ -3,4 +3,3 @@ class x0[x0] { } trait x3 extends x0 { // error x1 = 0 object // error // error -// error \ No newline at end of file diff --git a/tests/neg/parser-stability-5.scala b/tests/neg/parser-stability-5.scala index 69f4568aab73..5de49927ee58 100644 --- a/tests/neg/parser-stability-5.scala +++ b/tests/neg/parser-stability-5.scala @@ -1,4 +1,4 @@ trait x0 { -x1 : { // error +x1 : { var x2 // error \ No newline at end of file diff --git a/tests/pos/closure-args.scala b/tests/pos/closure-args.scala index dd30baf7d2b0..98e49407f9b0 100644 --- a/tests/pos/closure-args.scala +++ b/tests/pos/closure-args.scala @@ -1,16 +1,36 @@ import language.experimental.fewerBraces -val xs = List(1, 2, 3) -val ys = xs.map x => - x + 1 -val x = ys.foldLeft(0) (x, y) => - x + y -val y = ys.foldLeft(0) (x: Int, y: Int) => - val z = x + y - z * z -val as: Int = xs - .map x => - x * x - .filter y => +object Test1: + val xs = List(1, 2, 3) + val ys = xs.map: x => + x + 1 + val ys1 = List(1) map: x => + x + 1 + val x = ys.foldLeft(0): (x, y) => + x + y + val y = ys.foldLeft(0): (x: Int, y: Int) => + val z = x + y + z * z + val a: Int = xs + .map: x => + x * x + .filter: (y: Int) => + y > 0 + (0) + val e = xs.map: + case 1 => 2 + case 2 => 3 + case x => x + .filter: + x => x > 0 + + extension (xs: List[Int]) def foo(f: [X] => X => X) = () + + val p = xs.foo: + [X] => (x: X) => x + + val q = (x: String => String) => x + + val r = x < 0 && locally: y > 0 - (0) + diff --git a/tests/pos/i12218.scala b/tests/pos/i12218.scala index 515e71f83fc8..da1e1bc61184 100644 --- a/tests/pos/i12218.scala +++ b/tests/pos/i12218.scala @@ -3,7 +3,7 @@ import language.experimental.fewerBraces val arr = Array(1,2,3) if arr.isEmpty - || : + || locally: val first = arr(0) first != 1 then println("invalid arr") diff --git a/tests/pos/indent-colons.scala b/tests/pos/indent-colons.scala index 09abc69a483a..b839568a7aff 100644 --- a/tests/pos/indent-colons.scala +++ b/tests/pos/indent-colons.scala @@ -122,35 +122,41 @@ def tryEither[T](x: T)(y: Int => T): T = ??? def test1 = tryEither: - "hello" - : - y => y.toString + "hello" + .apply: + y => y.toString def test2 = tryEither: "hello" - : + .apply: _.toString val o = Some(3).fold: "nothing" - : + .apply: x => x.toString object Test23: - val x = 1.+ : // ok - 2 - val y = 1 + : // ok + transparent inline def nested[T](inline x: T): T = x + + val x = (1.+): 2 + val y = 1 + nested: // ok + x + + val _ = 1 `+`: // ok + x + val r = 1 to: 100 val credentials = List("OK") - val all = credentials ++ : + val all = credentials ++ nested: val file = "file" if file.isEmpty then Seq("none") @@ -162,7 +168,7 @@ extension (x: Boolean) def test24(x: Int, y: Int) = x < y or: x > y - or: + `or`: x == y diff --git a/tests/pos/no-selftype.scala b/tests/pos/no-selftype.scala new file mode 100644 index 000000000000..6774aafd36b3 --- /dev/null +++ b/tests/pos/no-selftype.scala @@ -0,0 +1,7 @@ +import language.experimental.fewerBraces +object f: + def apply(x: Int) = println(x) + +class C: + f: + 22 diff --git a/tests/run/LazyLists.scala b/tests/run/LazyLists.scala index d5d533fc64fd..2389b6de3369 100644 --- a/tests/run/LazyLists.scala +++ b/tests/run/LazyLists.scala @@ -46,7 +46,7 @@ package xcollections: val empty: LazyList[Nothing] = new: protected def force(): LazyList[Nothing] = this - object #:: : + object `#::`: def unapply[T](xs: LazyList[T]): Option[(T, LazyList[T])] = if xs.isEmpty then None else Some((xs.head, xs.tail))