diff options
-rw-r--r-- | src/dotty/tools/dotc/core/TyperState.scala | 22 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 59 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/ProtoTypes.scala | 45 | ||||
-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 |
6 files changed, 126 insertions, 101 deletions
diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index e64335218..4a1924909 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 efd12cb5e..3916f935f 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 767ccbe7d..4e2134c6a 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 34cd5448b..678e408e4 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 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". +} |