From ca36ff281c4c413994cee51fefa5d4d6d818c4de Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Jul 2020 12:33:31 +0200 Subject: [PATCH 1/4] Infer missing implicit args in using clause If there are some missing arguments in a using argument clause, infer them with implicit search. --- .../dotty/tools/dotc/typer/Applications.scala | 28 +++++++++++-------- tests/run/extra-implicits.check | 8 ++++++ tests/run/extra-implicits.scala | 18 ++++++++++++ 3 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 tests/run/extra-implicits.check create mode 100644 tests/run/extra-implicits.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 8181791a705d..011aa8cf8f81 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -543,6 +543,10 @@ trait Applications extends Compatibility { else identity } + def addTypedAndSubstitute(arg: Arg, formal: Type): List[Type] = + val substParam = addTyped(arg, formal) + formals1.mapconserve(substParam) + def missingArg(n: Int): Unit = { val pname = methodType.paramNames(n) fail( @@ -553,7 +557,7 @@ trait Applications extends Compatibility { def tryDefault(n: Int, args1: List[Arg]): Unit = { val sym = methRef.symbol - val defaultExpr = + def defaultExpr = if (isJavaAnnotConstr(sym)) { val cinfo = sym.owner.asClass.classInfo val pname = methodType.paramNames(n) @@ -580,12 +584,16 @@ trait Applications extends Compatibility { EmptyTree } - if (!defaultExpr.isEmpty) { - val substParam = addTyped(treeToArg(defaultExpr), formal) - matchArgs(args1, formals1.mapconserve(substParam), n + 1) - } + def implicitArg = implicitArgTree(formal, appPos.span) + + if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) then + matchArgs(args1, addTypedAndSubstitute(treeToArg(implicitArg), formal), n + 1) else - missingArg(n) + val defaultArg = defaultExpr + if !defaultArg.isEmpty then + matchArgs(args1, addTypedAndSubstitute(treeToArg(defaultArg), formal), n + 1) + else + missingArg(n) } if (formal.isRepeatedParam) @@ -605,8 +613,7 @@ trait Applications extends Compatibility { case EmptyTree :: args1 => tryDefault(n, args1) case arg :: args1 => - val substParam = addTyped(arg, formal) - matchArgs(args1, formals1.mapconserve(substParam), n + 1) + matchArgs(args1, addTypedAndSubstitute(arg, formal), n + 1) case nil => tryDefault(n, args) } @@ -1674,10 +1681,7 @@ trait Applications extends Compatibility { pt match case pt: FunProto => if pt.applyKind == ApplyKind.Using then - val alts0 = alts.filterConserve { alt => - val mt = alt.widen.stripPoly - mt.isImplicitMethod || mt.isContextualMethod - } + val alts0 = alts.filterConserve(_.widen.stripPoly.isImplicitMethod) if alts0 ne alts then return resolve(alts0) else if alts.exists(_.widen.stripPoly.isContextualMethod) then return resolveMapped(alts, alt => stripImplicit(alt.widen), pt) diff --git a/tests/run/extra-implicits.check b/tests/run/extra-implicits.check new file mode 100644 index 000000000000..5df402dfaf4d --- /dev/null +++ b/tests/run/extra-implicits.check @@ -0,0 +1,8 @@ +A(explicit) +B(default) +A(explicit) +B(default) +A(default) +B(explicit) +A(explicit) +B(explicit) diff --git a/tests/run/extra-implicits.scala b/tests/run/extra-implicits.scala new file mode 100644 index 000000000000..615bd50a344d --- /dev/null +++ b/tests/run/extra-implicits.scala @@ -0,0 +1,18 @@ + +case class A(x: String) +case class B(x: String) +given a1 as A("default") +given b1 as B("default") +val a2 = A("explicit") +val b2 = B("explicit") + +def f(using a: A, b: B): Unit = + println(a) + println(b) + +@main def Test = + f(using a2) + f(using a = a2) + f(using b = b2) + f(using b = b2, a = a2) + From c5e4942af0b186fec90f35a826de240ad2a77894 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Jul 2020 12:44:41 +0200 Subject: [PATCH 2/4] Drop redundant isContextualMethod comparisons isImplicitMethod is implied by isContextualMethod, no need to test the two separately. --- compiler/src/dotty/tools/dotc/core/Denotations.scala | 2 +- compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 11314ce11666..cf417ed032bb 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -540,7 +540,7 @@ object Denotations { tp2 match case tp2: MethodType if ctx.typeComparer.matchingMethodParams(tp1, tp2) - && (tp1.isImplicitMethod || tp1.isContextualMethod) == (tp2.isImplicitMethod || tp2.isContextualMethod) + && tp1.isImplicitMethod == tp2.isImplicitMethod && tp1.isErasedMethod == tp2.isErasedMethod => val resType = infoMeet(tp1.resType, tp2.resType.subst(tp2, tp1), safeIntersection) if resType.exists then diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 750683a0419c..42a085232343 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -380,7 +380,7 @@ object PatternMatcher { else { def applyImplicits(acc: Tree, implicits: List[Tree], mt: Type): Tree = mt match { case mt: MethodType => - assert(mt.isImplicitMethod || mt.isContextualMethod) + assert(mt.isImplicitMethod) val (args, rest) = implicits.splitAt(mt.paramNames.size) applyImplicits(acc.appliedToArgs(args), rest, mt.resultType) case _ => From ac1250b2777d0be757d06fd9d5249fa5fa3f7d4c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Jul 2020 12:46:12 +0200 Subject: [PATCH 3/4] Fuse addTyped and addTypedAndSubstitute --- .../dotty/tools/dotc/typer/Applications.scala | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 011aa8cf8f81..842901a83408 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -533,19 +533,17 @@ trait Applications extends Compatibility { case formal :: formals1 => /** Add result of typing argument `arg` against parameter type `formal`. - * @return A type transformation to apply to all arguments following this one. + * @return The remaining formal parameter types. If the method is parameter-dependent + * this means substituting the actual argument type for the current formal parameter + * in the remaining formal parameters. */ - def addTyped(arg: Arg, formal: Type): Type => Type = { + def addTyped(arg: Arg, formal: Type): List[Type] = addArg(typedArg(arg, formal), formal) - if (methodType.isParamDependent && typeOfArg(arg).exists) + if methodType.isParamDependent && typeOfArg(arg).exists then // `typeOfArg(arg)` could be missing because the evaluation of `arg` produced type errors - safeSubstParam(_, methodType.paramRefs(n), typeOfArg(arg)) - else identity - } - - def addTypedAndSubstitute(arg: Arg, formal: Type): List[Type] = - val substParam = addTyped(arg, formal) - formals1.mapconserve(substParam) + formals1.mapconserve(safeSubstParam(_, methodType.paramRefs(n), typeOfArg(arg))) + else + formals1 def missingArg(n: Int): Unit = { val pname = methodType.paramNames(n) @@ -587,11 +585,11 @@ trait Applications extends Compatibility { def implicitArg = implicitArgTree(formal, appPos.span) if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) then - matchArgs(args1, addTypedAndSubstitute(treeToArg(implicitArg), formal), n + 1) + matchArgs(args1, addTyped(treeToArg(implicitArg), formal), n + 1) else val defaultArg = defaultExpr if !defaultArg.isEmpty then - matchArgs(args1, addTypedAndSubstitute(treeToArg(defaultArg), formal), n + 1) + matchArgs(args1, addTyped(treeToArg(defaultArg), formal), n + 1) else missingArg(n) } @@ -613,7 +611,7 @@ trait Applications extends Compatibility { case EmptyTree :: args1 => tryDefault(n, args1) case arg :: args1 => - matchArgs(args1, addTypedAndSubstitute(arg, formal), n + 1) + matchArgs(args1, addTyped(arg, formal), n + 1) case nil => tryDefault(n, args) } From 55d73003ea7f5f0af304527c6ade11e572400f30 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Jul 2020 13:13:28 +0200 Subject: [PATCH 4/4] Change order of default and implicit attempts In a using argument clause with missing arguments: - resolve to defaults first, - only if that fails, do an implicit argument search. This is the same as if all implicit parameters `x: T` that did not have an explicit default now are given one like this: x: T = summon[T] --- .../src/dotty/tools/dotc/typer/Applications.scala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 842901a83408..c97999f30da1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -555,7 +555,7 @@ trait Applications extends Compatibility { def tryDefault(n: Int, args1: List[Arg]): Unit = { val sym = methRef.symbol - def defaultExpr = + val defaultArg = if (isJavaAnnotConstr(sym)) { val cinfo = sym.owner.asClass.classInfo val pname = methodType.paramNames(n) @@ -584,14 +584,12 @@ trait Applications extends Compatibility { def implicitArg = implicitArgTree(formal, appPos.span) - if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) then + if !defaultArg.isEmpty then + matchArgs(args1, addTyped(treeToArg(defaultArg), formal), n + 1) + else if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) then matchArgs(args1, addTyped(treeToArg(implicitArg), formal), n + 1) else - val defaultArg = defaultExpr - if !defaultArg.isEmpty then - matchArgs(args1, addTyped(treeToArg(defaultArg), formal), n + 1) - else - missingArg(n) + missingArg(n) } if (formal.isRepeatedParam)