aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/core/TyperState.scala21
-rw-r--r--src/dotty/tools/dotc/core/Types.scala11
-rw-r--r--src/dotty/tools/dotc/typer/Applications.scala66
-rw-r--r--src/dotty/tools/dotc/typer/ProtoTypes.scala47
-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
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".
+}