Skip to content

Commit 704f4d7

Browse files
committed
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.
1 parent 1b2fc1f commit 704f4d7

File tree

6 files changed

+126
-101
lines changed

6 files changed

+126
-101
lines changed

src/dotty/tools/dotc/core/TyperState.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ class TyperState(r: Reporter) extends DotClass with Showable {
5959
/** Commit state so that it gets propagated to enclosing context */
6060
def commit()(implicit ctx: Context): Unit = unsupported("commit")
6161

62+
/** The typer state has already been committed */
63+
def isCommitted: Boolean = false
64+
65+
/** Optionally, if this is a mutable typerstate, it's creator state */
66+
def parent: Option[TyperState] = None
67+
68+
/** The closest ancestor of this typer state (including possible this typer state itself)
69+
* which is not yet committed.
70+
*/
71+
def uncommittedAncestor: TyperState = parent match {
72+
case Some(p) if p.isCommitted => p.uncommittedAncestor
73+
case _ => this
74+
}
75+
6276
/** Make type variable instances permanent by assigning to `inst` field if
6377
* type variable instantiation cannot be retracted anymore. Then, remove
6478
* no-longer needed constraint entries.
@@ -115,6 +129,7 @@ extends TyperState(r) {
115129
*/
116130
override def commit()(implicit ctx: Context) = {
117131
val targetState = ctx.typerState
132+
assert(targetState eq previous)
118133
assert(isCommittable)
119134
targetState.constraint = constraint
120135
constraint foreachTypeVar { tvar =>
@@ -124,8 +139,15 @@ extends TyperState(r) {
124139
targetState.ephemeral = ephemeral
125140
targetState.gc()
126141
reporter.flush()
142+
myIsCommitted = true
127143
}
128144

145+
private var myIsCommitted = false
146+
147+
override def isCommitted: Boolean = myIsCommitted
148+
149+
override def parent = Some(previous)
150+
129151
override def gc()(implicit ctx: Context): Unit = {
130152
val toCollect = new mutable.ListBuffer[GenericType]
131153
constraint foreachTypeVar { tvar =>

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -541,18 +541,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
541541

542542
def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
543543

544-
/** Try same application with an implicit inserted around the qualifier of the function
545-
* part. Return an optional value to indicate success.
546-
*/
547-
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] =
548-
tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 =>
549-
tryEither { implicit ctx =>
550-
Some(typedApply(
551-
cpy.Apply(tree)(untpd.TypedSplice(fun2), proto.typedArgs map untpd.TypedSplice),
552-
pt)): Option[Tree]
553-
} { (_, _) => None }
554-
}
555-
556544
def realApply(implicit ctx: Context): Tree = track("realApply") {
557545
val originalProto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree))
558546
val fun1 = typedExpr(tree.fun, originalProto)
@@ -574,6 +562,32 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
574562
if (!constrainResult(fun1.tpe.widen, proto.derivedFunProto(resultType = pt)))
575563
typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt")
576564

565+
/** Type application where arguments come from prototype, and no implicits are inserted */
566+
def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree =
567+
methPart(fun1).tpe match {
568+
case funRef: TermRef =>
569+
val app =
570+
if (proto.allArgTypesAreCurrent())
571+
new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt)
572+
else
573+
new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree))
574+
convertNewGenericArray(ConstFold(app.result))
575+
case _ =>
576+
handleUnexpectedFunType(tree, fun1)
577+
}
578+
579+
/** Try same application with an implicit inserted around the qualifier of the function
580+
* part. Return an optional value to indicate success.
581+
*/
582+
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] =
583+
tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 =>
584+
tryEither {
585+
implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree]
586+
} {
587+
(_, _) => None
588+
}
589+
}
590+
577591
fun1.tpe match {
578592
case ErrorType => tree.withType(ErrorType)
579593
case TryDynamicCallType =>
@@ -583,23 +597,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
583597
case _ =>
584598
handleUnexpectedFunType(tree, fun1)
585599
}
586-
case _ => methPart(fun1).tpe match {
587-
case funRef: TermRef =>
588-
tryEither { implicit ctx =>
589-
val app =
590-
if (proto.argsAreTyped) new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt)
591-
else new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree))
592-
val result = app.result
593-
convertNewGenericArray(ConstFold(result))
594-
} { (failedVal, failedState) =>
600+
case _ =>
601+
tryEither {
602+
implicit ctx => simpleApply(fun1, proto)
603+
} {
604+
(failedVal, failedState) =>
595605
def fail = { failedState.commit(); failedVal }
596606
tryWithImplicitOnQualifier(fun1, originalProto).getOrElse(
597607
if (proto eq originalProto) fail
598608
else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail))
599-
}
600-
case _ =>
601-
handleUnexpectedFunType(tree, fun1)
602-
}
609+
}
603610
}
604611
}
605612

@@ -611,7 +618,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
611618
*
612619
* { val xs = es; e' = e' + args }
613620
*/
614-
def typedOpAssign: Tree = track("typedOpAssign") {
621+
def typedOpAssign: Tree = track("typedOpAssign") {
615622
val Apply(Select(lhs, name), rhss) = tree
616623
val lhs1 = typedExpr(lhs)
617624
val liftedDefs = new mutable.ListBuffer[Tree]

src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ object ProtoTypes {
172172
/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
173173
private var myTypedArg: SimpleMap[untpd.Tree, Tree] = SimpleMap.Empty
174174

175+
/** A map recording the typer states in which arguments stored in myTypedArg were typed */
176+
private var evalState: SimpleMap[untpd.Tree, TyperState] = SimpleMap.Empty
177+
175178
def isMatchedBy(tp: Type)(implicit ctx: Context) =
176179
typer.isApplicable(tp, Nil, typedArgs, resultType)
177180

@@ -181,27 +184,49 @@ object ProtoTypes {
181184

182185
def argsAreTyped: Boolean = myTypedArgs.size == args.length
183186

187+
private def typedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree)(implicit ctx: Context): Tree = {
188+
var targ = myTypedArg(arg)
189+
if (targ == null) {
190+
targ = typerFn(arg)
191+
if (!ctx.reporter.hasPending) {
192+
myTypedArg = myTypedArg.updated(arg, targ)
193+
evalState = evalState.updated(arg, ctx.typerState)
194+
}
195+
}
196+
targ
197+
}
198+
199+
/** Forget the types of any arguments that have been typed producing a constraint in a
200+
* typer state that is not yet committed into the one of the current context `ctx`.
201+
* This is necessary to avoid "orphan" PolyParams that are referred to from
202+
* type variables in the typed arguments, but that are not registered in the
203+
* current constraint. A test case is pos/t1756.scala.
204+
* @return True if all arguments have types (in particular, no types were forgotten).
205+
*/
206+
def allArgTypesAreCurrent()(implicit ctx: Context): Boolean = {
207+
evalState foreachBinding { (arg, tstate) =>
208+
if (tstate.uncommittedAncestor.constraint ne ctx.typerState.constraint) {
209+
println(i"need to invalidate $arg / ${myTypedArg(arg)}, ${tstate.constraint}, current = ${ctx.typerState.constraint}")
210+
myTypedArg = myTypedArg.remove(arg)
211+
evalState = evalState.remove(arg)
212+
}
213+
}
214+
myTypedArg.size == args.length
215+
}
216+
184217
/** The typed arguments. This takes any arguments already typed using
185218
* `typedArg` into account.
186219
*/
187220
def typedArgs: List[Tree] = {
188-
if (!argsAreTyped)
189-
myTypedArgs = args mapconserve { arg =>
190-
val targ = myTypedArg(arg)
191-
if (targ != null) targ else typer.typed(arg)
192-
}
221+
if (!argsAreTyped) myTypedArgs = args.mapconserve(typedArg(_, typer.typed(_)))
193222
myTypedArgs
194223
}
195224

196225
/** Type single argument and remember the unadapted result in `myTypedArg`.
197226
* used to avoid repeated typings of trees when backtracking.
198227
*/
199228
def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): Tree = {
200-
var targ = myTypedArg(arg)
201-
if (targ == null) {
202-
targ = typer.typedUnadapted(arg, formal)
203-
if (!ctx.reporter.hasPending) myTypedArg = myTypedArg.updated(arg, targ)
204-
}
229+
val targ = typedArg(arg, typer.typedUnadapted(_, formal))
205230
typer.adapt(targ, formal, arg)
206231
}
207232

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,11 +1445,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
14451445
val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt)
14461446
if (sel.tpe.isError) sel else adapt(sel, pt)
14471447
} { (failedTree, failedState) =>
1448-
tryInsertImplicitOnQualifier(tree, pt) match {
1449-
case Some(tree1) => adapt(tree1, pt)
1450-
case none => fallBack(failedTree, failedState)
1451-
}
1452-
}
1448+
tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack(failedTree, failedState))
1449+
}
14531450

14541451
/** If this tree is a select node `qual.name`, try to insert an implicit conversion
14551452
* `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
14621459
tryEither { implicit ctx =>
14631460
val qual1 = adaptInterpolated(qual, qualProto, EmptyTree)
14641461
if ((qual eq qual1) || ctx.reporter.hasErrors) None
1465-
else Some(typedSelect(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt))
1462+
else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt))
14661463
} { (_, _) => None
14671464
}
14681465
case _ => None

tests/pending/pos/t1756.scala

Lines changed: 0 additions & 59 deletions
This file was deleted.

tests/pos/t1756.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
trait Ring[T <: Ring[T]] {
2+
def +(that: T): T
3+
def *(that: T): T
4+
}
5+
6+
class A extends Ring[A] {
7+
def +(that: A) = new A
8+
def *(that: A) = new A
9+
}
10+
11+
class Poly[C <: Ring[C]](val c: C) extends Ring[Poly[C]] {
12+
def +(that: Poly[C]) = new Poly(this.c + that.c)
13+
def *(that: Poly[C]) = new Poly(this.c*that.c)
14+
}
15+
16+
object Test extends App {
17+
18+
implicit def coef2poly[CI <: Ring[CI]](c: CI): Poly[CI] = new Poly(c)
19+
20+
val a = new A
21+
val x = new Poly(new A)
22+
23+
println(x + a) // works
24+
println(a + x) // works
25+
26+
val y = new Poly(new Poly(new A))
27+
28+
println(x + y*x) // works
29+
println(x*y + x) // works
30+
println(y*x + x) // works
31+
32+
println(x + x*y) // failed before, first with type error, after that was fixed with "orphan poly parameter CI".
33+
}

0 commit comments

Comments
 (0)