Skip to content

Commit 32c0f84

Browse files
committed
Restrict implicit args to using
Typer#adaptNoArgsImplicitMethod populates implicit args when an arg list is missing. To remedy missing implicits, it tries a named application `using` args it did find. Then Applications#tryDefault supplies a default arg if available. A previous fix to allow tryDefault to supply implicit args for `implicit` params is now restricted to explicit `using`; typer now adds `using` for `implicit` when it needs to try defaults. This commit restores propagatedFailure and the previous condition that default params are tried if there is an error that is not an ambiguity. An additional restriction is that default params must be useful: there must be a param which has a default arg to be added (because it's not a named arg).
1 parent 590691b commit 32c0f84

File tree

10 files changed

+171
-95
lines changed

10 files changed

+171
-95
lines changed

Diff for: compiler/src/dotty/tools/dotc/typer/Applications.scala

+5-4
Original file line numberDiff line numberDiff line change
@@ -755,13 +755,14 @@ trait Applications extends Compatibility {
755755
}
756756
else defaultArgument(normalizedFun, n, testOnly)
757757

758-
def implicitArg = implicitArgTree(formal, appPos.span)
759-
760758
if !defaultArg.isEmpty then
761759
defaultArg.tpe.widen match
762760
case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1)
763761
case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
764-
else if methodType.isImplicitMethod && ctx.mode.is(Mode.ImplicitsEnabled) then
762+
else if (methodType.isContextualMethod || applyKind == ApplyKind.Using && methodType.isImplicitMethod)
763+
&& ctx.mode.is(Mode.ImplicitsEnabled)
764+
then
765+
val implicitArg = implicitArgTree(formal, appPos.span)
765766
matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1)
766767
else
767768
missingArg(n)
@@ -1198,7 +1199,7 @@ trait Applications extends Compatibility {
11981199
//
11991200
// summonFrom {
12001201
// case given A[t] =>
1201-
// summonFrom
1202+
// summonFrom {
12021203
// case given `t` => ...
12031204
// }
12041205
// }

Diff for: compiler/src/dotty/tools/dotc/typer/Implicits.scala

+2
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,8 @@ object Implicits:
570570
i"""
571571
|Note that implicit $what cannot be applied because they are ambiguous;
572572
|$explanation""" :: Nil
573+
574+
def asNested = if nested then this else AmbiguousImplicits(alt1, alt2, expectedType, argument, nested = true)
573575
end AmbiguousImplicits
574576

575577
class MismatchedImplicit(ref: TermRef,

Diff for: compiler/src/dotty/tools/dotc/typer/Typer.scala

+76-67
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import util.{Property, SimpleIdentityMap, SrcPos}
3636
import Applications.{tupleComponentTypes, wrapDefs, defaultArgument}
3737

3838
import collection.mutable
39-
import annotation.tailrec
4039
import Implicits.*
4140
import util.Stats.record
4241
import config.Printers.{gadts, typr}
@@ -52,7 +51,8 @@ import config.Config
5251
import config.MigrationVersion
5352
import transform.CheckUnused.OriginalName
5453

55-
import scala.annotation.constructorOnly
54+
import scala.annotation.{unchecked as _, *}
55+
import dotty.tools.dotc.util.chaining.*
5656

5757
object Typer {
5858

@@ -4193,6 +4193,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
41934193

41944194
def addImplicitArgs(using Context) =
41954195
def hasDefaultParams = methPart(tree).symbol.hasDefaultParams
4196+
def findDefaultArgument(argIndex: Int): Tree =
4197+
def appPart(t: Tree): Tree = t match
4198+
case Block(_, expr) => appPart(expr)
4199+
case Inlined(_, _, expr) => appPart(expr)
4200+
case t => t
4201+
defaultArgument(appPart(tree), n = argIndex, testOnly = false)
41964202
def implicitArgs(formals: List[Type], argIndex: Int, pt: Type): List[Tree] = formals match
41974203
case Nil => Nil
41984204
case formal :: formals1 =>
@@ -4214,13 +4220,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42144220
then implicitArgs(formals, argIndex, pt1)
42154221
else arg :: implicitArgs(formals1, argIndex + 1, pt1)
42164222
case failed: SearchFailureType =>
4217-
lazy val defaultArg =
4218-
def appPart(t: Tree): Tree = t match
4219-
case Block(stats, expr) => appPart(expr)
4220-
case Inlined(_, _, expr) => appPart(expr)
4221-
case _ => t
4222-
defaultArgument(appPart(tree), argIndex, testOnly = false)
4223-
.showing(i"default argument: for $formal, $tree, $argIndex = $result", typr)
4223+
lazy val defaultArg = findDefaultArgument(argIndex)
4224+
.showing(i"default argument: for $formal, $tree, $argIndex = $result", typr)
42244225
if !hasDefaultParams || defaultArg.isEmpty then
42254226
// no need to search further, the adapt fails in any case
42264227
// the reason why we continue inferring arguments in case of an AmbiguousImplicits
@@ -4242,44 +4243,44 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42424243
arg :: inferArgsAfter(arg)
42434244
end implicitArgs
42444245

4245-
/** Reports errors for arguments of `appTree` that have a
4246-
* `SearchFailureType`.
4247-
*/
4248-
def issueErrors(fun: Tree, args: List[Tree]): Tree =
4249-
// Prefer other errors over ambiguities. If nested in outer searches a missing
4250-
// implicit can be healed by simply dropping this alternative and trying something
4251-
// else. But an ambiguity is sticky and propagates outwards. If we have both
4252-
// a missing implicit on one argument and an ambiguity on another the whole
4253-
// branch should be classified as a missing implicit.
4254-
val firstNonAmbiguous = args.tpes.find(tp => tp.isError && !tp.isInstanceOf[AmbiguousImplicits])
4255-
def firstError = args.tpes.find(_.isInstanceOf[SearchFailureType]).getOrElse(NoType)
4256-
def firstFailure = firstNonAmbiguous.getOrElse(firstError)
4257-
val errorType =
4258-
firstFailure match
4259-
case tp: AmbiguousImplicits =>
4260-
AmbiguousImplicits(tp.alt1, tp.alt2, tp.expectedType, tp.argument, nested = true)
4261-
case tp =>
4262-
tp
4263-
val res = untpd.Apply(fun, args).withType(errorType)
4264-
4265-
wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach { (paramName, formal, arg) =>
4266-
arg.tpe match
4267-
case failure: SearchFailureType =>
4268-
val methodStr = err.refStr(methPart(fun).tpe)
4269-
val paramStr = implicitParamString(paramName, methodStr, fun)
4270-
val paramSym = fun.symbol.paramSymss.flatten.find(_.name == paramName)
4271-
val paramSymWithMethodCallTree = paramSym.map((_, res))
4272-
report.error(
4273-
missingArgMsg(arg, formal, paramStr, paramSymWithMethodCallTree),
4274-
tree.srcPos.endPos
4275-
)
4276-
case _ =>
4277-
}
4278-
4279-
res
4246+
// Pick a failure type to propagate, if any.
4247+
// Prefer other errors over ambiguities. If nested in outer searches a missing
4248+
// implicit can be healed by simply dropping this alternative and trying something
4249+
// else. But an ambiguity is sticky and propagates outwards. If we have both
4250+
// a missing implicit on one argument and an ambiguity on another the whole
4251+
// branch should be classified as a missing implicit.
4252+
def propagatedFailure(args: List[Tree]): Type = args match
4253+
case arg :: args => arg.tpe match
4254+
case ambi: AmbiguousImplicits => propagatedFailure(args) match
4255+
case NoType | (_: AmbiguousImplicits) => ambi
4256+
case failed => failed
4257+
case failed: SearchFailureType => failed
4258+
case _ => propagatedFailure(args)
4259+
case Nil => NoType
4260+
4261+
/** Reports errors for arguments of `appTree` that have a `SearchFailureType`.
4262+
*/
4263+
def issueErrors(fun: Tree, args: List[Tree], failureType: Type): Tree =
4264+
val errorType = failureType match
4265+
case ai: AmbiguousImplicits => ai.asNested
4266+
case tp => tp
4267+
untpd.Apply(fun, args)
4268+
.withType(errorType)
4269+
.tap: res =>
4270+
wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach: (paramName, formal, arg) =>
4271+
arg.tpe match
4272+
case failure: SearchFailureType =>
4273+
val methodStr = err.refStr(methPart(fun).tpe)
4274+
val paramStr = implicitParamString(paramName, methodStr, fun)
4275+
val paramSym = fun.symbol.paramSymss.flatten.find(_.name == paramName)
4276+
val paramSymWithMethodCallTree = paramSym.map((_, res))
4277+
val msg = missingArgMsg(arg, formal, paramStr, paramSymWithMethodCallTree)
4278+
report.error(msg, tree.srcPos.endPos)
4279+
case _ =>
42804280

42814281
val args = implicitArgs(wtp.paramInfos, 0, pt)
4282-
if (args.tpes.exists(_.isInstanceOf[SearchFailureType])) {
4282+
val failureType = propagatedFailure(args)
4283+
if failureType.exists then
42834284
// If there are several arguments, some arguments might already
42844285
// have influenced the context, binding variables, but later ones
42854286
// might fail. In that case the constraint and instantiated variables
@@ -4288,32 +4289,40 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42884289

42894290
// If method has default params, fall back to regular application
42904291
// where all inferred implicits are passed as named args.
4291-
if hasDefaultParams then
4292+
if hasDefaultParams && !failureType.isInstanceOf[AmbiguousImplicits] then
42924293
// Only keep the arguments that don't have an error type, or that
4293-
// have an `AmbiguousImplicits` error type. The later ensures that a
4294+
// have an `AmbiguousImplicits` error type. The latter ensures that a
42944295
// default argument can't override an ambiguous implicit. See tests
42954296
// `given-ambiguous-default*` and `19414*`.
42964297
val namedArgs =
4297-
wtp.paramNames.lazyZip(args)
4298-
.filter((_, arg) => !arg.tpe.isError || arg.tpe.isInstanceOf[AmbiguousImplicits])
4299-
.map((pname, arg) => untpd.NamedArg(pname, untpd.TypedSplice(arg)))
4300-
4301-
val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs)
4302-
val needsUsing = wtp.isContextualMethod || wtp.match
4303-
case MethodType(ContextBoundParamName(_) :: _) => sourceVersion.isAtLeast(`3.4`)
4304-
case _ => false
4305-
if needsUsing then app.setApplyKind(ApplyKind.Using)
4306-
typr.println(i"try with default implicit args $app")
4307-
val retyped = typed(app, pt, locked)
4308-
4309-
// If the retyped tree still has an error type and is an `Apply`
4310-
// node, we can report the errors for each argument nicely.
4311-
// Otherwise, we don't report anything here.
4312-
retyped match
4313-
case Apply(tree, args) if retyped.tpe.isError => issueErrors(tree, args)
4314-
case _ => retyped
4315-
else issueErrors(tree, args)
4316-
}
4298+
wtp.paramNames.lazyZip(args).collect:
4299+
case (pname, arg) if !arg.tpe.isError || arg.tpe.isInstanceOf[AmbiguousImplicits] =>
4300+
untpd.NamedArg(pname, untpd.TypedSplice(arg))
4301+
.toList
4302+
val usingDefaultArgs =
4303+
wtp.paramNames.zipWithIndex
4304+
.exists((n, i) => !namedArgs.exists(_.name == n) && !findDefaultArgument(i).isEmpty)
4305+
4306+
if !usingDefaultArgs then
4307+
issueErrors(tree, args, failureType)
4308+
else
4309+
val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs)
4310+
// old-style implicit needs to be marked using so that implicits are searched
4311+
val needsUsing = wtp.isImplicitMethod || wtp.match
4312+
case MethodType(ContextBoundParamName(_) :: _) => sourceVersion.isAtLeast(`3.4`)
4313+
case _ => false
4314+
if needsUsing then app.setApplyKind(ApplyKind.Using)
4315+
typr.println(i"try with default implicit args $app")
4316+
// If the retyped tree still has an error type and is an `Apply`
4317+
// node, we can report the errors for each argument nicely.
4318+
// Otherwise, we don't report anything here.
4319+
typed(app, pt, locked) match
4320+
case retyped @ Apply(tree, args) if retyped.tpe.isError =>
4321+
propagatedFailure(args) match
4322+
case sft: SearchFailureType => issueErrors(tree, args, sft)
4323+
case _ => issueErrors(tree, args, retyped.tpe)
4324+
case retyped => retyped
4325+
else issueErrors(tree, args, failureType)
43174326
else
43184327
inContext(origCtx):
43194328
// Reset context in case it was set to a supercall context before.

Diff for: tests/neg/given-ambiguous-default-1.check

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
-- [E172] Type Error: tests/neg/given-ambiguous-default-1.scala:18:23 --------------------------------------------------
22
18 |def f: Unit = summon[B] // error: Ambiguous given instances
33
| ^
4-
| No best given instance of type B was found for parameter x of method summon in object Predef.
5-
| I found:
4+
| No best given instance of type B was found for parameter x of method summon in object Predef.
5+
| I found:
66
|
7-
| given_B(a = /* ambiguous: both given instance a1 and given instance a2 match type A */summon[A])
7+
| given_B(/* ambiguous: both given instance a1 and given instance a2 match type A */summon[A])
88
|
9-
| But both given instance a1 and given instance a2 match type A.
9+
| But both given instance a1 and given instance a2 match type A.

Diff for: tests/neg/i18123.check

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
-- [E172] Type Error: tests/neg/i18123.scala:25:33 ---------------------------------------------------------------------
1+
-- [E172] Type Error: tests/neg/i18123.scala:25:48 ---------------------------------------------------------------------
22
25 | (charClassIntersection.rep() | classItem.rep()) // error
3-
| ^^^^^^^^^^^^^^^
4-
|No given instance of type pkg.Implicits.Repeater[pkg.RegexTree, V] was found.
3+
| ^
4+
|No given instance of type pkg.Implicits.Repeater[pkg.RegexTree, V] was found for parameter repeater of method rep in package pkg.
55
|I found:
66
|
77
| pkg.Implicits.Repeater.GenericRepeaterImplicit[T]

Diff for: tests/neg/i19594.check

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
-- [E172] Type Error: tests/neg/i19594.scala:12:14 ---------------------------------------------------------------------
2-
12 | assertEquals(true, 1, "values are not the same") // error
3-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4-
| Can you see me?!
5-
-- [E172] Type Error: tests/neg/i19594.scala:13:14 ---------------------------------------------------------------------
6-
13 | assertEquals(true, 1) // error
7-
| ^^^^^^^^^^^^^^^^^^^^^
8-
| Can you see me?!
1+
-- [E172] Type Error: tests/neg/i19594.scala:15:50 ---------------------------------------------------------------------
2+
15 | assertEquals(true, 1, "values are not the same") // error
3+
| ^
4+
| Can you see me?!
5+
-- [E172] Type Error: tests/neg/i19594.scala:16:23 ---------------------------------------------------------------------
6+
16 | assertEquals(true, 1) // error
7+
| ^
8+
| Can you see me?!
9+
-- [E172] Type Error: tests/neg/i19594.scala:20:12 ---------------------------------------------------------------------
10+
20 | f(true, 1) // error
11+
| ^
12+
| Can you see me?!
13+
-- [E172] Type Error: tests/neg/i19594.scala:23:39 ---------------------------------------------------------------------
14+
23 | g(true, 1, "values are not the same") // error
15+
| ^
16+
| Can you see me?!
17+
-- [E172] Type Error: tests/neg/i19594.scala:26:3 ----------------------------------------------------------------------
18+
26 | h(true, 1) // error
19+
| ^^^^^^^^^^
20+
| No given instance of type String was found

Diff for: tests/neg/i19594.scala

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import scala.annotation.implicitNotFound
22

33
@implicitNotFound("Can you see me?!")
4-
trait Compare[A, B]
4+
trait Compare[-A, -B]
5+
6+
object Compare:
7+
val any: Compare[Any, Any] = new Compare {}
58

69
object example extends App:
710

@@ -11,3 +14,13 @@ object example extends App:
1114

1215
assertEquals(true, 1, "values are not the same") // error
1316
assertEquals(true, 1) // error
17+
18+
object updated:
19+
def f[A, B](a: A, b: B)(using Compare[A, B]) = ()
20+
f(true, 1) // error
21+
22+
def g[A, B](a: A, b: B, clue: => Any)(implicit comp: Compare[A, B]) = ()
23+
g(true, 1, "values are not the same") // error
24+
25+
def h[A, B](a: A, b: B)(using c: Compare[A, B] = Compare.any, s: String) = ()
26+
h(true, 1) // error

Diff for: tests/neg/i22439.check

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-- [E171] Type Error: tests/neg/i22439.scala:7:3 -----------------------------------------------------------------------
2+
7 | f() // error f() missing arg
3+
| ^^^
4+
| missing argument for parameter i of method f: (implicit i: Int, j: Int): Int
5+
-- [E050] Type Error: tests/neg/i22439.scala:8:2 -----------------------------------------------------------------------
6+
8 | g() // error g(given_Int, given_Int)() doesn't take more params
7+
| ^
8+
| method g does not take more parameters
9+
|
10+
| longer explanation available when compiling with `-explain`
11+
-- [E171] Type Error: tests/neg/i22439.scala:11:3 ----------------------------------------------------------------------
12+
11 | f(j = 27) // error missing argument for parameter i of method f
13+
| ^^^^^^^^^
14+
| missing argument for parameter i of method f: (implicit i: Int, j: Int): Int
15+
-- [E172] Type Error: tests/neg/i22439.scala:16:3 ----------------------------------------------------------------------
16+
16 | h // error
17+
| ^
18+
| No given instance of type String was found for parameter s of method h
19+
-- [E172] Type Error: tests/neg/i22439.scala:17:3 ----------------------------------------------------------------------
20+
17 | h(using i = 17) // error
21+
| ^^^^^^^^^^^^^^^
22+
| No given instance of type String was found

Diff for: tests/neg/i22439.scala

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
@main def test() = println:
3+
given Int = 42
4+
def f(implicit i: Int, j: Int) = i + j
5+
def g(using i: Int, j: Int) = i + j
6+
val x: Int = f
7+
f() // error f() missing arg
8+
g() // error g(given_Int, given_Int)() doesn't take more params
9+
f // ok implicits
10+
g // ok implicits
11+
f(j = 27) // error missing argument for parameter i of method f
12+
f(using j = 27) // ok, explicit supplemented by implicit
13+
g(using j = 27) // ok, explicit supplemented by implicit
14+
15+
def h(using i: Int, s: String) = s * i
16+
h // error
17+
h(using i = 17) // error

Diff for: tests/run/extra-implicits.scala

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11

22
case class A(x: String)
33
case class B(x: String)
4-
given a1: A("default")
5-
given b1: B("default")
6-
val a2 = A("explicit")
7-
val b2 = B("explicit")
4+
given A("default")
5+
given B("default")
6+
val a = A("explicit")
7+
val b = B("explicit")
88

99
def f(using a: A, b: B): Unit =
1010
println(a)
1111
println(b)
1212

1313
@main def Test =
14-
f(using a2)
15-
f(using a = a2)
16-
f(using b = b2)
17-
f(using b = b2, a = a2)
14+
f(using a)
15+
f(using a = a)
16+
f(using b = b)
17+
f(using b = b, a = a)
1818

0 commit comments

Comments
 (0)