From 1b2fc1f016f837ca51c7627d359ed88e24692df6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Aug 2016 11:07:52 +0200 Subject: [PATCH 1/5] Don't force in isErronous check isErroneous forced LazyRefs through the `existsPart` combinator. This might prompt further errors or infinite recursions, so should be avoided. Seen in the wild when trying to trace t1756.scala with -Ylog:front and typr printer on. --- src/dotty/tools/dotc/core/Types.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 1bfd6eaee727..115995ddc0dc 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -177,7 +177,7 @@ object Types { } /** Is some part of this type produced as a repair for an error? */ - final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError) + final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false) /** Does the type carry an annotation that is an instance of `cls`? */ final def hasAnnotation(cls: ClassSymbol)(implicit ctx: Context): Boolean = stripTypeVar match { @@ -219,8 +219,8 @@ object Types { /** Returns true if there is a part of this type that satisfies predicate `p`. */ - final def existsPart(p: Type => Boolean)(implicit ctx: Context): Boolean = - new ExistsAccumulator(p).apply(false, this) + final def existsPart(p: Type => Boolean, forceLazy: Boolean = true)(implicit ctx: Context): Boolean = + new ExistsAccumulator(p, forceLazy).apply(false, this) /** Returns true if all parts of this type satisfy predicate `p`. */ @@ -3688,9 +3688,10 @@ object Types { protected def traverseChildren(tp: Type) = foldOver((), tp) } - class ExistsAccumulator(p: Type => Boolean)(implicit ctx: Context) extends TypeAccumulator[Boolean] { + class ExistsAccumulator(p: Type => Boolean, forceLazy: Boolean = true)(implicit ctx: Context) extends TypeAccumulator[Boolean] { override def stopAtStatic = false - def apply(x: Boolean, tp: Type) = x || p(tp) || foldOver(x, tp) + def apply(x: Boolean, tp: Type) = + x || p(tp) || (forceLazy || !tp.isInstanceOf[LazyRef]) && foldOver(x, tp) } class ForeachAccumulator(p: Type => Unit)(implicit ctx: Context) extends TypeAccumulator[Unit] { From 704f4d745e2b71b30e44533d38936cdb43813acf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Aug 2016 11:18:42 +0200 Subject: [PATCH 2/5] Make sure arguments are evaluated in the correct typer state. There's a tricky interaction with caching of typed arguments in FunProto types and backtracking using different typer states. We might end up with a typed argument that is evaluated in one typer state and that is used in another. The problem is that the argument typing might have inserted type variables (maybe by adding polymorphic implicit views) that are not registered in the typer state in which the application is finally typed. In that case we will see an "orphan poly parameter" in pickling. The fix is to discard argument types is their typerstate is not committed to the one in which the application is finally typed. To apply the fix we need to track - for typer states: whether or not it was committed, and what its parent is. - for function prototypes: the typer state in which an argument with cached type was evaluated. Test case is t1756.scala, which produced an "orphan poly parameter CI" before. --- src/dotty/tools/dotc/core/TyperState.scala | 22 +++++++ src/dotty/tools/dotc/typer/Applications.scala | 59 +++++++++++-------- src/dotty/tools/dotc/typer/ProtoTypes.scala | 45 ++++++++++---- src/dotty/tools/dotc/typer/Typer.scala | 9 +-- tests/pending/pos/t1756.scala | 59 ------------------- tests/pos/t1756.scala | 33 +++++++++++ 6 files changed, 126 insertions(+), 101 deletions(-) delete mode 100644 tests/pending/pos/t1756.scala create mode 100644 tests/pos/t1756.scala diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index e64335218367..4a1924909d91 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -59,6 +59,20 @@ class TyperState(r: Reporter) extends DotClass with Showable { /** Commit state so that it gets propagated to enclosing context */ def commit()(implicit ctx: Context): Unit = unsupported("commit") + /** The typer state has already been committed */ + def isCommitted: Boolean = false + + /** Optionally, if this is a mutable typerstate, it's creator state */ + def parent: Option[TyperState] = None + + /** The closest ancestor of this typer state (including possible this typer state itself) + * which is not yet committed. + */ + def uncommittedAncestor: TyperState = parent match { + case Some(p) if p.isCommitted => p.uncommittedAncestor + case _ => this + } + /** Make type variable instances permanent by assigning to `inst` field if * type variable instantiation cannot be retracted anymore. Then, remove * no-longer needed constraint entries. @@ -115,6 +129,7 @@ extends TyperState(r) { */ override def commit()(implicit ctx: Context) = { val targetState = ctx.typerState + assert(targetState eq previous) assert(isCommittable) targetState.constraint = constraint constraint foreachTypeVar { tvar => @@ -124,8 +139,15 @@ extends TyperState(r) { targetState.ephemeral = ephemeral targetState.gc() reporter.flush() + myIsCommitted = true } + private var myIsCommitted = false + + override def isCommitted: Boolean = myIsCommitted + + override def parent = Some(previous) + override def gc()(implicit ctx: Context): Unit = { val toCollect = new mutable.ListBuffer[GenericType] constraint foreachTypeVar { tvar => diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index efd12cb5e13f..3916f935f843 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -541,18 +541,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { - /** Try same application with an implicit inserted around the qualifier of the function - * part. Return an optional value to indicate success. - */ - def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = - tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 => - tryEither { implicit ctx => - Some(typedApply( - cpy.Apply(tree)(untpd.TypedSplice(fun2), proto.typedArgs map untpd.TypedSplice), - pt)): Option[Tree] - } { (_, _) => None } - } - def realApply(implicit ctx: Context): Tree = track("realApply") { val originalProto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree)) val fun1 = typedExpr(tree.fun, originalProto) @@ -574,6 +562,32 @@ trait Applications extends Compatibility { self: Typer with Dynamic => if (!constrainResult(fun1.tpe.widen, proto.derivedFunProto(resultType = pt))) typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt") + /** Type application where arguments come from prototype, and no implicits are inserted */ + def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = + methPart(fun1).tpe match { + case funRef: TermRef => + val app = + if (proto.allArgTypesAreCurrent()) + new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt) + else + new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree)) + convertNewGenericArray(ConstFold(app.result)) + case _ => + handleUnexpectedFunType(tree, fun1) + } + + /** Try same application with an implicit inserted around the qualifier of the function + * part. Return an optional value to indicate success. + */ + def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = + tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 => + tryEither { + implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] + } { + (_, _) => None + } + } + fun1.tpe match { case ErrorType => tree.withType(ErrorType) case TryDynamicCallType => @@ -583,23 +597,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case _ => handleUnexpectedFunType(tree, fun1) } - case _ => methPart(fun1).tpe match { - case funRef: TermRef => - tryEither { implicit ctx => - val app = - if (proto.argsAreTyped) new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt) - else new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree)) - val result = app.result - convertNewGenericArray(ConstFold(result)) - } { (failedVal, failedState) => + case _ => + tryEither { + implicit ctx => simpleApply(fun1, proto) + } { + (failedVal, failedState) => def fail = { failedState.commit(); failedVal } tryWithImplicitOnQualifier(fun1, originalProto).getOrElse( if (proto eq originalProto) fail else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) - } - case _ => - handleUnexpectedFunType(tree, fun1) - } + } } } @@ -611,7 +618,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * * { val xs = es; e' = e' + args } */ - def typedOpAssign: Tree = track("typedOpAssign") { + def typedOpAssign: Tree = track("typedOpAssign") { val Apply(Select(lhs, name), rhss) = tree val lhs1 = typedExpr(lhs) val liftedDefs = new mutable.ListBuffer[Tree] diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 767ccbe7d089..4e2134c6a58d 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -172,6 +172,9 @@ object ProtoTypes { /** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */ private var myTypedArg: SimpleMap[untpd.Tree, Tree] = SimpleMap.Empty + /** A map recording the typer states in which arguments stored in myTypedArg were typed */ + private var evalState: SimpleMap[untpd.Tree, TyperState] = SimpleMap.Empty + def isMatchedBy(tp: Type)(implicit ctx: Context) = typer.isApplicable(tp, Nil, typedArgs, resultType) @@ -181,15 +184,41 @@ object ProtoTypes { def argsAreTyped: Boolean = myTypedArgs.size == args.length + private def typedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree)(implicit ctx: Context): Tree = { + var targ = myTypedArg(arg) + if (targ == null) { + targ = typerFn(arg) + if (!ctx.reporter.hasPending) { + myTypedArg = myTypedArg.updated(arg, targ) + evalState = evalState.updated(arg, ctx.typerState) + } + } + targ + } + + /** Forget the types of any arguments that have been typed producing a constraint in a + * typer state that is not yet committed into the one of the current context `ctx`. + * This is necessary to avoid "orphan" PolyParams that are referred to from + * type variables in the typed arguments, but that are not registered in the + * current constraint. A test case is pos/t1756.scala. + * @return True if all arguments have types (in particular, no types were forgotten). + */ + def allArgTypesAreCurrent()(implicit ctx: Context): Boolean = { + evalState foreachBinding { (arg, tstate) => + if (tstate.uncommittedAncestor.constraint ne ctx.typerState.constraint) { + println(i"need to invalidate $arg / ${myTypedArg(arg)}, ${tstate.constraint}, current = ${ctx.typerState.constraint}") + myTypedArg = myTypedArg.remove(arg) + evalState = evalState.remove(arg) + } + } + myTypedArg.size == args.length + } + /** The typed arguments. This takes any arguments already typed using * `typedArg` into account. */ def typedArgs: List[Tree] = { - if (!argsAreTyped) - myTypedArgs = args mapconserve { arg => - val targ = myTypedArg(arg) - if (targ != null) targ else typer.typed(arg) - } + if (!argsAreTyped) myTypedArgs = args.mapconserve(typedArg(_, typer.typed(_))) myTypedArgs } @@ -197,11 +226,7 @@ object ProtoTypes { * used to avoid repeated typings of trees when backtracking. */ def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): Tree = { - var targ = myTypedArg(arg) - if (targ == null) { - targ = typer.typedUnadapted(arg, formal) - if (!ctx.reporter.hasPending) myTypedArg = myTypedArg.updated(arg, targ) - } + val targ = typedArg(arg, typer.typedUnadapted(_, formal)) typer.adapt(targ, formal, arg) } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 34cd5448b138..678e408e4082 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1445,11 +1445,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) if (sel.tpe.isError) sel else adapt(sel, pt) } { (failedTree, failedState) => - tryInsertImplicitOnQualifier(tree, pt) match { - case Some(tree1) => adapt(tree1, pt) - case none => fallBack(failedTree, failedState) - } - } + tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack(failedTree, failedState)) + } /** If this tree is a select node `qual.name`, try to insert an implicit conversion * `c` around `qual` so that `c(qual).name` conforms to `pt`. If that fails @@ -1462,7 +1459,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tryEither { implicit ctx => val qual1 = adaptInterpolated(qual, qualProto, EmptyTree) if ((qual eq qual1) || ctx.reporter.hasErrors) None - else Some(typedSelect(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt)) + else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt)) } { (_, _) => None } case _ => None diff --git a/tests/pending/pos/t1756.scala b/tests/pending/pos/t1756.scala deleted file mode 100644 index 34bf273ab945..000000000000 --- a/tests/pending/pos/t1756.scala +++ /dev/null @@ -1,59 +0,0 @@ - -/** -This is a tricky issue which has to do with the fact that too much conflicting -type information is propagated into a single implicit search, where the intended -solution applies two implicit searches. - -Roughly, in x + x * y, the first x is first typed as Poly[A]. That -means the x * y is then typed as Poly[A]. Then the second x is typed -as Poly[A], then y is typed as Poly[Poly[A]]. The application x * y -fails, so the coef2poly implicit conversion is applied to x. That -means we look for an implicit conversion from type Poly[A] to type -?{val *(x$1: ?>: Poly[Poly[A]] <: Any): Poly[A]}. Note that the result -type Poly[A] is propagated into the implicit search. Poly[A] comes as -expected type from x+, because the lhs x is still typed as a Poly[A]. -This means that the argument of the implicit conversion is typechecked -with expected type A with Poly[A]. And no solution is found. - -To solve this, I added a fallback scheme similar to implicit arguments: -When an implicit view that adds a method matching given arguments and result -type fails, try again without the result type. - -However, troubles are not yet over. We now get an oprhan poly param C when pickling -and, if typr printer and -Ylog:front is on, an infinite type of the form - - mu x. Ring[LazyRef(x) & A] -*/ -trait Ring[T <: Ring[T]] { - def +(that: T): T - def *(that: T): T -} - -class A extends Ring[A] { - def +(that: A) = new A - def *(that: A) = new A -} - -class Poly[C <: Ring[C]](val c: C) extends Ring[Poly[C]] { - def +(that: Poly[C]) = new Poly(this.c + that.c) - def *(that: Poly[C]) = new Poly(this.c*that.c) -} - -object Test extends App { - - implicit def coef2poly[C <: Ring[C]](c: C): Poly[C] = new Poly(c) - - val a = new A - val x = new Poly(new A) - - println(x + a) // works - println(a + x) // works - - val y = new Poly(new Poly(new A)) - - println(x + y*x) // works - println(x*y + x) // works - println(y*x + x) // works - - println(x + x*y) // failed before -} diff --git a/tests/pos/t1756.scala b/tests/pos/t1756.scala new file mode 100644 index 000000000000..767eb54a7c82 --- /dev/null +++ b/tests/pos/t1756.scala @@ -0,0 +1,33 @@ +trait Ring[T <: Ring[T]] { + def +(that: T): T + def *(that: T): T +} + +class A extends Ring[A] { + def +(that: A) = new A + def *(that: A) = new A +} + +class Poly[C <: Ring[C]](val c: C) extends Ring[Poly[C]] { + def +(that: Poly[C]) = new Poly(this.c + that.c) + def *(that: Poly[C]) = new Poly(this.c*that.c) +} + +object Test extends App { + + implicit def coef2poly[CI <: Ring[CI]](c: CI): Poly[CI] = new Poly(c) + + val a = new A + val x = new Poly(new A) + + println(x + a) // works + println(a + x) // works + + val y = new Poly(new Poly(new A)) + + println(x + y*x) // works + println(x*y + x) // works + println(y*x + x) // works + + println(x + x*y) // failed before, first with type error, after that was fixed with "orphan poly parameter CI". +} From b3a169af0b3f33d83bbf6ce15971d2c047b036de Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Aug 2016 12:18:44 +0200 Subject: [PATCH 3/5] Fix comment. --- src/dotty/tools/dotc/typer/Applications.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 3916f935f843..676aafd35030 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -546,7 +546,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val fun1 = typedExpr(tree.fun, originalProto) // Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as - // a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application. + // a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application, + // until, possibly, we have to fall back to insert an implicit on thq qualifier. // This crucially relies on he fact that `proto` is used only in a single call of `adapt`, // otherwise we would get possible cross-talk between different `adapt` calls using the same // prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with From ba663f7b9f2d62f08d73133dab26d978d6a4ecb7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Aug 2016 12:54:22 +0200 Subject: [PATCH 4/5] Address reviewers comments. --- src/dotty/tools/dotc/core/TyperState.scala | 11 +++---- src/dotty/tools/dotc/typer/ProtoTypes.scala | 34 ++++++++++----------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 4a1924909d91..69c35faf50ce 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -65,13 +65,12 @@ class TyperState(r: Reporter) extends DotClass with Showable { /** Optionally, if this is a mutable typerstate, it's creator state */ def parent: Option[TyperState] = None - /** The closest ancestor of this typer state (including possible this typer state itself) - * which is not yet committed. + /** The closest ancestor of this typer state (including possibly this typer state itself) + * which is not yet committed, or which does not have a parent. */ - def uncommittedAncestor: TyperState = parent match { - case Some(p) if p.isCommitted => p.uncommittedAncestor - case _ => this - } + def uncommittedAncestor: TyperState = + if (!isCommitted || !parent.isDefined) this + else parent.get.uncommittedAncestor /** Make type variable instances permanent by assigning to `inst` field if * type variable instantiation cannot be retracted anymore. Then, remove diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4e2134c6a58d..f209c99bee53 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -182,20 +182,6 @@ object ProtoTypes { if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this else new FunProto(args, resultType, typer) - def argsAreTyped: Boolean = myTypedArgs.size == args.length - - private def typedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree)(implicit ctx: Context): Tree = { - var targ = myTypedArg(arg) - if (targ == null) { - targ = typerFn(arg) - if (!ctx.reporter.hasPending) { - myTypedArg = myTypedArg.updated(arg, targ) - evalState = evalState.updated(arg, ctx.typerState) - } - } - targ - } - /** Forget the types of any arguments that have been typed producing a constraint in a * typer state that is not yet committed into the one of the current context `ctx`. * This is necessary to avoid "orphan" PolyParams that are referred to from @@ -206,7 +192,7 @@ object ProtoTypes { def allArgTypesAreCurrent()(implicit ctx: Context): Boolean = { evalState foreachBinding { (arg, tstate) => if (tstate.uncommittedAncestor.constraint ne ctx.typerState.constraint) { - println(i"need to invalidate $arg / ${myTypedArg(arg)}, ${tstate.constraint}, current = ${ctx.typerState.constraint}") + typr.println(i"need to invalidate $arg / ${myTypedArg(arg)}, ${tstate.constraint}, current = ${ctx.typerState.constraint}") myTypedArg = myTypedArg.remove(arg) evalState = evalState.remove(arg) } @@ -214,11 +200,24 @@ object ProtoTypes { myTypedArg.size == args.length } + private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree)(implicit ctx: Context): Tree = { + var targ = myTypedArg(arg) + if (targ == null) { + targ = typerFn(arg) + if (!ctx.reporter.hasPending) { + myTypedArg = myTypedArg.updated(arg, targ) + evalState = evalState.updated(arg, ctx.typerState) + } + } + targ + } + /** The typed arguments. This takes any arguments already typed using * `typedArg` into account. */ def typedArgs: List[Tree] = { - if (!argsAreTyped) myTypedArgs = args.mapconserve(typedArg(_, typer.typed(_))) + if (myTypedArgs.size != args.length) + myTypedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_))) myTypedArgs } @@ -226,7 +225,7 @@ object ProtoTypes { * used to avoid repeated typings of trees when backtracking. */ def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): Tree = { - val targ = typedArg(arg, typer.typedUnadapted(_, formal)) + val targ = cacheTypedArg(arg, typer.typedUnadapted(_, formal)) typer.adapt(targ, formal, arg) } @@ -262,7 +261,6 @@ object ProtoTypes { */ class FunProtoTyped(args: List[tpd.Tree], resultType: Type, typer: Typer)(implicit ctx: Context) extends FunProto(args, resultType, typer)(ctx) { override def typedArgs = args - override def argsAreTyped = true } /** A prototype for implicitly inferred views: From 41b7ca73480e868d830b08db382debf049418973 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Aug 2016 13:20:07 +0200 Subject: [PATCH 5/5] Add comment. --- src/dotty/tools/dotc/typer/Applications.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 676aafd35030..45ed4d93858d 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -547,7 +547,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as // a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application, - // until, possibly, we have to fall back to insert an implicit on thq qualifier. + // until, possibly, we have to fall back to insert an implicit on the qualifier. // This crucially relies on he fact that `proto` is used only in a single call of `adapt`, // otherwise we would get possible cross-talk between different `adapt` calls using the same // prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with @@ -604,6 +604,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } { (failedVal, failedState) => def fail = { failedState.commit(); failedVal } + // Try once with original prototype and once (if different) with tupled one. + // The reason we need to try both is that the decision whether to use tupled + // or not was already taken but might have to be revised when an implicit + // is inserted on the qualifier. tryWithImplicitOnQualifier(fun1, originalProto).getOrElse( if (proto eq originalProto) fail else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail))