Skip to content

Fix insertion of using in applications with trailing lambda syntax #22927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand All @@ -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] */
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
32 changes: 26 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(_, _))) =>
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
}

Expand All @@ -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
}
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Migrations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 14 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
*
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i22440.check
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion tests/rewrites/i22440.check
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

def foo(implicit x: Int) = ()
val _ = foo(using 1)
val _ = foo (using 1)
val _ = foo(using 1)
11 changes: 11 additions & 0 deletions tests/rewrites/i22927.check
Original file line number Diff line number Diff line change
@@ -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("")
})
13 changes: 13 additions & 0 deletions tests/rewrites/i22927.scala
Original file line number Diff line number Diff line change
@@ -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("")
}
}
51 changes: 51 additions & 0 deletions tests/rewrites/i22927b.check
Original file line number Diff line number Diff line change
@@ -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)

Loading
Loading