Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't try default args when there are ambiguous implicits #19647

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 9 additions & 19 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3849,22 +3849,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
end implicitArgs

val args = implicitArgs(wtp.paramInfos, 0, pt)

def propagatedFailure(args: List[Tree]): Type = args match {
case arg :: args1 =>
arg.tpe match {
case ambi: AmbiguousImplicits =>
propagatedFailure(args1) match {
case NoType | (_: AmbiguousImplicits) => ambi
case failed => failed
}
case failed: SearchFailureType => failed
case _ => propagatedFailure(args1)
}
case Nil => NoType
}

val propFail = propagatedFailure(args)
val firstAmbiguous = args.tpes.find(_.isInstanceOf[AmbiguousImplicits])
def firstError = args.tpes.find(_.isError)
Copy link
Member Author

@mbovel mbovel Feb 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now traversing args twice in the worst case, but I think the readability gain is worth it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: as before, I am only checking for AmbiguousImplicits. Should TooUnspecific fall in the same case? I am asking because both are considered ambiguous here:

https://github.com/lampepfl/dotty/blob/4c1d58f067f40c9cd52628f195c6306b5a483d4f/compiler/src/dotty/tools/dotc/typer/Implicits.scala#L442

val propFail = firstAmbiguous.orElse(firstError).getOrElse(NoType)
Copy link
Member

@smarter smarter Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the previous logic preferred non-ambiguity errors over ambiguity errors, and this PR changes it to prefer ambiguity errors over non-ambiguity errors, but since ambiguity is no longer special after https://dotty.epfl.ch/docs/reference/changed-features/implicit-resolution.html, perhaps we could just report the first error found? /cc @odersky


def issueErrors(): Tree = {
def paramSymWithMethodTree(paramName: TermName) =
Expand Down Expand Up @@ -3897,9 +3884,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
// need to be reset.
ctx.typerState.resetTo(saved)

// If method has default params, fall back to regular application
// where all inferred implicits are passed as named args.
if hasDefaultParams && !propFail.isInstanceOf[AmbiguousImplicits] then
// If method has default params and there are no "Ambiguous implicits"
// error, fall back to regular application where all inferred
// implicits are passed as named args.
// If there are "Ambiguous implicits" errors, these take precedence
// over the default params (see issue #19414 and related tests).
if hasDefaultParams && firstAmbiguous.isEmpty then
val namedArgs = wtp.paramNames.lazyZip(args).flatMap { (pname, arg) =>
if (arg.tpe.isError) Nil else untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil
}
Expand Down
4 changes: 4 additions & 0 deletions tests/neg/19414-desugared.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E172] Type Error: tests/neg/19414-desugared.scala:34:34 ------------------------------------------------------------
34 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances
| ^
|Ambiguous given instances: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] of parameter x of method summon in object Predef
34 changes: 34 additions & 0 deletions tests/neg/19414-desugared.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/** This test checks that provided given instances take precedence over default
* given arguments, even when there are multiple default arguments.
*
* Before the fix for issue #19414, this code would fail with a "No given
* instance of type BodySerializer[JsObject]".
*
* See also:
* - tests/neg/19414.scala
* - tests/neg/given-ambiguous-default-1.scala
* - tests/neg/given-ambiguous-default-2.scala
*/

trait JsValue
trait JsObject extends JsValue

trait Writer[T]
trait BodySerializer[-B]

class Printer

given Writer[JsValue] = ???
given Writer[JsObject] = ???

// This is not an exact desugaring of the original code: currently the compiler
// actually changes the modifier of the parameter list from `using` to
// `implicit` when desugaring the context-bound `B: Writer` to `implicit writer:
// Writer[B]`, but we can't write in user code as this is not valid syntax.
given [B](using
writer: Writer[B],
printer: Printer = new Printer
): BodySerializer[B] = ???

def f: Unit =
summon[BodySerializer[JsObject]] // error: Ambiguous given instances
4 changes: 4 additions & 0 deletions tests/neg/19414.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E172] Type Error: tests/neg/19414.scala:27:34 ----------------------------------------------------------------------
27 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances
| ^
|Ambiguous given instances: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] of parameter x of method summon in object Predef
27 changes: 27 additions & 0 deletions tests/neg/19414.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/** This test checks that provided given instances take precedence over default
* given arguments, even when there are multiple default arguments.
*
* Before the fix for issue #19414, this code would fail with a "No given
* instance of type BodySerializer[JsObject]".
*
* See also:
* - tests/neg/19414-desugared.scala
* - tests/neg/given-ambiguous-default-1.scala
* - tests/neg/given-ambiguous-default-2.scala
*/

trait JsValue
trait JsObject extends JsValue

trait Writer[T]
trait BodySerializer[-B]

class Printer

given Writer[JsValue] = ???
given Writer[JsObject] = ???

given [B: Writer](using printer: Printer = new Printer): BodySerializer[B] = ???

def f: Unit =
summon[BodySerializer[JsObject]] // error: Ambiguous given instances
4 changes: 4 additions & 0 deletions tests/neg/given-ambiguous-default-1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E172] Type Error: tests/neg/given-ambiguous-default-1.scala:19:23 --------------------------------------------------
19 |def f: Unit = summon[B] // error: Ambiguous given instances
| ^
|Ambiguous given instances: both given instance a1 and given instance a2 match type A of parameter x of method summon in object Predef
19 changes: 19 additions & 0 deletions tests/neg/given-ambiguous-default-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** This test checks that provided given instances take precedence over default
* given arguments.
*
* In the following code, the compiler must report an "Ambiguous implicits"
* error for the parameter `a`, and must not use its default value.
*
* See also:
* - tests/neg/19414.scala
mbovel marked this conversation as resolved.
Show resolved Hide resolved
* - tests/neg/19414-desugared.scala
* - tests/neg/given-ambiguous-default-2.scala
*/

class A
class B
given a1: A = ???
given a2: A = ???
given (using a: A = A()): B = ???

def f: Unit = summon[B] // error: Ambiguous given instances
4 changes: 4 additions & 0 deletions tests/neg/given-ambiguous-default-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E172] Type Error: tests/neg/given-ambiguous-default-2.scala:19:23 --------------------------------------------------
19 |def f: Unit = summon[C] // error: Ambiguous given instances
| ^
|Ambiguous given instances: both given instance a1 and given instance a2 match type A of parameter x of method summon in object Predef
19 changes: 19 additions & 0 deletions tests/neg/given-ambiguous-default-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** This test checks that provided given instances take precedence over default
* given arguments, even when there are multiple default arguments.
*
* Before the fix for issue #19414, this code would compile without errors.
*
* See also:
* - tests/neg/given-ambiguous-default-1.scala
* - tests/neg/19414.scala
* - tests/neg/19414-desugared.scala
*/

class A
class B
class C
given a1: A = ???
given a2: A = ???
given (using a: A = A(), b: B = B()): C = ???

def f: Unit = summon[C] // error: Ambiguous given instances
Loading