From 35d425d18cd18c61a1ceaee17216b5c2ae97a45e Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sun, 21 Jul 2019 10:54:46 -0400 Subject: [PATCH] Fix #6900: Support chained polymorphic extension methods Given a tree `qual.foo`, when we typecheck `qual` we now that the result must have a member named `foo`, if it doesn't we'll try to adapt it through implicit conversion / extension method selection to something which does have such a member. Given a tree `qual.foo.bar`, the same process happens, but when we perform the conversion on `qual` we actually now more about the member `foo`: it must have a member named `bar`. However in general we cannot use this information: this member might actually require another conversion and as a rule we don't chain conversions. Therefore, we need to ignore the expected type of `foo`, this is implemented by `ProtoTypes#selectionProto` wrapping the expected member type in `IgnoredProto`. However, the following failed before this commit: given bla[A] { def (a: A) foo[C]: C => A = _ => a } 1.foo[String].foo[String] This happens because when `Applications#extMethodApply` extracts the type arguments of the extension method from its expected type, it might end up unpealing an `IgnoredProto`. The solution is to rewrap the new expeted type into an `IgnoredProto` if the original was ignored. --- .../dotty/tools/dotc/typer/Applications.scala | 18 ++++++++++++++---- tests/pos/i6900.scala | 8 ++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i6900.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 460aa8d2ecc3..20274e8f087d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1837,13 +1837,23 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * () or * []() * - * where comes from `pt` if it is a PolyProto. + * where comes from `pt` if it is a (possibly ignored) PolyProto. */ def extMethodApply(methodRef: untpd.Tree, receiver: Tree, pt: Type)(implicit ctx: Context) = { - val (core, pt1) = pt.revealIgnored match { - case PolyProto(targs, restpe) => (untpd.TypeApply(methodRef, targs.map(untpd.TypedSplice(_))), restpe) - case _ => (methodRef, pt) + /** Integrate the type arguments from `currentPt` into `methodRef`, and produce + * a matching expected type. + * If `currentPt` is ignored, the new expected type will be ignored too. + */ + def integrateTypeArgs(currentPt: Type, wasIgnored: Boolean = false): (untpd.Tree, Type) = currentPt match { + case IgnoredProto(ignored) => + integrateTypeArgs(ignored, wasIgnored = true) + case PolyProto(targs, restpe) => + val core = untpd.TypeApply(methodRef, targs.map(untpd.TypedSplice(_))) + (core, if (wasIgnored) IgnoredProto(restpe) else restpe) + case _ => + (methodRef, pt) } + val (core, pt1) = integrateTypeArgs(pt) val app = typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)( ctx.addMode(Mode.SynthesizeExtMethodReceiver)) diff --git a/tests/pos/i6900.scala b/tests/pos/i6900.scala new file mode 100644 index 000000000000..df6d308ee0e1 --- /dev/null +++ b/tests/pos/i6900.scala @@ -0,0 +1,8 @@ +object Test { + given bla[A] { def (a: A) foo[C]: C => A = _ => a } + + 1.foo.foo + 1.foo.foo[String] + 1.foo[String].foo + 1.foo[String].foo[String] +}