From 1c60fcf562026f2df0f44a670862e4a6319c0521 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 28 Apr 2020 18:02:07 +0200 Subject: [PATCH 1/4] Fix #8802: Use wildcards for result type approximation of implicits Use wildcards for result type approximation of dependent implicit methods. In a situation like ``` def f(using x: T): C[x.Out] ``` we need to go constrain the result type of `f` before doing the implicit search for `T`. Previously we created a fresh type variable `param(1)` with `T & Singleton` as upper bound. That works well for other uses of result type approximation, but in this case, we miss constraining `param(1)` from below by the result of the implicit search. So in the end `param(1)` gets interpolated to its upper bound, which leads to problems. One possible solution is to add the missing lower constraint for the dependent type variable. But it's easier to just use a Wildcard type, which is what this PR does. --- .../src/dotty/tools/dotc/typer/ProtoTypes.scala | 13 +++++++------ tests/pos/i8802.scala | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 tests/pos/i8802.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index e72f7eac0b38..ece671216f7d 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -536,14 +536,15 @@ object ProtoTypes { /** The result type of `mt`, where all references to parameters of `mt` are * replaced by either wildcards (if typevarsMissContext) or TypeParamRefs. */ - def resultTypeApprox(mt: MethodType)(using Context): Type = - if (mt.isResultDependent) { + def resultTypeApprox(mt: MethodType, wildcardOnly: Boolean = false)(using Context): Type = + if mt.isResultDependent then def replacement(tp: Type) = - if (ctx.mode.is(Mode.TypevarsMissContext) || - !tp.widenExpr.isValueTypeOrWildcard) WildcardType + if wildcardOnly + || ctx.mode.is(Mode.TypevarsMissContext) + || !tp.widenExpr.isValueTypeOrWildcard + then WildcardType else newDepTypeVar(tp) mt.resultType.substParams(mt, mt.paramInfos.map(replacement)) - } else mt.resultType /** The normalized form of a type @@ -568,7 +569,7 @@ object ProtoTypes { case poly: PolyType => normalize(constrained(poly).resultType, pt) case mt: MethodType => - if (mt.isImplicitMethod) normalize(resultTypeApprox(mt), pt) + if (mt.isImplicitMethod) normalize(resultTypeApprox(mt, wildcardOnly = true), pt) else if (mt.isResultDependent) tp else { val rt = normalize(mt.resultType, pt) diff --git a/tests/pos/i8802.scala b/tests/pos/i8802.scala new file mode 100644 index 000000000000..e9afc174d748 --- /dev/null +++ b/tests/pos/i8802.scala @@ -0,0 +1,17 @@ +trait Foo[A, B] { + type Out +} + +object Test { + + type Bar[A] + + def unit: Bar[Unit] = ??? + def product[A, B](fst: Bar[A], snd: Bar[B])(implicit foo: Foo[A, B]): Bar[foo.Out] = ??? + + implicit def foo[A]: Foo[A, Unit] { type Out = A } = ??? + + def check[A](bar: Bar[A])(a: A): Unit = {} + + check(product(unit, unit))(()) // error +} \ No newline at end of file From f24fffa6b1c628deb4aeefbc0f199bb64376195b Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 28 Apr 2020 18:50:56 +0200 Subject: [PATCH 2/4] Update compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala Co-Authored-By: Guillaume Martres --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index ece671216f7d..1f00852dcbde 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -534,7 +534,7 @@ object ProtoTypes { newTypeVar(TypeBounds.upper(AndType(tp.widenExpr, defn.SingletonClass.typeRef))) /** The result type of `mt`, where all references to parameters of `mt` are - * replaced by either wildcards (if typevarsMissContext) or TypeParamRefs. + * replaced by either wildcards or TypeParamRefs. */ def resultTypeApprox(mt: MethodType, wildcardOnly: Boolean = false)(using Context): Type = if mt.isResultDependent then From f867da2616d76d67fc75f308d6167614c8c4c8ca Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 28 Apr 2020 19:04:07 +0200 Subject: [PATCH 3/4] Test case for 8825 --- tests/pos/i8825.scala | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/pos/i8825.scala diff --git a/tests/pos/i8825.scala b/tests/pos/i8825.scala new file mode 100644 index 000000000000..49c85434ce98 --- /dev/null +++ b/tests/pos/i8825.scala @@ -0,0 +1,24 @@ +sealed trait Nat +case class Succ[N <: Nat](n: N) extends Nat +case object Zero extends Nat +type Zero = Zero.type +type One = Succ[Zero] + +sealed trait HList +case class HCons[+H, +T <: HList](head: H, tail: T) extends HList +case object HNil extends HList +type HNil = HNil.type + +trait Length[L <: HList] { + type Out <: Nat +} +object Length { + type Aux[L <: HList, Out0 <: Nat] = Length[L] { type Out = Out0 } + def instance[L <: HList, Out0 <: Nat]: Aux[L, Out0] = new Length[L] { type Out = Out0 } + + given hnilLength as Aux[HNil, Zero] = instance + given hconsLength[H, T <: HList] (using length: Length[T]) as Aux[HCons[H, T], Succ[length.Out]] = instance // (*) + //given hconsLength[H, T <: HList, N <: Nat] (using length: Aux[T, N]) as Aux[HCons[H, T], Succ[N]] = instance // (**) +} + +val test = summon[Length.Aux[HCons[Int, HNil], One]] \ No newline at end of file From 7f8a4dbd08cc3e3345af21374c3b18bd1fa44fa1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 28 Apr 2020 22:35:31 +0200 Subject: [PATCH 4/4] Fix docs, addressing review comment --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 1f00852dcbde..b62e1bc7f0e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -550,7 +550,7 @@ object ProtoTypes { /** The normalized form of a type * - unwraps polymorphic types, tracking their parameters in the current constraint * - skips implicit parameters of methods and functions; - * if result type depends on implicit parameter, replace with fresh type dependent parameter. + * if result type depends on implicit parameter, replace with wildcard. * - converts non-dependent method types to the corresponding function types * unless the expected type is an ApplyingProto or IgnoredProto. * - dereferences parameterless method types