Skip to content

Commit af7cf11

Browse files
committed
Merge pull request scala#3533 from adriaanm/t8283
SI-8283 mutation-free bound inference for existentials
2 parents e86675f + 2a1b15e commit af7cf11

File tree

4 files changed

+84
-41
lines changed

4 files changed

+84
-41
lines changed

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
384384
/** Create a new existential type skolem with this symbol its owner,
385385
* based on the given symbol and origin.
386386
*/
387-
def newExistentialSkolem(basis: Symbol, origin: AnyRef): TypeSkolem = {
388-
val skolem = newTypeSkolemSymbol(basis.name.toTypeName, origin, basis.pos, (basis.flags | EXISTENTIAL) & ~PARAM)
389-
skolem setInfo (basis.info cloneInfo skolem)
387+
def newExistentialSkolem(basis: Symbol, origin: AnyRef): TypeSkolem =
388+
newExistentialSkolem(basis.name.toTypeName, basis.info, basis.flags, basis.pos, origin)
389+
390+
/** Create a new existential type skolem with this symbol its owner, and the given other properties.
391+
*/
392+
def newExistentialSkolem(name: TypeName, info: Type, flags: Long, pos: Position, origin: AnyRef): TypeSkolem = {
393+
val skolem = newTypeSkolemSymbol(name.toTypeName, origin, pos, (flags | EXISTENTIAL) & ~PARAM)
394+
skolem setInfo (info cloneInfo skolem)
390395
}
391396

392397
// don't test directly -- use isGADTSkolem
@@ -3419,6 +3424,21 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
34193424
mapList(syms1)(_ substInfo (syms, syms1))
34203425
}
34213426

3427+
/** Derives a new list of symbols from the given list by mapping the given
3428+
* list of `syms` and `as` across the given function.
3429+
* Then fixes the info of all the new symbols
3430+
* by substituting the new symbols for the original symbols.
3431+
*
3432+
* @param syms the prototypical symbols
3433+
* @param as arguments to be passed to symFn together with symbols from syms (must be same length)
3434+
* @param symFn the function to create new symbols
3435+
* @return the new list of info-adjusted symbols
3436+
*/
3437+
def deriveSymbols2[A](syms: List[Symbol], as: List[A], symFn: (Symbol, A) => Symbol): List[Symbol] = {
3438+
val syms1 = map2(syms, as)(symFn)
3439+
mapList(syms1)(_ substInfo (syms, syms1))
3440+
}
3441+
34223442
/** Derives a new Type by first deriving new symbols as in deriveSymbols,
34233443
* then performing the same oldSyms => newSyms substitution on `tpe` as is
34243444
* performed on the symbol infos in deriveSymbols.
@@ -3432,6 +3452,22 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
34323452
val syms1 = deriveSymbols(syms, symFn)
34333453
tpe.substSym(syms, syms1)
34343454
}
3455+
3456+
/** Derives a new Type by first deriving new symbols as in deriveSymbols2,
3457+
* then performing the same oldSyms => newSyms substitution on `tpe` as is
3458+
* performed on the symbol infos in deriveSymbols.
3459+
*
3460+
* @param syms the prototypical symbols
3461+
* @param as arguments to be passed to symFn together with symbols from syms (must be same length)
3462+
* @param symFn the function to create new symbols based on `as`
3463+
* @param tpe the prototypical type
3464+
* @return the new symbol-subsituted type
3465+
*/
3466+
def deriveType2[A](syms: List[Symbol], as: List[A], symFn: (Symbol, A) => Symbol)(tpe: Type): Type = {
3467+
val syms1 = deriveSymbols2(syms, as, symFn)
3468+
tpe.substSym(syms, syms1)
3469+
}
3470+
34353471
/** Derives a new Type by instantiating the given list of symbols as
34363472
* WildcardTypes.
34373473
*

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ trait Types
9393

9494
private final val traceTypeVars = sys.props contains "scalac.debug.tvar"
9595
private final val breakCycles = settings.breakCycles.value
96-
/** In case anyone wants to turn off type parameter bounds being used
96+
/** In case anyone wants to turn on type parameter bounds being used
9797
* to seed type constraints.
9898
*/
9999
private final val propagateParameterBoundsToTypeVars = sys.props contains "scalac.debug.prop-constraints"
100+
private final val sharperSkolems = sys.props contains "scalac.experimental.sharper-skolems"
100101

101102
protected val enableTypeVarExperimentals = settings.Xexperimental.value
102103

@@ -2551,56 +2552,58 @@ trait Types
25512552
override def baseTypeSeq = underlying.baseTypeSeq map maybeRewrap
25522553
override def isHigherKinded = false
25532554

2555+
// TODO: check invariant that all quantifiers have the same (existing) owner
2556+
private def quantifierOwner = quantified collectFirst { case q if q.owner.exists => q.owner } getOrElse NoSymbol
2557+
2558+
// Is this existential of the form: T[Q1, ..., QN] forSome { type Q1 >: L1 <: U1, ..., QN >: LN <: UN}
2559+
private def isStraightApplication = (quantified corresponds underlying.typeArgs){ (q, a) => q.tpe =:= a }
2560+
25542561
/** [SI-6169, SI-8197 -- companion to SI-1786]
25552562
*
2556-
* Approximation to improve the bounds of a Java-defined existential type,
2557-
* based on the bounds of the type parameters of the quantified type
2558-
* In Scala syntax, given a java-defined class C[T <: String], the existential type C[_]
2559-
* is improved to C[_ <: String] before skolemization, which captures (get it?) what Java does:
2560-
* enter the type paramers' bounds into the context when checking subtyping/type equality of existential types
2563+
* Approximation to improve the bounds of a Java-defined existential type,
2564+
* based on the bounds of the type parameters of the quantified type
2565+
* In Scala syntax, given a java-defined class C[T <: String], the existential type C[_]
2566+
* is improved to C[_ <: String] before skolemization, which captures (get it?) what Java does:
2567+
* enter the type paramers' bounds into the context when checking subtyping/type equality of existential types
25612568
*
2562-
* (Also tried doing this once during class file parsing or when creating the existential type,
2563-
* but that causes cyclic errors because it happens too early.)
2569+
* Also tried doing this once during class file parsing or when creating the existential type,
2570+
* but that causes cyclic errors because it happens too early.
2571+
*
2572+
* NOTE: we're only modifying the skolems to avoid leaking the sharper bounds to `quantified` (SI-8283)
25642573
*
25652574
* TODO: figure out how to do this earlier without running into cycles, so this can subsume the fix for SI-1786
25662575
*/
2567-
private def sharpenQuantifierBounds(): Unit = {
2568-
/* Check that we're looking at rawToExistential's handiwork
2569-
* (`existentialAbstraction(eparams, typeRef(apply(pre), sym, eparams map (_.tpe)))`).
2570-
* We can't do this sharpening there because we'll run into cycles.
2571-
*/
2572-
def rawToExistentialCreatedMe = (quantified corresponds underlying.typeArgs){ (q, a) => q.tpe =:= a }
2573-
2574-
if (underlying.typeSymbol.isJavaDefined && rawToExistentialCreatedMe) {
2575-
val tpars = underlying.typeSymbol.initialize.typeParams // TODO: is initialize needed?
2576-
debuglog(s"sharpen bounds: $this | ${underlying.typeArgs.map(_.typeSymbol)} <-- ${tpars.map(_.info)}")
2577-
2578-
foreach2(quantified, tpars) { (quant, tparam) =>
2579-
// TODO: check `tparam.info.substSym(tpars, quantified) <:< quant.info` instead (for some weird reason not working for test/t6169/ExistF)
2580-
// for now, crude approximation for the common case
2581-
if (quant.info.bounds.isEmptyBounds && !tparam.info.bounds.isEmptyBounds) {
2582-
// avoid creating cycles [pos/t2940] that consist of an existential quantifier's
2583-
// bounded by an existential type that unhygienically has that quantifier as its own quantifier
2584-
// (TODO: clone latter existential with fresh quantifiers -- not covering this case for now)
2585-
if ((existentialsInType(tparam.info) intersect quantified).isEmpty)
2586-
quant setInfo tparam.info.substSym(tpars, quantified)
2587-
}
2588-
}
2589-
}
2576+
override def skolemizeExistential(owner0: Symbol, origin: AnyRef) = {
2577+
val owner = owner0 orElse quantifierOwner
25902578

2591-
_sharpenQuantifierBounds = false
2592-
}
2593-
private[this] var _sharpenQuantifierBounds = true
2594-
2595-
override def skolemizeExistential(owner: Symbol, origin: AnyRef) = {
25962579
// do this here because it's quite close to what Java does:
25972580
// when checking subtyping/type equality, enter constraints
25982581
// derived from the existentially quantified type into the typing environment
25992582
// (aka \Gamma, which tracks types for variables and constraints/kinds for types)
26002583
// as a nice bonus, delaying this until we need it avoids cyclic errors
2601-
if (_sharpenQuantifierBounds) sharpenQuantifierBounds
2584+
def tpars = underlying.typeSymbol.initialize.typeParams
2585+
2586+
def newSkolem(quant: Symbol) = owner.newExistentialSkolem(quant, origin)
2587+
def newSharpenedSkolem(quant: Symbol, tparam: Symbol): Symbol = {
2588+
def emptyBounds(sym: Symbol) = sym.info.bounds.isEmptyBounds
2589+
2590+
// avoid creating cycles [pos/t2940] that consist of an existential quantifier's
2591+
// bounded by an existential type that unhygienically has that quantifier as its own quantifier
2592+
// (TODO: clone latter existential with fresh quantifiers -- not covering this case for now)
2593+
val canSharpen = (
2594+
emptyBounds(quant) && !emptyBounds(tparam)
2595+
&& (existentialsInType(tparam.info) intersect quantified).isEmpty
2596+
)
2597+
2598+
val skolemInfo = if (!canSharpen) quant.info else tparam.info.substSym(tpars, quantified)
2599+
2600+
owner.newExistentialSkolem(quant.name.toTypeName, skolemInfo, quant.flags, quant.pos, origin)
2601+
}
2602+
2603+
val canSharpenBounds = (underlying.typeSymbol.isJavaDefined || sharperSkolems) && isStraightApplication
26022604

2603-
deriveType(quantified, tparam => (owner orElse tparam.owner).newExistentialSkolem(tparam, origin))(underlying)
2605+
if (canSharpenBounds) deriveType2(quantified, tpars, newSharpenedSkolem)(underlying)
2606+
else deriveType(quantified, newSkolem)(underlying)
26042607
}
26052608

26062609
private def wildcardArgsString(qset: Set[Symbol], args: List[Type]): List[String] = args map {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
case class CC(x: J[_])
2+
3+
case class CC1(x: Any => J[_])
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public class J<T extends String> {}

0 commit comments

Comments
 (0)