Skip to content

Propagate dependencies through implicit parameters #5601

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

Merged
merged 1 commit into from
Dec 12, 2018
Merged
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
13 changes: 10 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2348,7 +2348,7 @@ class Typer extends Namer
def dummyArg(tp: Type) = untpd.Ident(nme.???).withTypeUnchecked(tp)

def addImplicitArgs(implicit ctx: Context) = {
def implicitArgs(formals: List[Type]): List[Tree] = formals match {
def implicitArgs(formals: List[Type], argIndex: Int): List[Tree] = formals match {
case Nil => Nil
case formal :: formals1 =>
val arg = inferImplicitArg(formal, tree.pos.endPos)
Expand All @@ -2361,10 +2361,17 @@ class Typer extends Namer
// If there are none, we have to propagate the ambiguity to the caller.
arg :: formals1.map(dummyArg)
case _ =>
arg :: implicitArgs(formals1)
// If the implicit parameter list is dependent we must propagate inferred
// types through the remainder of the parameter list similarly to how it's
// done for non-implicit parameter lists in Applications#matchArgs#addTyped.
val formals2 =
if (wtp.isParamDependent && arg.tpe.exists)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think arg.tpe.exists can occur here unlike addTyped, but we should check arg.tpe.isError since in case of implicit failure, the type will be an instance of SearchFailureType which is a subtype of ErrorType:

Suggested change
if (wtp.isParamDependent && arg.tpe.exists)
if (wtp.isParamDependent && !arg.tpe.isError)

I also suggest adding some negative test cases that exercises this code path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do need exists here. Switching to !arg.tpe.isError results in an assertion for a non-existent getter denotation.

I'll add some negative test cases.

formals1.mapconserve(f1 => safeSubstParam(f1, wtp.paramRefs(argIndex), arg.tpe))
else formals1
arg :: implicitArgs(formals2, argIndex + 1)
}
}
val args = implicitArgs(wtp.paramInfos)
val args = implicitArgs(wtp.paramInfos, 0)

def propagatedFailure(args: List[Tree]): Type = args match {
case arg :: args1 =>
Expand Down
26 changes: 26 additions & 0 deletions tests/neg/i5427.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
trait Foo[In] { type Out }

object Test {
def fooInt: Foo[Int] { type Out = String } = ???
implicit def str: String = ???

def test1[A](f1: Foo[A])(implicit f2: f1.Out) = ???
def test2[A](implicit f1: Foo[A], f2: f1.Out) = ???

test1(fooInt) // OK
test2 // error
}

object Test2 {
implicit def fooInt: Foo[Int] { type Out = String } = ???
implicit def fooString: Foo[String] { type Out = Boolean } = ???
implicit def fooBoolean: Foo[Boolean] { type Out = Double } = ???

def test3[A](f1: Foo[A], f2: Foo[f1.Out])(implicit f3: Foo[f2.Out]): f3.Out = ???
def test4[A](implicit f1: Foo[A], f2: Foo[f1.Out], f3: Foo[f2.Out]): f3.Out = ???

val t3 = test3(fooInt, fooString)
t3: Double
val t4 = test4 // error
t4: Double
}
46 changes: 46 additions & 0 deletions tests/pos/i5427.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
trait Foo[In] { type Out }

object Test {
implicit def fooInt: Foo[Int] { type Out = String } = ???
implicit def str: String = ???

def test1[A](f1: Foo[A])(implicit f2: f1.Out) = ???
def test2[A](implicit f1: Foo[A], f2: f1.Out) = ???

test1(fooInt) // OK
test2 // OK
}

object Test2 {
implicit def fooInt: Foo[Int] { type Out = String } = ???
implicit def fooString: Foo[String] { type Out = Boolean } = ???
implicit def fooBoolean: Foo[Boolean] { type Out = Double } = ???

def test3[A](f1: Foo[A], f2: Foo[f1.Out])(implicit f3: Foo[f2.Out]): f3.Out = ???
def test4[A](implicit f1: Foo[A], f2: Foo[f1.Out], f3: Foo[f2.Out]): f3.Out = ???

val t3 = test3(fooInt, fooString)
t3: Double
val t4 = test4[Int]
t4: Double
}

object Test3 {
def fooInt: Foo[Int] { type Out = String } = ???
implicit def str: String = ???

def test5[A](implicit f1: Foo[A] = fooInt, f2: f1.Out) = f2

val t5 = test5
t5: String
}

object Test4 {
implicit def fooInt: Foo[Int] { type Out = String } = ???
def str: String = ???

def test6[A](implicit f1: Foo[A], f2: f1.Out = str) = f2

val t6 = test6
t6: String
}