diff options
-rw-r--r-- | src/dotty/tools/dotc/core/TyperState.scala | 21 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Types.scala | 11 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 66 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/ProtoTypes.scala | 47 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Typer.scala | 9 | ||||
-rw-r--r-- | tests/pending/pos/t1756.scala | 59 | ||||
-rw-r--r-- | tests/pos/t1756.scala | 33 |
7 files changed, 137 insertions, 109 deletions
diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index e64335218..69c35faf5 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -59,6 +59,19 @@ 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 possibly this typer state itself) + * which is not yet committed, or which does not have a parent. + */ + 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 * no-longer needed constraint entries. @@ -115,6 +128,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 +138,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/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index ad27f924e..87d94dcbe 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`. */ @@ -3695,9 +3695,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] { diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index efd12cb5e..45ed4d938 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -541,24 +541,13 @@ 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) // 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 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 @@ -574,6 +563,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 +598,20 @@ 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 } + // 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)) - } - case _ => - handleUnexpectedFunType(tree, fun1) - } + } } } @@ -611,7 +623,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 767ccbe7d..f209c99be 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) @@ -179,17 +182,42 @@ 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 + /** 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) { + typr.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 + } + + 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 { arg => - val targ = myTypedArg(arg) - if (targ != null) targ else typer.typed(arg) - } + if (myTypedArgs.size != args.length) + myTypedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_))) myTypedArgs } @@ -197,11 +225,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 = cacheTypedArg(arg, typer.typedUnadapted(_, formal)) typer.adapt(targ, formal, arg) } @@ -237,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: diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index c798a4fc1..7eb022b51 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1481,11 +1481,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 @@ -1498,7 +1495,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 34bf273ab..000000000 --- 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 000000000..767eb54a7 --- /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". +} |