Skip to content

Commit 56a9d3b

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 0e567c1 commit 56a9d3b

File tree

10 files changed

+171
-95
lines changed

10 files changed

+171
-95
lines changed

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
// }

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,

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 scala.util.chaining.given
5656

5757
object Typer {
5858

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

41894189
def addImplicitArgs(using Context) =
41904190
def hasDefaultParams = methPart(tree).symbol.hasDefaultParams
4191+
def findDefaultArgument(argIndex: Int): Tree =
4192+
def appPart(t: Tree): Tree = t match
4193+
case Block(_, expr) => appPart(expr)
4194+
case Inlined(_, _, expr) => appPart(expr)
4195+
case t => t
4196+
defaultArgument(appPart(tree), n = argIndex, testOnly = false)
41914197
def implicitArgs(formals: List[Type], argIndex: Int, pt: Type): List[Tree] = formals match
41924198
case Nil => Nil
41934199
case formal :: formals1 =>
@@ -4209,13 +4215,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42094215
then implicitArgs(formals, argIndex, pt1)
42104216
else arg :: implicitArgs(formals1, argIndex + 1, pt1)
42114217
case failed: SearchFailureType =>
4212-
lazy val defaultArg =
4213-
def appPart(t: Tree): Tree = t match
4214-
case Block(stats, expr) => appPart(expr)
4215-
case Inlined(_, _, expr) => appPart(expr)
4216-
case _ => t
4217-
defaultArgument(appPart(tree), argIndex, testOnly = false)
4218-
.showing(i"default argument: for $formal, $tree, $argIndex = $result", typr)
4218+
lazy val defaultArg = findDefaultArgument(argIndex)
4219+
.showing(i"default argument: for $formal, $tree, $argIndex = $result", typr)
42194220
if !hasDefaultParams || defaultArg.isEmpty then
42204221
// no need to search further, the adapt fails in any case
42214222
// the reason why we continue inferring arguments in case of an AmbiguousImplicits
@@ -4237,44 +4238,44 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42374238
arg :: inferArgsAfter(arg)
42384239
end implicitArgs
42394240

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

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

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

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]

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

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

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

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

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)