diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fdefc14aadd6..9262bc116846 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -513,6 +513,21 @@ object Trees { case Using // r.f(using x) case InfixTuple // r f (x1, ..., xN) where N != 1; needs to be treated specially for an error message in typedApply + /** The syntax used to pass the arguments in an application. */ + enum ApplyStyle: + /** The arguments are passed in parentheses, e.g. `f(x)`. */ + case Parentheses + /** A single argument is passed as a trailing lambda with braces, e.g. + * `f { x => ... }`. + */ + case TrailingBraces + /** A single argument is passed as a trailing lambda with colon and + * indentation, e.g. `f: x => ...`. + */ + case TrailingColon + /** The syntax used to pass the arguments is unknown. */ + case Unknown + /** fun(args) */ case class Apply[+T <: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends GenericApply[T] { @@ -527,6 +542,13 @@ object Trees { */ def applyKind: ApplyKind = attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular) + + def setApplyStyle(style: ApplyStyle) = + putAttachment(untpd.StyleOfApply, style) + this + + def applyStyle: ApplyStyle = + attachmentOrElse(untpd.StyleOfApply, ApplyStyle.Unknown) } /** fun[args] */ diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index ae3ed9fcad3b..35f4a2a496a3 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1662,5 +1662,5 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { protected def FunProto(args: List[Tree], resType: Type)(using Context) = - ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, ApplyKind.Regular) + ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, ApplyKind.Regular, ApplyStyle.Unknown) } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 145c61584fcc..eb9566fdfe87 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -386,6 +386,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { /** Property key for contextual Apply trees of the form `fn given arg` */ val KindOfApply: Property.StickyKey[ApplyKind] = Property.StickyKey() + /** Property key to attach an [[ApplyStyle]] to [[Apply]] trees. */ + val StyleOfApply: Property.StickyKey[ApplyStyle] = Property.StickyKey() + // ------ Creation methods for untyped only ----------------- def Ident(name: Name)(implicit src: SourceFile): Ident = new Ident(name) @@ -859,5 +862,5 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } protected def FunProto(args: List[Tree], resType: Type)(using Context) = - ProtoTypes.FunProto(args, resType)(ctx.typer, ApplyKind.Regular) + ProtoTypes.FunProto(args, resType)(ctx.typer, ApplyKind.Regular, ApplyStyle.Unknown) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3a42b8a682f1..c7bbe199022c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2805,7 +2805,8 @@ object Parsers { val tapp = atSpan(startOffset(t), in.offset) { TypeApply(t, typeArgs(namedOK = true, wildOK = false)) } simpleExprRest(tapp, location, canApply = true) case LPAREN | LBRACE | INDENT if canApply => - val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) } + val beforeArgs = in.token + val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs(), beforeArgs) } if in.rewriteToIndent then app match case Apply(Apply(_, List(Block(_, _))), List(blk @ Block(_, _))) => @@ -2827,8 +2828,9 @@ object Parsers { } case _ => t else if isColonLambda then + val beforeArgs = in.token val app = atSpan(startOffset(t), in.skipToken()) { - Apply(t, expr(Location.InColonArg) :: Nil) + mkApply(t, (expr(Location.InColonArg) :: Nil, false), beforeArgs) } simpleExprRest(app, location, canApply = true) else t @@ -2892,8 +2894,22 @@ object Parsers { def argumentExprs(): (List[Tree], Boolean) = if (in.isNestedStart) (blockExpr() :: Nil, false) else parArgumentExprs() - def mkApply(fn: Tree, args: (List[Tree], Boolean)): Tree = + /** Creates an [[Apply]] tree. + * + * @param fn The function to apply. + * @param args A pair containing the list of arguments and a boolean + * indicating if the list is preceded by `using`. + * @param beforeArgs The token that precedes the arguments. + */ + def mkApply(fn: Tree, args: (List[Tree], Boolean), beforeArgs: Token): Tree = val res = Apply(fn, args._1) + val applyStyle = + beforeArgs match + case LPAREN => ApplyStyle.Parentheses + case LBRACE => ApplyStyle.TrailingBraces + case INDENT | COLONfollow | COLONeol => ApplyStyle.TrailingColon + case _ => ApplyStyle.Unknown + res.setApplyStyle(applyStyle) if args._2 then res.setApplyKind(ApplyKind.Using) res @@ -2905,7 +2921,9 @@ object Parsers { */ def argumentExprss(fn: Tree): Tree = { argumentStart() - if (in.token == LPAREN || in.isNestedStart) argumentExprss(mkApply(fn, argumentExprs())) + if in.token == LPAREN || in.isNestedStart then + val beforeArgs = in.token + argumentExprss(mkApply(fn, argumentExprs(), beforeArgs)) else fn } @@ -2930,8 +2948,9 @@ object Parsers { } } if (in.token == LPAREN && (!inClassConstrAnnots || isLegalAnnotArg)) + val beforeArgs = in.token parArgumentExprss( - atSpan(startOffset(fn)) { mkApply(fn, parArgumentExprs()) } + atSpan(startOffset(fn)) { mkApply(fn, parArgumentExprs(), beforeArgs) } ) else fn } @@ -4046,7 +4065,8 @@ object Parsers { def selfInvocation(): Tree = atSpan(accept(THIS)) { argumentStart() - argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs())) + val beforeArgs = in.token + argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs(), beforeArgs)) } /** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type] diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 71fc250d0710..d12b003a6b40 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1085,7 +1085,7 @@ trait Applications extends Compatibility { // Do ignore other expected result types, since there might be an implicit conversion // on the result. We could drop this if we disallow unrestricted implicit conversions. val originalProto = - new FunProto(tree.args, resultProto)(this, tree.applyKind)(using argCtx(tree)) + new FunProto(tree.args, resultProto)(this, tree.applyKind, tree.applyStyle)(using argCtx(tree)) record("typedApply") val fun1 = typedExpr(tree.fun, originalProto) diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index 0e6dc27ecf7f..a983a3b3e147 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -133,12 +133,18 @@ trait Migrations: val rewriteMsg = Message.rewriteNotice("This code", mversion.patchFrom) report.errorOrMigrationWarning( em"""Implicit parameters should be provided with a `using` clause.$rewriteMsg - |To disable the warning, please use the following option: + |To disable the warning, please use the following option: | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" - |""", + |""", pt.args.head.srcPos, mversion) if mversion.needsPatch then - patch(Span(pt.args.head.span.start), "using ") + // In order to insert a `using`, the application needs to be done with + // parentheses syntax. See issue #22927 and related tests. + patch(Span(tree.span.end, pt.args.head.span.start), "(using ") + if pt.applyStyle != ApplyStyle.Parentheses then + // If the application wasn't done with the parentheses syntax, we need + // to add a trailing closing parenthesis. + patch(Span(pt.args.head.span.end), ")") end implicitParams end Migrations diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 379bdbc8d6a0..f3eec92c6d3b 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -344,6 +344,7 @@ object ProtoTypes { trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto trait FunOrPolyProto extends ProtoType: // common trait of PolyProto and FunProto def applyKind: ApplyKind = ApplyKind.Regular + def applyStyle: ApplyStyle = ApplyStyle.Unknown class FunProtoState { @@ -368,9 +369,10 @@ object ProtoTypes { * [](args): resultType * * @param args The untyped arguments to which the function is applied - * @param resType The expeected result type + * @param resType The expected result type * @param typer The typer to use for typing the arguments * @param applyKind The kind of application (regular/using/tupled infix operand) + * @param applyStyle The [[ApplyStyle]] of the application * @param state The state object to use for tracking the changes to this prototype * @param constrainResultDeep * A flag to indicate that constrainResult on this prototype @@ -379,6 +381,7 @@ object ProtoTypes { case class FunProto(args: List[untpd.Tree], resType: Type)( typer: Typer, override val applyKind: ApplyKind, + override val applyStyle: ApplyStyle, state: FunProtoState = new FunProtoState, val constrainResultDeep: Boolean = false)(using protoCtx: Context) extends UncachedGroundType with ApplyingProto with FunOrPolyProto { @@ -402,7 +405,7 @@ object ProtoTypes { && (typer eq this.typer) && constrainResultDeep == this.constrainResultDeep then this - else new FunProto(args, resultType)(typer, applyKind, constrainResultDeep = constrainResultDeep) + else new FunProto(args, resultType)(typer, applyKind, applyStyle, constrainResultDeep = constrainResultDeep) /** @return True if all arguments have types. */ @@ -572,7 +575,7 @@ object ProtoTypes { val dualArgs = args match case untpd.Tuple(elems) :: Nil => elems case _ => untpd.Tuple(args) :: Nil - state.tupledDual = new FunProto(dualArgs, resultType)(typer, applyKind) + state.tupledDual = new FunProto(dualArgs, resultType)(typer, applyKind, applyStyle) tupledDual } @@ -614,15 +617,15 @@ object ProtoTypes { override def withContext(newCtx: Context): ProtoType = if newCtx `eq` protoCtx then this - else new FunProto(args, resType)(typer, applyKind, state)(using newCtx) + else new FunProto(args, resType)(typer, applyKind, applyStyle, state)(using newCtx) } /** A prototype for expressions that appear in function position * * [](args): resultType, where args are known to be typed */ - class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, applyKind: ApplyKind)(using Context) - extends FunProto(args, resultType)(typer, applyKind): + class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, applyKind: ApplyKind, applyStyle: ApplyStyle)(using Context) + extends FunProto(args, resultType)(typer, applyKind, applyStyle): override def typedArgs(norm: (untpd.Tree, Int) => untpd.Tree)(using Context): List[tpd.Tree] = args override def typedArg(arg: untpd.Tree, formal: Type)(using Context): tpd.Tree = arg.asInstanceOf[tpd.Tree] override def allArgTypesAreCurrent()(using Context): Boolean = true @@ -684,7 +687,10 @@ object ProtoTypes { } class UnapplyFunProto(argType: Type, typer: Typer)(using Context) extends FunProto( - untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source)) :: Nil, WildcardType)(typer, applyKind = ApplyKind.Regular) + untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source)) :: Nil, WildcardType + )( + typer, applyKind = ApplyKind.Regular, applyStyle = ApplyStyle.Parentheses + ) /** A prototype for expressions [] that are type-parameterized: * @@ -1006,7 +1012,7 @@ object ProtoTypes { if (args eq tp.args) && (resTp eq tp.resultType) then tp else - FunProtoTyped(args, resTp)(ctx.typer, tp.applyKind) + FunProtoTyped(args, resTp)(ctx.typer, tp.applyKind, tp.applyStyle) case tp: IgnoredProto => WildcardType case _: ThisType | _: BoundType => // default case, inlined for speed diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6b7b840e7606..91e01fc35879 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1861,7 +1861,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val nestedCtx = outerCtx.fresh.setNewTyperState() inContext(nestedCtx) { val protoArgs = args map (_ withType WildcardType) - val callProto = FunProto(protoArgs, WildcardType)(this, app.applyKind) + val callProto = FunProto(protoArgs, WildcardType)(this, app.applyKind, app.applyStyle) val expr1 = typedExpr(expr, callProto) if nestedCtx.reporter.hasErrors then NoType else inContext(outerCtx) { diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 47ed2aa6564d..15f246a67600 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -88,7 +88,9 @@ class CompilationTests { compileFile("tests/rewrites/ambiguous-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21382.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/unused.scala", defaultOptions.and("-rewrite", "-Wunused:all")), - compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")) + compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")), + compileFile("tests/rewrites/i22927.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")), + compileFile("tests/rewrites/i22927b.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")) ).checkRewrites() } diff --git a/tests/neg/i22440.check b/tests/neg/i22440.check index 699d70f343c3..f5df4916db87 100644 --- a/tests/neg/i22440.check +++ b/tests/neg/i22440.check @@ -3,5 +3,5 @@ | ^ | Implicit parameters should be provided with a `using` clause. | This code can be rewritten automatically under -rewrite -source 3.7-migration. - | To disable the warning, please use the following option: + | To disable the warning, please use the following option: | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" diff --git a/tests/rewrites/i22440.check b/tests/rewrites/i22440.check index 417fd442f9f2..2776c9de26e6 100644 --- a/tests/rewrites/i22440.check +++ b/tests/rewrites/i22440.check @@ -2,4 +2,4 @@ def foo(implicit x: Int) = () val _ = foo(using 1) -val _ = foo (using 1) +val _ = foo(using 1) diff --git a/tests/rewrites/i22927.check b/tests/rewrites/i22927.check new file mode 100644 index 000000000000..e3be2c4a6b7c --- /dev/null +++ b/tests/rewrites/i22927.check @@ -0,0 +1,11 @@ +class Input(x: String) + +trait Decoder[T] + +object Decoder: + inline def apply[T](implicit f: () => Unit): Decoder[T] = ??? + +object Input: + given Decoder[Input] = Decoder(using { () => + Input("") + }) diff --git a/tests/rewrites/i22927.scala b/tests/rewrites/i22927.scala new file mode 100644 index 000000000000..fbc23404bb8e --- /dev/null +++ b/tests/rewrites/i22927.scala @@ -0,0 +1,13 @@ +class Input(x: String) + +trait Decoder[T] + +object Decoder { + inline def apply[T](implicit f: () => Unit): Decoder[T] = ??? +} + +object Input { + given Decoder[Input] = Decoder { () => + Input("") + } +} diff --git a/tests/rewrites/i22927b.check b/tests/rewrites/i22927b.check new file mode 100644 index 000000000000..439d6b7b9262 --- /dev/null +++ b/tests/rewrites/i22927b.check @@ -0,0 +1,51 @@ + +def foo(implicit f: () => Unit): Unit = ??? +def bar(a: Int)(implicit f: () => Unit): Unit = ??? + +@main def main = + // Trailing lambdas with braces: + foo(using { () => 43 }) + foo(using { () => val x = 42; 43 }) + foo(using { () => val x = 42; 43 }) + foo(using {() => val x = 42; 43}) + bar(1)(using { () => + val x = 42 + 43 }) + + // Parentheses: + foo(using () => 43 ) + foo(using () => + val x = 42 + 43 + ) + foo(using () => + val x = 42 + 43 + ) + foo(using () => + val x = 42 + 43 + ) + bar(1)(using () => + val x = 42 + 43 ) + + // Trailing lambdas with column and indentation: + foo(using () => + 43) + foo(using () => + val x = 42 + 43) + foo(using () => + val x = 42 + 43) + foo(using () => + val x = 42 + 43) + foo(using () => + val x = 42 + 43) + bar(1)(using () => + val x = 42 + 43) + diff --git a/tests/rewrites/i22927b.scala b/tests/rewrites/i22927b.scala new file mode 100644 index 000000000000..5fbde5c30dc5 --- /dev/null +++ b/tests/rewrites/i22927b.scala @@ -0,0 +1,54 @@ + +def foo(implicit f: () => Unit): Unit = ??? +def bar(a: Int)(implicit f: () => Unit): Unit = ??? + +@main def main = + // Trailing lambdas with braces: + foo { () => 43 } + foo { () => val x = 42; 43 } + foo{ () => val x = 42; 43 } + foo {() => val x = 42; 43} + bar(1) { () => + val x = 42 + 43 } + + // Parentheses: + foo ( () => 43 ) + foo ( () => + val x = 42 + 43 + ) + foo( () => + val x = 42 + 43 + ) + foo (() => + val x = 42 + 43 + ) + bar(1) ( () => + val x = 42 + 43 ) + + // Trailing lambdas with column and indentation: + foo: () => + 43 + foo : () => + val x = 42 + 43 + foo :() => + val x = 42 + 43 + foo + : () => + val x = 42 + 43 + foo + : + () => + val x = 42 + 43 + bar(1) : () => + val x = 42 + 43 + diff --git a/tests/warn/i22440.check b/tests/warn/i22440.check index eaa357661a59..018a31e57044 100644 --- a/tests/warn/i22440.check +++ b/tests/warn/i22440.check @@ -3,5 +3,5 @@ | ^ | Implicit parameters should be provided with a `using` clause. | This code can be rewritten automatically under -rewrite -source 3.7-migration. - | To disable the warning, please use the following option: + | To disable the warning, please use the following option: | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s"