Skip to content

Commit 6fe89ef

Browse files
committed
Patch empty implicit parens on error recovery
1 parent 744ba92 commit 6fe89ef

File tree

8 files changed

+111
-35
lines changed

8 files changed

+111
-35
lines changed

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

+9-1
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}
@@ -1593,6 +1593,14 @@ class MissingArgument(pname: Name, methString: String)(using Context)
15931593
else s"missing argument for parameter $pname of $methString"
15941594
def explain(using Context) = ""
15951595

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

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

+63-33
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}
@@ -602,7 +602,7 @@ trait Applications extends Compatibility {
602602
fail(TypeMismatch(methType.resultType, resultType, None))
603603

604604
// match all arguments with corresponding formal parameters
605-
if success then matchArgs(orderedArgs, methType.paramInfos, 0)
605+
if success then matchArgs(orderedArgs, methType.paramInfos, n=0)
606606
case _ =>
607607
if (methType.isError) ok = false
608608
else fail(em"$methString does not take parameters")
@@ -757,13 +757,20 @@ trait Applications extends Compatibility {
757757
}
758758
else defaultArgument(normalizedFun, n, testOnly)
759759

760+
// a bug allowed empty parens to expand to implicit args, offer rewrite only on migration
761+
def canSupplyImplicits = methodType.isImplicitMethod
762+
&& (applyKind == ApplyKind.Using || {
763+
if args1.isEmpty then
764+
fail(MissingImplicitParameterInEmptyArguments(methodType.paramNames(n), methString))
765+
true
766+
})
767+
&& ctx.mode.is(Mode.ImplicitsEnabled)
768+
760769
if !defaultArg.isEmpty then
761770
defaultArg.tpe.widen match
762771
case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1)
763772
case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
764-
else if (methodType.isContextualMethod || applyKind == ApplyKind.Using && methodType.isImplicitMethod)
765-
&& ctx.mode.is(Mode.ImplicitsEnabled)
766-
then
773+
else if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) || canSupplyImplicits then
767774
val implicitArg = implicitArgTree(formal, appPos.span)
768775
matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1)
769776
else
@@ -1141,32 +1148,53 @@ trait Applications extends Compatibility {
11411148
}
11421149
}
11431150

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

11711199
val result = fun1.tpe match {
11721200
case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err)
@@ -1223,10 +1251,13 @@ trait Applications extends Compatibility {
12231251
errorTree(tree, em"argument to summonFrom must be a pattern matching closure")
12241252
}
12251253
else
1226-
tryEither {
1227-
simpleApply(fun1, proto)
1228-
} {
1229-
(failedVal, failedState) =>
1254+
tryEither(simpleApply(fun1, proto)): (failedVal, failedState) =>
1255+
// a bug allowed empty parens to expand to implicit args, offer rewrite only on migration, then retry
1256+
if proto.args.isEmpty && maybePatchBadParensForImplicit(failedState) then
1257+
tryWithUsing(fun1, proto).getOrElse:
1258+
failedState.commit()
1259+
failedVal
1260+
else
12301261
def fail =
12311262
maybeAddInsertedApplyNote(failedState, fun1)
12321263
failedState.commit()
@@ -1236,10 +1267,9 @@ trait Applications extends Compatibility {
12361267
// The reason we need to try both is that the decision whether to use tupled
12371268
// or not was already taken but might have to be revised when an implicit
12381269
// is inserted on the qualifier.
1239-
tryWithImplicitOnQualifier(fun1, originalProto).getOrElse(
1270+
tryWithImplicitOnQualifier(fun1, originalProto).getOrElse:
12401271
if (proto eq originalProto) fail
1241-
else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail))
1242-
}
1272+
else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)
12431273
}
12441274

12451275
if result.tpe.isNothingType then

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

+3
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,9 @@ object ProtoTypes {
615615
override def withContext(newCtx: Context): ProtoType =
616616
if newCtx `eq` protoCtx then this
617617
else new FunProto(args, resType)(typer, applyKind, state)(using newCtx)
618+
619+
def withApplyKind(applyKind: ApplyKind) =
620+
new FunProto(args, resType)(typer, applyKind, state)
618621
}
619622

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

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ class CompilationTests {
8888
compileFile("tests/rewrites/ambiguous-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
8989
compileFile("tests/rewrites/i21382.scala", defaultOptions.and("-indent", "-rewrite")),
9090
compileFile("tests/rewrites/unused.scala", defaultOptions.and("-rewrite", "-Wunused:all")),
91-
compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite"))
91+
compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")),
92+
compileFile("tests/rewrites/i22792.scala", defaultOptions.and("-rewrite")),
9293
).checkRewrites()
9394
}
9495

tests/neg/i22792.check

+10
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

+8
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

+8
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

+8
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)