Skip to content

Commit b300542

Browse files
authored
Merge pull request #1460 from dotty-staging/fix-t1756
Make sure arguments are evaluated in the correct typer state.
2 parents 0e8f05d + 41b7ca7 commit b300542

File tree

7 files changed

+137
-109
lines changed

7 files changed

+137
-109
lines changed

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ 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 possibly this typer state itself)
69+
* which is not yet committed, or which does not have a parent.
70+
*/
71+
def uncommittedAncestor: TyperState =
72+
if (!isCommitted || !parent.isDefined) this
73+
else parent.get.uncommittedAncestor
74+
6275
/** Make type variable instances permanent by assigning to `inst` field if
6376
* type variable instantiation cannot be retracted anymore. Then, remove
6477
* no-longer needed constraint entries.
@@ -115,6 +128,7 @@ extends TyperState(r) {
115128
*/
116129
override def commit()(implicit ctx: Context) = {
117130
val targetState = ctx.typerState
131+
assert(targetState eq previous)
118132
assert(isCommittable)
119133
targetState.constraint = constraint
120134
constraint foreachTypeVar { tvar =>
@@ -124,8 +138,15 @@ extends TyperState(r) {
124138
targetState.ephemeral = ephemeral
125139
targetState.gc()
126140
reporter.flush()
141+
myIsCommitted = true
127142
}
128143

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

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ object Types {
177177
}
178178

179179
/** Is some part of this type produced as a repair for an error? */
180-
final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError)
180+
final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false)
181181

182182
/** Does the type carry an annotation that is an instance of `cls`? */
183183
final def hasAnnotation(cls: ClassSymbol)(implicit ctx: Context): Boolean = stripTypeVar match {
@@ -219,8 +219,8 @@ object Types {
219219

220220
/** Returns true if there is a part of this type that satisfies predicate `p`.
221221
*/
222-
final def existsPart(p: Type => Boolean)(implicit ctx: Context): Boolean =
223-
new ExistsAccumulator(p).apply(false, this)
222+
final def existsPart(p: Type => Boolean, forceLazy: Boolean = true)(implicit ctx: Context): Boolean =
223+
new ExistsAccumulator(p, forceLazy).apply(false, this)
224224

225225
/** Returns true if all parts of this type satisfy predicate `p`.
226226
*/
@@ -3695,9 +3695,10 @@ object Types {
36953695
protected def traverseChildren(tp: Type) = foldOver((), tp)
36963696
}
36973697

3698-
class ExistsAccumulator(p: Type => Boolean)(implicit ctx: Context) extends TypeAccumulator[Boolean] {
3698+
class ExistsAccumulator(p: Type => Boolean, forceLazy: Boolean = true)(implicit ctx: Context) extends TypeAccumulator[Boolean] {
36993699
override def stopAtStatic = false
3700-
def apply(x: Boolean, tp: Type) = x || p(tp) || foldOver(x, tp)
3700+
def apply(x: Boolean, tp: Type) =
3701+
x || p(tp) || (forceLazy || !tp.isInstanceOf[LazyRef]) && foldOver(x, tp)
37013702
}
37023703

37033704
class ForeachAccumulator(p: Type => Unit)(implicit ctx: Context) extends TypeAccumulator[Unit] {

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

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -541,24 +541,13 @@ 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)
559547

560548
// Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as
561-
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application.
549+
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application,
550+
// until, possibly, we have to fall back to insert an implicit on the qualifier.
562551
// This crucially relies on he fact that `proto` is used only in a single call of `adapt`,
563552
// otherwise we would get possible cross-talk between different `adapt` calls using the same
564553
// 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 =>
574563
if (!constrainResult(fun1.tpe.widen, proto.derivedFunProto(resultType = pt)))
575564
typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt")
576565

566+
/** Type application where arguments come from prototype, and no implicits are inserted */
567+
def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree =
568+
methPart(fun1).tpe match {
569+
case funRef: TermRef =>
570+
val app =
571+
if (proto.allArgTypesAreCurrent())
572+
new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt)
573+
else
574+
new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree))
575+
convertNewGenericArray(ConstFold(app.result))
576+
case _ =>
577+
handleUnexpectedFunType(tree, fun1)
578+
}
579+
580+
/** Try same application with an implicit inserted around the qualifier of the function
581+
* part. Return an optional value to indicate success.
582+
*/
583+
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] =
584+
tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 =>
585+
tryEither {
586+
implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree]
587+
} {
588+
(_, _) => None
589+
}
590+
}
591+
577592
fun1.tpe match {
578593
case ErrorType => tree.withType(ErrorType)
579594
case TryDynamicCallType =>
@@ -583,23 +598,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
583598
case _ =>
584599
handleUnexpectedFunType(tree, fun1)
585600
}
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) =>
601+
case _ =>
602+
tryEither {
603+
implicit ctx => simpleApply(fun1, proto)
604+
} {
605+
(failedVal, failedState) =>
595606
def fail = { failedState.commit(); failedVal }
607+
// Try once with original prototype and once (if different) with tupled one.
608+
// The reason we need to try both is that the decision whether to use tupled
609+
// or not was already taken but might have to be revised when an implicit
610+
// is inserted on the qualifier.
596611
tryWithImplicitOnQualifier(fun1, originalProto).getOrElse(
597612
if (proto eq originalProto) fail
598613
else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail))
599-
}
600-
case _ =>
601-
handleUnexpectedFunType(tree, fun1)
602-
}
614+
}
603615
}
604616
}
605617

@@ -611,7 +623,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
611623
*
612624
* { val xs = es; e' = e' + args }
613625
*/
614-
def typedOpAssign: Tree = track("typedOpAssign") {
626+
def typedOpAssign: Tree = track("typedOpAssign") {
615627
val Apply(Select(lhs, name), rhss) = tree
616628
val lhs1 = typedExpr(lhs)
617629
val liftedDefs = new mutable.ListBuffer[Tree]

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -172,36 +172,60 @@ 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

178181
def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer) =
179182
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
180183
else new FunProto(args, resultType, typer)
181184

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

184215
/** The typed arguments. This takes any arguments already typed using
185216
* `typedArg` into account.
186217
*/
187218
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-
}
219+
if (myTypedArgs.size != args.length)
220+
myTypedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_)))
193221
myTypedArgs
194222
}
195223

196224
/** Type single argument and remember the unadapted result in `myTypedArg`.
197225
* used to avoid repeated typings of trees when backtracking.
198226
*/
199227
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-
}
228+
val targ = cacheTypedArg(arg, typer.typedUnadapted(_, formal))
205229
typer.adapt(targ, formal, arg)
206230
}
207231

@@ -237,7 +261,6 @@ object ProtoTypes {
237261
*/
238262
class FunProtoTyped(args: List[tpd.Tree], resultType: Type, typer: Typer)(implicit ctx: Context) extends FunProto(args, resultType, typer)(ctx) {
239263
override def typedArgs = args
240-
override def argsAreTyped = true
241264
}
242265

243266
/** A prototype for implicitly inferred views:

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,11 +1481,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
14811481
val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt)
14821482
if (sel.tpe.isError) sel else adapt(sel, pt)
14831483
} { (failedTree, failedState) =>
1484-
tryInsertImplicitOnQualifier(tree, pt) match {
1485-
case Some(tree1) => adapt(tree1, pt)
1486-
case none => fallBack(failedTree, failedState)
1487-
}
1488-
}
1484+
tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack(failedTree, failedState))
1485+
}
14891486

14901487
/** If this tree is a select node `qual.name`, try to insert an implicit conversion
14911488
* `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
14981495
tryEither { implicit ctx =>
14991496
val qual1 = adaptInterpolated(qual, qualProto, EmptyTree)
15001497
if ((qual eq qual1) || ctx.reporter.hasErrors) None
1501-
else Some(typedSelect(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt))
1498+
else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt))
15021499
} { (_, _) => None
15031500
}
15041501
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)