diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 91c90923f323..5ae40421add2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1122,33 +1122,17 @@ trait Implicits: adapt(generated, pt.widenExpr, locked) else { def untpdGenerated = untpd.TypedSplice(generated) - def conversionResultType(info: Type): Type = info match - case info: PolyType => conversionResultType(info.resType) - case info: MethodType if info.isImplicitMethod => conversionResultType(info.resType) - case _ => - if info.derivesFrom(defn.ConversionClass) then - pt match - case selProto: SelectionProto => - // we want to avoid embedding a SelectionProto in a Conversion, as the result type - // as it might end up within a GADT cast type, e.g. tests/pos/i15867.scala - // so, if we can find the target result type - as in, - // if it matches the selection prototype, then let's adapt to that instead - // otherwise just skip adapting with a prototype (by returning NoType) - info.baseType(defn.ConversionClass) match - case AppliedType(_, List(_, restpe)) if selProto.isMatchedBy(restpe) => - restpe - case _ => NoType // can't find conversion result type, avoid adapting with SelectionProto - case _: ProtoType => NoType // avoid adapting with ProtoType - case _ => pt // not a ProtoType, so use it for adapting - else NoType // not a Conversion, don't adapt + def producesConversion(info: Type): Boolean = info match + case info: PolyType => producesConversion(info.resType) + case info: MethodType if info.isImplicitMethod => producesConversion(info.resType) + case _ => info.derivesFrom(defn.ConversionClass) def tryConversion(using Context) = { - val restpeConv = if ref.symbol.is(Given) then conversionResultType(ref.widenTermRefExpr) else NoType val untpdConv = - if restpeConv.exists then + if ref.symbol.is(Given) && producesConversion(ref.symbol.info) then untpd.Select( untpd.TypedSplice( adapt(generated, - defn.ConversionClass.typeRef.appliedTo(argument.tpe, restpeConv), + defn.ConversionClass.typeRef.appliedTo(argument.tpe, pt), locked)), nme.apply) else untpdGenerated diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1c00c276376e..a8ba3c31655c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4560,7 +4560,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer AnnotatedType(conj, Annotation(defn.UncheckedStableAnnot, tree.symbol.span)) else conj else pt - gadts.println(i"insert GADT cast from $tree to $target") - tree.cast(target) + if target.existsPart(_.isInstanceOf[ProtoType]) then + // we want to avoid embedding a SelectionProto in a Conversion, as the result type + // as it might end up within a GADT cast type, e.g. tests/pos/i15867.scala + // so we just bail - in that example, a GADT cast will be insert on application, so it compiles. + // but tests/pos/i18062.scala is an example with a polymorphic method, which requires type variables to + // be applied to the tree and then constrained before they match the prototype. + // so rather than try to handle all that before calling adapt, let's just bail on this side. + tree + else + gadts.println(i"insert GADT cast from $tree to $target") + tree.cast(target) end insertGadtCast } diff --git a/tests/pos/i18062.scala b/tests/pos/i18062.scala new file mode 100644 index 000000000000..48863c4349c7 --- /dev/null +++ b/tests/pos/i18062.scala @@ -0,0 +1,14 @@ +trait CB[X] { def get: X } + +trait WrapperConvert[F[_], G[_]]: + def conv[X](fx: F[X]): G[X] + +object WrapperConvert: + implicit def id[F[_]]: WrapperConvert[F, F] = new WrapperConvert[F, F]: + def conv[X](fx: F[X]): F[X] = fx + +transparent inline given convertX[F[_], X](using wc: WrapperConvert[F, CB]): Conversion[F[X], X] = + new Conversion[F[X], X]: + def apply(fx: F[X]) = wc.conv(fx).get + +def test(cb: CB[Int], x: Int): Int = cb + x