Skip to content

Commit b44f1a2

Browse files
committed
Patch empty implicit parens on error recovery
1 parent a577116 commit b44f1a2

File tree

8 files changed

+111
-35
lines changed

8 files changed

+111
-35
lines changed

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import printing.Highlighting.*
1414
import printing.Formatting
1515
import ErrorMessageID.*
1616
import ast.Trees
17-
import config.{Feature, ScalaVersion}
17+
import config.{Feature, MigrationVersion, ScalaVersion}
1818
import transform.patmat.Space
1919
import transform.patmat.SpaceEngine
2020
import typer.ErrorReporting.{err, matchReductionAddendum, substitutableTypeSymbolsInScope}
@@ -1592,6 +1592,14 @@ class MissingArgument(pname: Name, methString: String)(using Context)
15921592
else s"missing argument for parameter $pname of $methString"
15931593
def explain(using Context) = ""
15941594

1595+
class MissingImplicitParameterInEmptyArguments(pname: Name, methString: String)(using Context)
1596+
extends MissingArgument(pname, methString):
1597+
override def msg(using Context) =
1598+
val mv = MigrationVersion.ImplicitParamsWithoutUsing
1599+
super.msg.concat(Message.rewriteNotice("This code", mv.patchFrom)) // patch emitted up the stack
1600+
override def explain(using Context) =
1601+
"Old-style implicit argument lists may be omitted but not empty; this syntax was corrected in 3.7."
1602+
15951603
class MissingArgumentList(method: String, sym: Symbol)(using Context)
15961604
extends TypeMsg(MissingArgumentListID) {
15971605
def msg(using Context) =

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import ProtoTypes.*
2323
import Inferencing.*
2424
import reporting.*
2525
import Nullables.*, NullOpsDecorator.*
26-
import config.{Feature, SourceVersion}
26+
import config.{Feature, MigrationVersion, SourceVersion}
2727

2828
import collection.mutable
2929
import config.Printers.{overload, typr, unapp}
@@ -607,7 +607,7 @@ trait Applications extends Compatibility {
607607
fail(TypeMismatch(methType.resultType, resultType, None))
608608

609609
// match all arguments with corresponding formal parameters
610-
if success then matchArgs(orderedArgs, methType.paramInfos, 0)
610+
if success then matchArgs(orderedArgs, methType.paramInfos, n=0)
611611
case _ =>
612612
if (methType.isError) ok = false
613613
else fail(em"$methString does not take parameters")
@@ -765,13 +765,20 @@ trait Applications extends Compatibility {
765765
}
766766
else defaultArgument(normalizedFun, n, testOnly)
767767

768+
// a bug allowed empty parens to expand to implicit args, offer rewrite only on migration
769+
def canSupplyImplicits = methodType.isImplicitMethod
770+
&& (applyKind == ApplyKind.Using || {
771+
if args1.isEmpty then
772+
fail(MissingImplicitParameterInEmptyArguments(methodType.paramNames(n), methString))
773+
true
774+
})
775+
&& ctx.mode.is(Mode.ImplicitsEnabled)
776+
768777
if !defaultArg.isEmpty then
769778
defaultArg.tpe.widen match
770779
case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1)
771780
case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
772-
else if (methodType.isContextualMethod || applyKind == ApplyKind.Using && methodType.isImplicitMethod)
773-
&& ctx.mode.is(Mode.ImplicitsEnabled)
774-
then
781+
else if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) || canSupplyImplicits then
775782
val implicitArg = implicitArgTree(formal, appPos.span)
776783
matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1)
777784
else
@@ -1149,32 +1156,53 @@ trait Applications extends Compatibility {
11491156
}
11501157
}
11511158

1159+
def tryWithUsing(fun1: Tree, proto: FunProto)(using Context): Option[Tree] =
1160+
tryEither(Option(simpleApply(fun1, proto.withApplyKind(ApplyKind.Using)))): (_, _) =>
1161+
None
1162+
11521163
/** If the applied function is an automatically inserted `apply`
1153-
* method and one of its arguments has a type mismatch , append
1154-
* a note to the error message that explains where the required
1155-
* type comes from. See #19680 and associated test case.
1164+
* method and one of its arguments has a type mismatch , append
1165+
* a note to the error message that explains where the required
1166+
* type comes from. See #19680 and associated test case.
11561167
*/
11571168
def maybeAddInsertedApplyNote(failedState: TyperState, fun1: Tree)(using Context): Unit =
11581169
if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then
11591170
fun1 match
1160-
case Select(qualifier, _) =>
1161-
def mapMessage(dia: Diagnostic): Diagnostic =
1162-
dia match
1163-
case dia: Diagnostic.Error =>
1164-
dia.msg match
1165-
case msg: TypeMismatch =>
1166-
msg.inTree match
1167-
case Some(arg) if tree.args.exists(_.span == arg.span) =>
1168-
val noteText =
1169-
i"""The required type comes from a parameter of the automatically
1170-
|inserted `apply` method of `${qualifier.tpe}`.""".stripMargin
1171-
Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos)
1172-
case _ => dia
1173-
case msg => dia
1174-
case dia => dia
1175-
failedState.reporter.mapBufferedMessages(mapMessage)
1176-
case _ => ()
1177-
else ()
1171+
case Select(qualifier, _) =>
1172+
def mapMessage(dia: Diagnostic): Diagnostic =
1173+
dia match
1174+
case dia: Diagnostic.Error =>
1175+
dia.msg match
1176+
case msg: TypeMismatch =>
1177+
msg.inTree match
1178+
case Some(arg) if tree.args.exists(_.span == arg.span) =>
1179+
val noteText =
1180+
i"""The required type comes from a parameter of the automatically
1181+
|inserted `apply` method of `${qualifier.tpe}`.""".stripMargin
1182+
Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos)
1183+
case _ => dia
1184+
case msg => dia
1185+
case dia => dia
1186+
failedState.reporter.mapBufferedMessages(mapMessage)
1187+
case _ => ()
1188+
1189+
def maybePatchBadParensForImplicit(failedState: TyperState)(using Context): Boolean =
1190+
var retry = false
1191+
failedState.reporter.mapBufferedMessages: dia =>
1192+
dia match
1193+
case err: Diagnostic.Error =>
1194+
err.msg match
1195+
case msg: MissingImplicitParameterInEmptyArguments =>
1196+
val mv = MigrationVersion.ImplicitParamsWithoutUsing
1197+
if mv.needsPatch then
1198+
retry = true
1199+
rewrites.Rewrites.patch(tree.span.withStart(tree.span.point), "") // f() -> f
1200+
Diagnostic.Warning(err.msg, err.pos)
1201+
else
1202+
err
1203+
case _ => err
1204+
case dia => dia
1205+
retry
11781206

11791207
val result = fun1.tpe match {
11801208
case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err)
@@ -1231,10 +1259,13 @@ trait Applications extends Compatibility {
12311259
errorTree(tree, em"argument to summonFrom must be a pattern matching closure")
12321260
}
12331261
else
1234-
tryEither {
1235-
simpleApply(fun1, proto)
1236-
} {
1237-
(failedVal, failedState) =>
1262+
tryEither(simpleApply(fun1, proto)): (failedVal, failedState) =>
1263+
// a bug allowed empty parens to expand to implicit args, offer rewrite only on migration, then retry
1264+
if proto.args.isEmpty && maybePatchBadParensForImplicit(failedState) then
1265+
tryWithUsing(fun1, proto).getOrElse:
1266+
failedState.commit()
1267+
failedVal
1268+
else
12381269
def fail =
12391270
maybeAddInsertedApplyNote(failedState, fun1)
12401271
failedState.commit()
@@ -1244,10 +1275,9 @@ trait Applications extends Compatibility {
12441275
// The reason we need to try both is that the decision whether to use tupled
12451276
// or not was already taken but might have to be revised when an implicit
12461277
// is inserted on the qualifier.
1247-
tryWithImplicitOnQualifier(fun1, originalProto).getOrElse(
1278+
tryWithImplicitOnQualifier(fun1, originalProto).getOrElse:
12481279
if (proto eq originalProto) fail
1249-
else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail))
1250-
}
1280+
else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)
12511281
}
12521282

12531283
if result.tpe.isNothingType then

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,9 @@ object ProtoTypes {
623623
override def withContext(newCtx: Context): ProtoType =
624624
if newCtx `eq` protoCtx then this
625625
else new FunProto(args, resType)(typer, applyKind, state)(using newCtx)
626+
627+
def withApplyKind(applyKind: ApplyKind) =
628+
new FunProto(args, resType)(typer, applyKind, state)
626629
}
627630

628631
/** A prototype for expressions that appear in function position

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ class CompilationTests {
8686
compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")),
8787
compileFile("tests/rewrites/i22731.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")),
8888
compileFile("tests/rewrites/i22731b.scala", defaultOptions.and("-rewrite", "-source:3.7-migration")),
89-
compileFile("tests/rewrites/implicit-to-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given"))
89+
compileFile("tests/rewrites/implicit-to-given.scala", defaultOptions.and("-rewrite", "-Yimplicit-to-given")),
90+
compileFile("tests/rewrites/i22792.scala", defaultOptions.and("-rewrite")),
9091
).checkRewrites()
9192
}
9293

tests/neg/i22792.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- [E171] Type Error: tests/neg/i22792.scala:8:30 ----------------------------------------------------------------------
2+
8 |@main def Test = new Foo().run() // error
3+
| ^^^^^^^^^^^^^^^
4+
| missing argument for parameter ev of method run in class Foo: (implicit ev: Permit): Unit
5+
| This code can be rewritten automatically under -rewrite -source 3.7-migration.
6+
|---------------------------------------------------------------------------------------------------------------------
7+
| Explanation (enabled by `-explain`)
8+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
9+
| Old-style implicit argument lists may be omitted but not empty; this syntax was corrected in 3.7.
10+
---------------------------------------------------------------------------------------------------------------------

tests/neg/i22792.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//> using options -explain
2+
3+
trait Permit
4+
class Foo:
5+
def run(implicit ev: Permit): Unit = ???
6+
7+
given Permit = ???
8+
@main def Test = new Foo().run() // error

tests/rewrites/i22792.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//> using options -source 3.7-migration
2+
3+
trait Permit
4+
class Foo:
5+
def run(implicit ev: Permit): Unit = ???
6+
7+
given Permit = ???
8+
@main def Test = new Foo().run

tests/rewrites/i22792.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//> using options -source 3.7-migration
2+
3+
trait Permit
4+
class Foo:
5+
def run(implicit ev: Permit): Unit = ???
6+
7+
given Permit = ???
8+
@main def Test = new Foo().run()

0 commit comments

Comments
 (0)