diff --git a/community-build/community-projects/Monocle b/community-build/community-projects/Monocle index a0e70744e9b3..49a6e56aafd5 160000 --- a/community-build/community-projects/Monocle +++ b/community-build/community-projects/Monocle @@ -1 +1 @@ -Subproject commit a0e70744e9b3bfb0f12e4ea292151c49c3302cd1 +Subproject commit 49a6e56aafd5a1aafe02731659315299c5782ef4 diff --git a/community-build/community-projects/akka b/community-build/community-projects/akka index ed97fe5233cb..2f075cf6f171 160000 --- a/community-build/community-projects/akka +++ b/community-build/community-projects/akka @@ -1 +1 @@ -Subproject commit ed97fe5233cbda2da02abad50d48c310077b313c +Subproject commit 2f075cf6f171cc529f9496d6dc92216625ab9558 diff --git a/community-build/community-projects/cats b/community-build/community-projects/cats index 704c7fd5d207..22291cbb270f 160000 --- a/community-build/community-projects/cats +++ b/community-build/community-projects/cats @@ -1 +1 @@ -Subproject commit 704c7fd5d2079854637885514fccb62165e267f7 +Subproject commit 22291cbb270f400c0ef19079fabc2bc5c1f8c60e diff --git a/community-build/community-projects/http4s b/community-build/community-projects/http4s index fc0a18dd0a80..4e7f94d794f5 160000 --- a/community-build/community-projects/http4s +++ b/community-build/community-projects/http4s @@ -1 +1 @@ -Subproject commit fc0a18dd0a8091f0b2cdc87b58953e76efa47426 +Subproject commit 4e7f94d794f57ff2e336d70e1ffba886682ff91b diff --git a/community-build/community-projects/play-json b/community-build/community-projects/play-json index 8a221e6465b5..5398d30311b5 160000 --- a/community-build/community-projects/play-json +++ b/community-build/community-projects/play-json @@ -1 +1 @@ -Subproject commit 8a221e6465b5139f46e63dd20957e5dcb2c73019 +Subproject commit 5398d30311b562aaaf11b2bbf74aa2464dc2f85a diff --git a/community-build/community-projects/protoquill b/community-build/community-projects/protoquill index 16d26fcb3072..08ee275ba1b9 160000 --- a/community-build/community-projects/protoquill +++ b/community-build/community-projects/protoquill @@ -1 +1 @@ -Subproject commit 16d26fcb30720b9aa81d29f08b9da10916e269a2 +Subproject commit 08ee275ba1b9899310053916de831321e9bee12b diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 5909829cf847..c1c5f8ff89fd 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 5909829cf84712e877cf6d50efae8f2deacaaa84 +Subproject commit c1c5f8ff89fd132ca3cc001e01958a350adf9241 diff --git a/community-build/community-projects/scalaz b/community-build/community-projects/scalaz index ee85b0925809..dfe6ab18d3a6 160000 --- a/community-build/community-projects/scalaz +++ b/community-build/community-projects/scalaz @@ -1 +1 @@ -Subproject commit ee85b0925809f6e04808a6124ae04dd89adba0d6 +Subproject commit dfe6ab18d3a6b952b260ca34fb43bca02c78c648 diff --git a/community-build/community-projects/zio b/community-build/community-projects/zio index 25977ff09847..09e61d4b1efa 160000 --- a/community-build/community-projects/zio +++ b/community-build/community-projects/zio @@ -1 +1 @@ -Subproject commit 25977ff09847f1e7857f799e0abaf00c82003e76 +Subproject commit 09e61d4b1efa523e3c022e4616b95b87134f7820 diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c78e4cd8fb2f..9bb7e744e890 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 @@ -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,20 +445,24 @@ 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 Typed(Splice(Ident(name)), tpt) => - makeParameter(("$" + name).toTermName, tpt, mods).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 Typed(Splice(Ident(name)), tpt) => + makeParameter(("$" + name).toTermName, tpt, mods).withSpan(tree.span) + case _ => + fail() /** Convert (qual)ident to type identifier */ @@ -891,6 +896,15 @@ object Parsers { val next = in.lookahead.token next == LBRACKET || next == LPAREN + def followingIsSelfType() = + val lookahead = in.LookaheadScanner(allowIndent = true) + lookahead.nextToken() + lookahead.token == COLON + && { + lookahead.nextToken() + canStartTypeTokens(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 = @@ -905,6 +919,36 @@ 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 `?=>`. + */ + def followingIsLambdaAfterColon(): Boolean = + val lookahead = in.LookaheadScanner() + lookahead.nextToken() + if lookahead.isIdent || lookahead.token == USCORE then + lookahead.nextToken() + lookahead.isArrow + else if lookahead.token == LPAREN || lookahead.token == LBRACKET then + lookahead.skipParens() + lookahead.isArrow + else false + /* --------- OPERAND/OPERATOR STACK --------------------------------------- */ var opStack: List[OpInfo] = Nil @@ -1361,48 +1405,14 @@ object Parsers { def typ(): Tree = { val start = in.offset var imods = Modifiers() - def functionRest(params: List[Tree]): Tree = - val paramSpan = Span(start, in.lastOffset) - atSpan(start, in.offset) { - if in.token == TLARROW then - if !imods.flags.isEmpty || params.isEmpty then - syntaxError(em"illegal parameter list for type lambda", start) - in.token = ARROW - else - for case ValDef(_, tpt: ByNameTypeTree, _) <- params do - syntaxError(em"parameter of type lambda may not be call-by-name", tpt.span) - in.nextToken() - return TermLambdaTypeTree(params.asInstanceOf[List[ValDef]], typ()) - - if in.token == CTXARROW then - in.nextToken() - imods |= Given - else - accept(ARROW) - val t = typ() - - if imods.isOneOf(Given | Erased) then - if imods.is(Given) && params.isEmpty then - syntaxError("context function types require at least one parameter", paramSpan) - new FunctionWithMods(params, t, imods) - else if !ctx.settings.YkindProjector.isDefault then - val (newParams :+ newT, tparams) = replaceKindProjectorPlaceholders(params :+ t): @unchecked - - lambdaAbstract(tparams, Function(newParams, newT)) - else - Function(params, t) - } - var isValParamList = false - val t = - if (in.token == LPAREN) { + if in.token == LPAREN then in.nextToken() - if (in.token == RPAREN) { + if in.token == RPAREN then in.nextToken() - functionRest(Nil) - } - else { + functionTypeRest(Nil, start, Modifiers()) + else if isErased then imods = addModifier(imods) val paramStart = in.offset val ts = in.currentRegion.withCommasExpected { @@ -1417,8 +1427,8 @@ object Parsers { } accept(RPAREN) if isValParamList || in.isArrow then - functionRest(ts) - else { + functionTypeRest(ts, start, imods) + else val ts1 = for (t <- ts) yield t match { @@ -1434,33 +1444,29 @@ object Parsers { withTypeRest( annotTypeRest( simpleTypeRest(tuple))))) - } - } - } - else if (in.token == LBRACKET) { + else if in.token == LBRACKET then val start = in.offset val tparams = typeParamClause(ParamOwner.TypeParam) - if (in.token == TLARROW) + if in.token == TLARROW then atSpan(start, in.skipToken())(LambdaTypeTree(tparams, toplevelTyp())) - else if (in.token == ARROW) { + else if in.token == ARROW then val arrowOffset = in.skipToken() val body = toplevelTyp() atSpan(start, arrowOffset) { if (isFunction(body)) PolyFunction(tparams, body) - else { + else syntaxError("Implementation restriction: polymorphic function types must have a value parameter", arrowOffset) Ident(nme.ERROR.toTypeName) - } } - } - else { accept(TLARROW); typ() } - } - else if (in.token == INDENT) enclosed(INDENT, typ()) + else + accept(TLARROW) + typ() + else if in.token == INDENT then enclosed(INDENT, typ()) else infixType() in.token match { - case ARROW | CTXARROW => functionRest(t :: Nil) + case ARROW | CTXARROW => functionTypeRest(t :: Nil, start, imods) case MATCH => matchType(t) case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => @@ -1470,6 +1476,39 @@ object Parsers { } } + def functionTypeRest(params: List[Tree], start: Offset, mods: Modifiers): Tree = + var imods = mods + val paramSpan = Span(start, in.lastOffset) + atSpan(start, in.offset) { + if in.token == TLARROW then + if !imods.flags.isEmpty || params.isEmpty then + syntaxError(em"illegal parameter list for type lambda", start) + in.token = ARROW + else + for case ValDef(_, tpt: ByNameTypeTree, _) <- params do + syntaxError(em"parameter of type lambda may not be call-by-name", tpt.span) + in.nextToken() + return TermLambdaTypeTree(params.asInstanceOf[List[ValDef]], typ()) + + if in.token == CTXARROW then + in.nextToken() + imods |= Given + else + accept(ARROW) + val t = typ() + + if imods.isOneOf(Given | Erased) then + if imods.is(Given) && params.isEmpty then + syntaxError("context function types require at least one parameter", paramSpan) + new FunctionWithMods(params, t, imods) + else if !ctx.settings.YkindProjector.isDefault then + val (newParams :+ newT, tparams) = replaceKindProjectorPlaceholders(params :+ t): @unchecked + lambdaAbstract(tparams, Function(newParams, newT)) + else + Function(params, t) + } + end functionTypeRest + private def makeKindProjectorTypeDef(name: TypeName): TypeDef = { val isVarianceAnnotated = name.startsWith("+") || name.startsWith("-") // We remove the variance marker from the name without passing along the specified variance at all @@ -1833,7 +1872,15 @@ object Parsers { else TypeTree().withSpan(Span(in.lastOffset)) def typeDependingOn(location: Location): Tree = - if location.inParens then typ() + if location.inParens then + if sourceVersion.isAtLeast(`3.2`) then + val start = in.offset + var t = infixType() + if in.isArrow then + t = functionTypeRest(t :: Nil, start, Modifiers()) + report.error(em"function type in type ascription must be enclosed in parentheses", t.srcPos) + t + else typ() else if location.inPattern then rejectWildcardType(refinedType()) else infixType() @@ -2199,7 +2246,17 @@ 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 then + if in.token == INDENT then + blockExpr() + else + in.insertMaxIndent() + try casesOrBlock(simplify = true) + finally accept(OUTDENT) + else expr() + Function(params, body) } /** PostfixExpr ::= InfixExpr [id [nl]] @@ -2249,9 +2306,12 @@ 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 + * | (CaseClauses | Block) outdent -- silent-indent is inserted by Scanner if no real indent is found * Quoted ::= ‘'’ ‘{’ Block ‘}’ * | ‘'’ ‘[’ Type ‘]’ */ @@ -2273,7 +2333,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() @@ -2307,55 +2367,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 in.fewerBracesEnabled && in.token == COLON && followingIsLambdaAfterColon() 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 @@ -2377,9 +2420,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 `*' ')' @@ -2448,12 +2502,13 @@ object Parsers { */ def blockExpr(): Tree = atSpan(in.offset) { val simplify = in.token == INDENT - inDefScopeBraces { - if (in.token == CASE) Match(EmptyTree, caseClauses(() => caseClause())) - else block(simplify) - } + inDefScopeBraces { casesOrBlock(simplify) } } + def casesOrBlock(simplify: Boolean): Tree = + if (in.token == CASE) Match(EmptyTree, caseClauses(() => caseClause())) + else block(simplify) + /** Block ::= BlockStatSeq * @note Return tree does not have a defined span. */ @@ -3892,7 +3947,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.token == COLON && 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.token == COLON then + in.nextToken() + infixType() + else + if selfName == nme.WILDCARD then accept(COLON) + 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 @@ -3904,25 +3989,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..bb2b678c460c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -189,7 +189,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 +209,17 @@ 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 fewerBracesEnabledCtx = myLanguageImportContext fewerBracesEnabledCache @@ -610,9 +618,13 @@ object Scanners { 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""")) + if r.indentWidth == IndentWidth.Max then + i"""Enclosing expression is nested in a one line lambda expression following `:`. + |It may not spill over to a new line.""" + else + 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 @@ -653,7 +665,9 @@ object Scanners { currentRegion = Indented(nextWidth, COLONEOL, currentRegion) offset = next.offset token = INDENT - end observeIndented + + def insertMaxIndent() = + currentRegion = Indented(IndentWidth.Max, ARROW, currentRegion) /** Insert an token if next token closes an indentation region. * Exception: continue if indentation region belongs to a `match` and next token is `case`. @@ -1067,7 +1081,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 } @@ -1643,25 +1657,29 @@ object Scanners { enum IndentWidth { case Run(ch: Char, n: Int) case Conc(l: IndentWidth, r: Run) + case Max // delimits one-line lambdas following a `:` - def <= (that: IndentWidth): Boolean = this match { + def <= (that: IndentWidth): Boolean = this match case Run(ch1, n1) => - that match { + that match case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) case Conc(l, r) => this <= l - } + case Max => true case Conc(l1, r1) => that match { case Conc(l2, r2) => l1 == l2 && r1 <= r2 + case Max => true case _ => false } - } + case Max => + that == Max def < (that: IndentWidth): Boolean = this <= that && !(that <= this) def toPrefix: String = this match { case Run(ch, n) => ch.toString * n case Conc(l, r) => l.toPrefix ++ r.toPrefix + case Max => "(max >>)" } override def toString: String = { @@ -1673,6 +1691,7 @@ object Scanners { this match { case Run(ch, n) => s"$n ${kind(ch)}${if (n == 1) "" else "s"}" case Conc(l, r) => s"$l, $r" + case Max => "(max >>)" } } } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 1f414e7e912c..594312f9aaaa 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -233,7 +233,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) diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index cae2ebae7af2..6c6aac641ba2 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -254,13 +254,14 @@ 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 -Quoted ::= ‘'’ ‘{’ Block ‘}’ - | ‘'’ ‘[’ Type ‘]’ + | XmlExpr -- to be dropped +ColonArgument ::= indent CaseClauses | Block outdent + | FunParams (‘=>’ | ‘?=>’) ColonArgBody + | HkTypeParamClause ‘=>’ ColonArgBody +ColonArgBody ::= indent (CaseClauses | Block) outdent + | (CaseClauses | Block) outdent -- silent-indent is inserted by Lexer if no real indent is found ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here | Expr diff --git a/docs/_docs/reference/experimental/fewer-braces.md b/docs/_docs/reference/experimental/fewer-braces.md index a6275e73a413..d467c0018d35 100644 --- a/docs/_docs/reference/experimental/fewer-braces.md +++ b/docs/_docs/reference/experimental/fewer-braces.md @@ -12,7 +12,8 @@ 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. +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. The `:` can +optionally be followed by the parameter part of a function literal. ## Using `:` At End Of Line @@ -50,34 +51,39 @@ val firstLine = files.get(fileName).fold: ## Lambda Arguments Without Braces -Braces can also be omitted around multiple line function value arguments: +The `:` can optionally be followed by the parameter part of a function literal: ```scala -val xs = elems.map x => +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. +Braces can be omitted if the lambda starts with a parameter list and an arrow symbol `=>` or `?=>`. The arrow is followed by the body of the functional literal, which can be +either on the same line or as an indented block on the following lines. Example: +```scala +val xs = elems + .map: x => x * x + .foldLeft (x, y) => x = y +``` ## Syntax Changes +As a lexical change, a `:` at the end of a line is now always treated as a +"colon at end of line" token. + +The context free grammar changes as follows: ``` SimpleExpr ::= ... - | SimpleExpr `:` IndentedArgument - | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument -InfixExpr ::= ... - | InfixExpr id `:` IndentedArgument -IndentedArgument ::= indent (CaseClauses | Block) outdent -``` + | SimpleExpr ‘:’ ColonArgument -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 + | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument +ColonArgument ::= indent CaseClauses | Block outdent + | FunParams (‘=>’ | ‘?=>’) ColonArgBody + | HkTypeParamClause ‘=>’ ColonArgBody +ColonArgBody ::= indent (CaseClauses | Block) outdent + | (CaseClauses | Block) outdent -- ``` +The last line is understood as follows: If the token following a `=>` or `?=>` in a +`ColonArgument` is not an `indent`, then the parser inserts a silent indent token +and assumes the associated indentation region has maximal indentation width. 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..9e359b5c803c 100644 --- a/tests/neg/closure-args.scala +++ b/tests/neg/closure-args.scala @@ -1,9 +1,14 @@ import language.experimental.fewerBraces -val x = List().map (x: => Int) => // error +val x = List().map: (x: => Int) => // error ??? -val y = List() map x => // error +val y = List() map: x => // error x + 1 // error -val z = List() map + => // error +val z = List().map: + => // ok ??? +val xs = List(1) +val d = xs + .map: x => x.toString + xs.dropWhile: + y => y > 0 // 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/fun-ascription.check b/tests/neg/fun-ascription.check new file mode 100644 index 000000000000..e1277d0b1a3a --- /dev/null +++ b/tests/neg/fun-ascription.check @@ -0,0 +1,9 @@ +-- Error: tests/neg/fun-ascription.scala:2:17 -------------------------------------------------------------------------- +2 |val x1 = (f: Int => Int) // error + | ^^^^^^^^^^ + | function type in type ascription must be enclosed in parentheses +-- Error: tests/neg/fun-ascription.scala:3:16 -------------------------------------------------------------------------- +3 |val x2 = f: Int => Int // error + | ^ + | parentheses are required around the parameter of a lambda + | This construct can be rewritten automatically under -rewrite -source 3.0-migration. diff --git a/tests/neg/fun-ascription.scala b/tests/neg/fun-ascription.scala new file mode 100644 index 000000000000..62a1a1398304 --- /dev/null +++ b/tests/neg/fun-ascription.scala @@ -0,0 +1,3 @@ +def f[T](x: T): T = x +val x1 = (f: Int => Int) // error +val x2 = f: Int => Int // error 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/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..2ae4ad89d828 100644 --- a/tests/pos/closure-args.scala +++ b/tests/pos/closure-args.scala @@ -1,16 +1,38 @@ 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 => - y > 0 - (0) +object Test1: + 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 a: Int = xs + .map: x => + x * x + .filter: (y: Int) => + y > 0 + (0) + val b: Int = xs + .map: x => x * x + .filter: y => y > 0 + (0) + val c = List(xs.map: y => y + y) + val d: String = xs + .map: x => x.toString + xs.dropWhile: y => y > 0 + .filter: z => !z.isEmpty + (0) + val e = xs.map: + case 1 => 2 + case 2 => 3 + case x => x + .filter: x => x > 0 + val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 + + extension (xs: List[Int]) def foo(f: [X] => X => X) = () + + val p = xs.foo: [X] => (x: X) => x + + val q = (x: String => String) => x diff --git a/tests/pos/i7851.scala b/tests/pos/i7851.scala index 7be04c45a257..acd89dbdbc8e 100644 --- a/tests/pos/i7851.scala +++ b/tests/pos/i7851.scala @@ -30,7 +30,7 @@ object WrapperTest { val test2: (Wrapped[Float], Wrapped[Float], Wrapped[Float]) => Wrapped[Float] = { (x, y, z) => x } def main(args: Array[String]): Unit = { - wrappedFunction(test1: (Wrapped[Float], Wrapped[Float], Wrapped[Float]) => Wrapped[Float])(5f, 11f, 3f) + wrappedFunction(test1: ((Wrapped[Float], Wrapped[Float], Wrapped[Float]) => Wrapped[Float]))(5f, 11f, 3f) wrappedFunction(test1)(5f, 11f, 3f) wrappedFunction(test2)(5f, 11f, 3f) } 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/pos/t4176b.scala b/tests/pos/t4176b.scala index eebb5dda583d..b524d18192a1 100644 --- a/tests/pos/t4176b.scala +++ b/tests/pos/t4176b.scala @@ -1,5 +1,5 @@ object Test { def foo(a1: String*) = a1 // val fooEta = foo _ - (foo: Seq[String] => Seq[String]) + foo: (Seq[String] => Seq[String]) } diff --git a/tests/pos/t8120.scala b/tests/pos/t8120.scala index e06f38d5db11..6507ab61ae62 100644 --- a/tests/pos/t8120.scala +++ b/tests/pos/t8120.scala @@ -5,5 +5,5 @@ object A { implicit class RichAny(a: Any) { def m(a: Any): Int = 0 } - (new C).m({ case (x, y) => x } : Any => Any) + (new C).m({ case (x, y) => x } : (Any => Any)) } diff --git a/tests/rewrites/rewrites.scala b/tests/rewrites/rewrites.scala index 6e2e4c2c8d9f..f38269bd0ed6 100644 --- a/tests/rewrites/rewrites.scala +++ b/tests/rewrites/rewrites.scala @@ -56,7 +56,7 @@ object test3 { } } } - def g = { x: Int => + def g = { (x: Int) => x + 1 } } diff --git a/tests/run-macros/quote-matching-optimize-4/Macro_1.scala b/tests/run-macros/quote-matching-optimize-4/Macro_1.scala index 01e1562b0770..391bd8cedc19 100644 --- a/tests/run-macros/quote-matching-optimize-4/Macro_1.scala +++ b/tests/run-macros/quote-matching-optimize-4/Macro_1.scala @@ -22,7 +22,7 @@ object Macro { case '{ ($ls: List[T]).filter($f).filter($g) } => optimize('{ $ls.filter(x => $f(x) && $g(x)) }) - case '{ type u; type v; ($ls: List[`u`]).map($f: `u` => `v`).map($g: `v` => T) } => + case '{ type u; type v; ($ls: List[`u`]).map($f: (`u` => `v`)).map($g: (`v` => T)) } => optimize('{ $ls.map(x => $g($f(x))) }) case _ => x diff --git a/tests/run/lambda-unit.scala b/tests/run/lambda-unit.scala index 073f4ae1e3e2..d949042e541f 100644 --- a/tests/run/lambda-unit.scala +++ b/tests/run/lambda-unit.scala @@ -13,7 +13,7 @@ object Test { def main(args: Array[String]): Unit = { fun("") - (fun: Object => Any)("") + (fun: (Object => Any))("") sam.foo("") genericSam.foo("") }