aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/core/TyperState.scala22
-rw-r--r--src/dotty/tools/dotc/typer/Applications.scala59
-rw-r--r--src/dotty/tools/dotc/typer/ProtoTypes.scala45
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala9
-rw-r--r--tests/pending/pos/t1756.scala59
-rw-r--r--tests/pos/t1756.scala33
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".
+}