aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2016-08-21 11:18:42 +0200
committerMartin Odersky <odersky@gmail.com>2016-08-21 15:19:27 +0200
commit704f4d745e2b71b30e44533d38936cdb43813acf (patch)
tree0426d2f410348c673f04af2bb9febfd1bedf7f8c /src/dotty/tools
parent1b2fc1f016f837ca51c7627d359ed88e24692df6 (diff)
downloaddotty-704f4d745e2b71b30e44533d38936cdb43813acf.tar.gz
dotty-704f4d745e2b71b30e44533d38936cdb43813acf.tar.bz2
dotty-704f4d745e2b71b30e44533d38936cdb43813acf.zip
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.
Diffstat (limited to 'src/dotty/tools')
-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
4 files changed, 93 insertions, 42 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