Skip to content

Commit 45ce129

Browse files
authored
Merge pull request #13292 from dotty-staging/fix-merge-constraint-3
Reimplement constraint merging for correctness
2 parents dd2962b + b9d8ca9 commit 45ce129

File tree

11 files changed

+185
-108
lines changed

11 files changed

+185
-108
lines changed

compiler/src/dotty/tools/dotc/core/Constraint.scala

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,6 @@ abstract class Constraint extends Showable {
152152
*/
153153
def uninstVars: collection.Seq[TypeVar]
154154

155-
/** The weakest constraint that subsumes both this constraint and `other`.
156-
* The constraints should be _compatible_, meaning that a type lambda
157-
* occurring in both constraints is associated with the same typevars in each.
158-
*
159-
* @param otherHasErrors If true, handle incompatible constraints by
160-
* returning an approximate constraint, instead of
161-
* failing with an exception
162-
*/
163-
def & (other: Constraint, otherHasErrors: Boolean)(using Context): Constraint
164-
165155
/** Whether `tl` is present in both `this` and `that` but is associated with
166156
* different TypeVars there, meaning that the constraints cannot be merged.
167157
*/
@@ -183,7 +173,4 @@ abstract class Constraint extends Showable {
183173
* of athe type lambda that is associated with the typevar itself.
184174
*/
185175
def checkConsistentVars()(using Context): Unit
186-
187-
/** A string describing the constraint's contents without a header or trailer */
188-
def contentsToString(using Context): String
189176
}

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

Lines changed: 10 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ object OrderingConstraint {
2424
type ParamOrdering = ArrayValuedMap[List[TypeParamRef]]
2525

2626
/** A new constraint with given maps */
27-
private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(using Context) : OrderingConstraint = {
28-
val result = new OrderingConstraint(boundsMap, lowerMap, upperMap)
29-
ctx.run.recordConstraintSize(result, result.boundsMap.size)
30-
result
31-
}
27+
private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(using Context) : OrderingConstraint =
28+
if boundsMap.isEmpty && lowerMap.isEmpty && upperMap.isEmpty then
29+
empty
30+
else
31+
val result = new OrderingConstraint(boundsMap, lowerMap, upperMap)
32+
ctx.run.recordConstraintSize(result, result.boundsMap.size)
33+
result
3234

3335
/** A lens for updating a single entry array in one of the three constraint maps */
3436
abstract class ConstraintLens[T <: AnyRef: ClassTag] {
@@ -457,48 +459,6 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
457459

458460
// ----------- Joins -----------------------------------------------------
459461

460-
def & (other: Constraint, otherHasErrors: Boolean)(using Context): OrderingConstraint = {
461-
462-
def merge[T](m1: ArrayValuedMap[T], m2: ArrayValuedMap[T], join: (T, T) => T): ArrayValuedMap[T] = {
463-
var merged = m1
464-
def mergeArrays(xs1: Array[T], xs2: Array[T]) = {
465-
val xs = xs1.clone
466-
for (i <- xs.indices) xs(i) = join(xs1(i), xs2(i))
467-
xs
468-
}
469-
m2.foreachBinding { (poly, xs2) =>
470-
merged = merged.updated(poly,
471-
if (m1.contains(poly)) mergeArrays(m1(poly), xs2) else xs2)
472-
}
473-
merged
474-
}
475-
476-
def mergeParams(ps1: List[TypeParamRef], ps2: List[TypeParamRef]) =
477-
ps2.foldLeft(ps1)((ps1, p2) => if (ps1.contains(p2)) ps1 else p2 :: ps1)
478-
479-
// Must be symmetric
480-
def mergeEntries(e1: Type, e2: Type): Type =
481-
(e1, e2) match {
482-
case _ if e1 eq e2 => e1
483-
case (e1: TypeBounds, e2: TypeBounds) => e1 & e2
484-
case (e1: TypeBounds, _) if e1 contains e2 => e2
485-
case (_, e2: TypeBounds) if e2 contains e1 => e1
486-
case (tv1: TypeVar, tv2: TypeVar) if tv1 eq tv2 => e1
487-
case _ =>
488-
if (otherHasErrors)
489-
e1
490-
else
491-
throw new AssertionError(i"cannot merge $this with $other, mergeEntries($e1, $e2) failed")
492-
}
493-
494-
val that = other.asInstanceOf[OrderingConstraint]
495-
496-
new OrderingConstraint(
497-
merge(this.boundsMap, that.boundsMap, mergeEntries),
498-
merge(this.lowerMap, that.lowerMap, mergeParams),
499-
merge(this.upperMap, that.upperMap, mergeParams))
500-
}.showing(i"constraint merge $this with $other = $result", constr)
501-
502462
def hasConflictingTypeVarsFor(tl: TypeLambda, that: Constraint): Boolean =
503463
contains(tl) && that.contains(tl) &&
504464
// Since TypeVars are allocated in bulk for each type lambda, we only have
@@ -641,49 +601,10 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
641601
upperMap.foreachBinding((_, paramss) => paramss.foreach(_.foreach(checkClosedType(_, "upper"))))
642602
end checkClosed
643603

644-
// ---------- toText -----------------------------------------------------
645-
646-
private def contentsToText(printer: Printer): Text =
647-
//Printer.debugPrintUnique = true
648-
def entryText(tp: Type) = tp match {
649-
case tp: TypeBounds =>
650-
tp.toText(printer)
651-
case _ =>
652-
" := " ~ tp.toText(printer)
653-
}
654-
val indent = 3
655-
val uninstVarsText = " uninstantiated variables: " ~
656-
Text(uninstVars.map(_.toText(printer)), ", ")
657-
val constrainedText =
658-
" constrained types: " ~ Text(domainLambdas map (_.toText(printer)), ", ")
659-
val boundsText =
660-
" bounds: " ~ {
661-
val assocs =
662-
for (param <- domainParams)
663-
yield (" " * indent) ~ param.toText(printer) ~ entryText(entry(param))
664-
Text(assocs, "\n")
665-
}
666-
val orderingText =
667-
" ordering: " ~ {
668-
val deps =
669-
for {
670-
param <- domainParams
671-
ups = minUpper(param)
672-
if ups.nonEmpty
673-
}
674-
yield
675-
(" " * indent) ~ param.toText(printer) ~ " <: " ~
676-
Text(ups.map(_.toText(printer)), ", ")
677-
Text(deps, "\n")
678-
}
679-
//Printer.debugPrintUnique = false
680-
Text.lines(List(uninstVarsText, constrainedText, boundsText, orderingText))
604+
// ---------- Printing -----------------------------------------------------
681605

682606
override def toText(printer: Printer): Text =
683-
Text.lines(List("Constraint(", contentsToText(printer), ")"))
684-
685-
def contentsToString(using Context): String =
686-
contentsToText(ctx.printer).show
607+
printer.toText(this)
687608

688609
override def toString: String = {
689610
def entryText(tp: Type): String = tp match {
@@ -692,7 +613,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
692613
}
693614
val constrainedText =
694615
" constrained types = " + domainLambdas.mkString("\n")
695-
val boundsText = domainLambdas
616+
val boundsText =
696617
" bounds = " + {
697618
val assocs =
698619
for (param <- domainParams)

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

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,43 @@ class TyperState() {
187187
*/
188188
def mergeConstraintWith(that: TyperState)(using Context): Unit =
189189
that.ensureNotConflicting(constraint)
190-
constraint = constraint & (that.constraint, otherHasErrors = that.reporter.errorsReported)
191-
for tvar <- constraint.uninstVars do
192-
if !isOwnedAnywhere(this, tvar) then includeVar(tvar)
190+
191+
val comparingCtx =
192+
if ctx.typerState == this then ctx
193+
else ctx.fresh.setTyperState(this)
194+
195+
comparing(typeComparer =>
196+
val other = that.constraint
197+
val res = other.domainLambdas.forall(tl =>
198+
// Integrate the type lambdas from `other`
199+
constraint.contains(tl) || other.isRemovable(tl) || {
200+
val tvars = tl.paramRefs.map(other.typeVarOfParam(_)).collect { case tv: TypeVar => tv }
201+
tvars.foreach(tvar => if !tvar.inst.exists && !isOwnedAnywhere(this, tvar) then includeVar(tvar))
202+
typeComparer.addToConstraint(tl, tvars)
203+
}) &&
204+
// Integrate the additional constraints on type variables from `other`
205+
constraint.uninstVars.forall(tv =>
206+
val p = tv.origin
207+
val otherLos = other.lower(p)
208+
val otherHis = other.upper(p)
209+
val otherEntry = other.entry(p)
210+
( (otherLos eq constraint.lower(p)) || otherLos.forall(_ <:< p)) &&
211+
( (otherHis eq constraint.upper(p)) || otherHis.forall(p <:< _)) &&
212+
((otherEntry eq constraint.entry(p)) || otherEntry.match
213+
case NoType =>
214+
true
215+
case tp: TypeBounds =>
216+
tp.contains(tv)
217+
case tp =>
218+
tv =:= tp
219+
)
220+
)
221+
assert(res || ctx.reporter.errorsReported, i"cannot merge $constraint with $other.")
222+
)(using comparingCtx)
223+
193224
for tl <- constraint.domainLambdas do
194225
if constraint.isRemovable(tl) then constraint = constraint.remove(tl)
226+
end mergeConstraintWith
195227

196228
/** Take ownership of `tvar`.
197229
*

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,47 @@ class PlainPrinter(_ctx: Context) extends Printer {
598598
case _ => "{...}"
599599
s"import $exprStr.$selectorStr"
600600

601+
def toText(c: OrderingConstraint): Text =
602+
val savedConstraint = ctx.typerState.constraint
603+
try
604+
// The current TyperState constraint determines how type variables are printed
605+
ctx.typerState.constraint = c
606+
def entryText(tp: Type) = tp match {
607+
case tp: TypeBounds =>
608+
toText(tp)
609+
case _ =>
610+
" := " ~ toText(tp)
611+
}
612+
val indent = 3
613+
val uninstVarsText = " uninstantiated variables: " ~
614+
Text(c.uninstVars.map(toText), ", ")
615+
val constrainedText =
616+
" constrained types: " ~ Text(c.domainLambdas.map(toText), ", ")
617+
val boundsText =
618+
" bounds: " ~ {
619+
val assocs =
620+
for (param <- c.domainParams)
621+
yield (" " * indent) ~ toText(param) ~ entryText(c.entry(param))
622+
Text(assocs, "\n")
623+
}
624+
val orderingText =
625+
" ordering: " ~ {
626+
val deps =
627+
for {
628+
param <- c.domainParams
629+
ups = c.minUpper(param)
630+
if ups.nonEmpty
631+
}
632+
yield
633+
(" " * indent) ~ toText(param) ~ " <: " ~
634+
Text(ups.map(toText), ", ")
635+
Text(deps, "\n")
636+
}
637+
//Printer.debugPrintUnique = false
638+
Text.lines(List(uninstVarsText, constrainedText, boundsText, orderingText))
639+
finally
640+
ctx.typerState.constraint = savedConstraint
641+
601642
def plain: PlainPrinter = this
602643

603644
protected def keywordStr(text: String): String = coloredStr(text, SyntaxHighlighting.KeywordColor)

compiler/src/dotty/tools/dotc/printing/Printer.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ abstract class Printer {
154154
/** Textual representation of info relating to an import clause */
155155
def toText(result: ImportInfo): Text
156156

157+
/** Textual representation of a constraint */
158+
def toText(c: OrderingConstraint): Text
159+
157160
/** Render element within highest precedence */
158161
def toTextLocal(elem: Showable): Text =
159162
atPrec(DotPrec) { elem.toText(this) }

compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ object ErrorReporting {
149149
"the empty constraint"
150150
else
151151
i"""a constraint with:
152-
|${c.contentsToString}"""
152+
|$c"""
153153
i"""
154154
|${TypeComparer.explained(_.isSubType(found, expected), header)}
155155
|

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ object ProtoTypes {
376376
def typedArgs(norm: (untpd.Tree, Int) => untpd.Tree = sameTree)(using Context): List[Tree] =
377377
if state.typedArgs.size == args.length then state.typedArgs
378378
else
379+
val passedCtx = ctx
379380
val passedTyperState = ctx.typerState
380381
inContext(protoCtx.withUncommittedTyperState) {
381382
val protoTyperState = ctx.typerState
@@ -409,8 +410,7 @@ object ProtoTypes {
409410
tvar.instantiate(fromBelow = false)
410411
case _ =>
411412
}
412-
413-
passedTyperState.mergeConstraintWith(protoTyperState)
413+
passedTyperState.mergeConstraintWith(protoTyperState)(using passedCtx)
414414
end if
415415
args1
416416
}

compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import collection.mutable.ListBuffer
66
* It has linear complexity for `apply`, `updated`, and `remove`.
77
*/
88
abstract class SimpleIdentityMap[K <: AnyRef, +V >: Null <: AnyRef] extends (K => V) {
9+
final def isEmpty: Boolean = this eq SimpleIdentityMap.myEmpty
910
def size: Int
1011
def apply(k: K): V
1112
def remove(k: K): SimpleIdentityMap[K, V]

compiler/src/dotty/tools/dotc/util/Stats.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import collection.mutable
6262
aggregate()
6363
println()
6464
println(hits.toList.sortBy(_._2).map{ case (x, y) => s"$x -> $y" } mkString "\n")
65+
hits.clear()
6566
}
6667
}
6768
else op
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package dotty.tools
2+
package dotc.core
3+
4+
import vulpix.TestConfiguration
5+
6+
import dotty.tools.dotc.core.Contexts.{*, given}
7+
import dotty.tools.dotc.core.Decorators.{*, given}
8+
import dotty.tools.dotc.core.Symbols.*
9+
import dotty.tools.dotc.core.Types.*
10+
import dotty.tools.dotc.typer.ProtoTypes.constrained
11+
12+
import org.junit.Test
13+
14+
import dotty.tools.DottyTest
15+
16+
class ConstraintsTest:
17+
18+
@Test def mergeParamsTransitivity: Unit =
19+
inCompilerContext(TestConfiguration.basicClasspath,
20+
scalaSources = "trait A { def foo[S, T, R]: Any }") {
21+
val tp = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda])
22+
val List(s, t, r) = tp.paramRefs
23+
24+
val innerCtx = ctx.fresh.setExploreTyperState()
25+
inContext(innerCtx) {
26+
s <:< t
27+
}
28+
29+
t <:< r
30+
31+
ctx.typerState.mergeConstraintWith(innerCtx.typerState)
32+
assert(s frozen_<:< r,
33+
i"Merging constraints `?S <: ?T` and `?T <: ?R` should result in `?S <:< ?R`: ${ctx.typerState.constraint}")
34+
}
35+
end mergeParamsTransitivity
36+
37+
@Test def mergeBoundsTransitivity: Unit =
38+
inCompilerContext(TestConfiguration.basicClasspath,
39+
scalaSources = "trait A { def foo[S, T]: Any }") {
40+
val tp = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda])
41+
val List(s, t) = tp.paramRefs
42+
43+
val innerCtx = ctx.fresh.setExploreTyperState()
44+
inContext(innerCtx) {
45+
s <:< t
46+
}
47+
48+
defn.IntType <:< s
49+
50+
ctx.typerState.mergeConstraintWith(innerCtx.typerState)
51+
assert(defn.IntType frozen_<:< t,
52+
i"Merging constraints `?S <: ?T` and `Int <: ?S` should result in `Int <:< ?T`: ${ctx.typerState.constraint}")
53+
}
54+
end mergeBoundsTransitivity

tests/pos/i12730.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
class ComponentSimple
2+
3+
class Props {
4+
def apply(props: Any): Any = ???
5+
}
6+
7+
class Foo[C] {
8+
def build: ComponentSimple = ???
9+
}
10+
11+
class Bar[E] {
12+
def render(r: E => Any): Unit = {}
13+
}
14+
15+
trait Conv[A, B] {
16+
def apply(a: A): B
17+
}
18+
19+
object Test {
20+
def toComponentCtor[F](c: ComponentSimple): Props = ???
21+
22+
def defaultToNoBackend[G, H](ev: G => Foo[H]): Conv[Foo[H], Bar[H]] = ???
23+
24+
def conforms[A]: A => A = ???
25+
26+
def problem = Main // crashes
27+
28+
def foo[H]: Foo[H] = ???
29+
30+
val NameChanger =
31+
foo
32+
.build
33+
34+
val Main =
35+
defaultToNoBackend(conforms).apply(foo)
36+
.render(_ => toComponentCtor(NameChanger)(13))
37+
}

0 commit comments

Comments
 (0)