Skip to content

Commit 17776df

Browse files
authored
Track type variable dependencies to guide instantiation decisions (#16042)
We now keep track of reverse type variable dependencies in constraints. E.g. if a constraint contains a clause like A >: List[B] We associate with `B` info that `A` depends co-variantly on it. Or, if A <: B => C we associate with `B` that `A` depends co-variantly on it and with `C` that `A` depends contra-variantly on it. Here, co-variant means that the allowable range of `A` narrows if the referred-to variable `B` grows, and contra-variant means that the allowable range of `A` narrows if the referred-to variable `C` shrinks. If there's an invariant reference such as A <: Array[B] Then `A` depends both co- and contra-variantly on `B`. These dependencies are then used to guide type variable instantiation. If an eligible type variable does not appear in the type of a typed expression, we interpolate it to one of its bounds. Previously this was done in an ad-hoc manner where we minimized the type variable if it had a lower bound and maximized it otherwise. We now take reverse dependencies into account. If maximizing a type variable would narrow the remaining constraint we minimize, and if minimizing a type variable would narrow the remaining constraint we maximize. Only if the type variable is not referred to from the remaining constraint we resort to the old heuristic based on the lower bound. Fixes #15864 Todo: This could be generalized in several directions: - We could base the dependency tracking on type param refs instead of type variables. That could make the `replace` operation in a constraint more efficient. - We could base more interpolation decisions on dependencies. E.g. we could interpolate a type variable only if both the type of an expression and the enclosing constraint agree in which direction this should be done.
2 parents eab19e3 + 145d2ba commit 17776df

File tree

11 files changed

+581
-158
lines changed

11 files changed

+581
-158
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ object Config {
2222
*/
2323
inline val checkConstraintsNonCyclic = false
2424

25+
/** Check that reverse dependencies in constraints are correct and complete.
26+
* Can also be enabled using -Ycheck-constraint-deps.
27+
*/
28+
inline val checkConstraintDeps = false
29+
2530
/** Check that each constraint resulting from a subtype test
2631
* is satisfiable. Also check that a type variable instantiation
2732
* satisfies its constraints.
@@ -184,6 +189,9 @@ object Config {
184189
/** If set, prints a trace of all symbol completions */
185190
inline val showCompletions = false
186191

192+
/** If set, show variable/variable reverse dependencies when printing constraints. */
193+
inline val showConstraintDeps = true
194+
187195
/** If set, method results that are context functions are flattened by adding
188196
* the parameters of the context function results to the methods themselves.
189197
* This is an optimization that reduces closure allocations.

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ private sealed trait YSettings:
309309
val YforceSbtPhases: Setting[Boolean] = BooleanSetting("-Yforce-sbt-phases", "Run the phases used by sbt for incremental compilation (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of sbt, for debugging.")
310310
val YdumpSbtInc: Setting[Boolean] = BooleanSetting("-Ydump-sbt-inc", "For every compiled foo.scala, output the API representation and dependencies used for sbt incremental compilation in foo.inc, implies -Yforce-sbt-phases.")
311311
val YcheckAllPatmat: Setting[Boolean] = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm).")
312+
val YcheckConstraintDeps: Setting[Boolean] = BooleanSetting("-Ycheck-constraint-deps", "Check dependency tracking in constraints (used for testing the algorithm).")
312313
val YretainTrees: Setting[Boolean] = BooleanSetting("-Yretain-trees", "Retain trees for top-level classes, accessible from ClassSymbol#tree")
313314
val YshowTreeIds: Setting[Boolean] = BooleanSetting("-Yshow-tree-ids", "Uniquely tag all tree nodes in debugging output.")
314315
val YfromTastyIgnoreList: Setting[List[String]] = MultiStringSetting("-Yfrom-tasty-ignore-list", "file", "List of `tasty` files in jar files that will not be loaded when using -from-tasty")

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package core
44

55
import Types._, Contexts._
66
import printing.Showable
7+
import util.{SimpleIdentitySet, SimpleIdentityMap}
78

89
/** Constraint over undetermined type parameters. Constraints are built
910
* over values of the following types:
@@ -128,7 +129,7 @@ abstract class Constraint extends Showable {
128129

129130
/** Is `tv` marked as hard in the constraint? */
130131
def isHard(tv: TypeVar): Boolean
131-
132+
132133
/** The same as this constraint, but with `tv` marked as hard. */
133134
def withHard(tv: TypeVar)(using Context): This
134135

@@ -165,15 +166,32 @@ abstract class Constraint extends Showable {
165166
*/
166167
def hasConflictingTypeVarsFor(tl: TypeLambda, that: Constraint): Boolean
167168

168-
/** Check that no constrained parameter contains itself as a bound */
169-
def checkNonCyclic()(using Context): this.type
170-
171169
/** Does `param` occur at the toplevel in `tp` ?
172170
* Toplevel means: the type itself or a factor in some
173171
* combination of `&` or `|` types.
174172
*/
175173
def occursAtToplevel(param: TypeParamRef, tp: Type)(using Context): Boolean
176174

175+
/** A string that shows the reverse dependencies maintained by this constraint
176+
* (coDeps and contraDeps for OrderingConstraints).
177+
*/
178+
def depsToString(using Context): String
179+
180+
/** Does the constraint restricted to variables outside `except` depend on `tv`
181+
* in the given direction `co`?
182+
* @param `co` If true, test whether the constraint would change if the variable is made larger
183+
* otherwise, test whether the constraint would change if the variable is made smaller.
184+
*/
185+
def dependsOn(tv: TypeVar, except: TypeVars, co: Boolean)(using Context): Boolean
186+
187+
/** Depending on Config settngs:
188+
* - Under `checkConstraintsNonCyclic`, check that no constrained
189+
* parameter contains itself as a bound.
190+
* - Under `checkConstraintDeps`, check hat reverse dependencies in
191+
* constraints are correct and complete.
192+
*/
193+
def checkWellFormed()(using Context): this.type
194+
177195
/** Check that constraint only refers to TypeParamRefs bound by itself */
178196
def checkClosed()(using Context): Unit
179197

0 commit comments

Comments
 (0)