Skip to content

Commit f738201

Browse files
authored
Merge pull request #1550 from dotty-staging/union-types
True union types
2 parents d96bba0 + ba18173 commit f738201

29 files changed

+273
-183
lines changed

Diff for: src/dotty/language.scala

-9
This file was deleted.

Diff for: src/dotty/tools/dotc/config/Config.scala

+6-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,12 @@ object Config {
8787
*/
8888
final val checkLambdaVariance = false
8989

90-
/** Check that certain types cannot be created in erasedTypes phases */
91-
final val checkUnerased = true
90+
/** Check that certain types cannot be created in erasedTypes phases.
91+
* Note: Turning this option on will get some false negatives, since it is
92+
* possible that And/Or types are still created during erasure as the result
93+
* of some operation on an existing type.
94+
*/
95+
final val checkUnerased = false
9296

9397
/** In `derivedSelect`, rewrite
9498
*

Diff for: src/dotty/tools/dotc/core/ConstraintHandling.scala

+12-3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ trait ConstraintHandling {
3535
/** If the constraint is frozen we cannot add new bounds to the constraint. */
3636
protected var frozenConstraint = false
3737

38+
protected var alwaysFluid = false
39+
40+
/** Perform `op` in a mode where all attempts to set `frozen` to true are ignored */
41+
def fluidly[T](op: => T): T = {
42+
val saved = alwaysFluid
43+
alwaysFluid = true
44+
try op finally alwaysFluid = saved
45+
}
46+
3847
/** We are currently comparing lambdas. Used as a flag for
3948
* optimization: when `false`, no need to do an expensive `pruneLambdaParams`
4049
*/
@@ -126,14 +135,14 @@ trait ConstraintHandling {
126135

127136
final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
128137
val saved = frozenConstraint
129-
frozenConstraint = true
138+
frozenConstraint = !alwaysFluid
130139
try isSubType(tp1, tp2)
131140
finally frozenConstraint = saved
132141
}
133142

134143
final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
135144
val saved = frozenConstraint
136-
frozenConstraint = true
145+
frozenConstraint = !alwaysFluid
137146
try isSameType(tp1, tp2)
138147
finally frozenConstraint = saved
139148
}
@@ -219,7 +228,7 @@ trait ConstraintHandling {
219228
// is not a union type, approximate the union type from above by an intersection
220229
// of all common base types.
221230
if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound))
222-
inst = inst.approximateUnion
231+
inst = ctx.harmonizeUnion(inst)
223232

224233
// 3. If instance is from below, and upper bound has open named parameters
225234
// make sure the instance has all named parameters of the bound.

Diff for: src/dotty/tools/dotc/core/Definitions.scala

+1-3
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,8 @@ class Definitions {
430430
def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol
431431
lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix)
432432
def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol
433-
lazy val LanguageModuleRef = ctx.requiredModule("dotty.language")
433+
lazy val LanguageModuleRef = ctx.requiredModule("scala.language")
434434
def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass
435-
lazy val Scala2LanguageModuleRef = ctx.requiredModule("scala.language")
436-
def Scala2LanguageModuleClass(implicit ctx: Context) = Scala2LanguageModuleRef.symbol.moduleClass.asClass
437435
lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl")
438436

439437
lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag")

Diff for: src/dotty/tools/dotc/core/Denotations.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@ object Denotations {
336336
(!sym2.isAsConcrete(sym1) ||
337337
precedes(sym1.owner, sym2.owner) ||
338338
accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1)) ||
339-
sym1.is(Method) && !sym2.is(Method))
339+
sym1.is(Method) && !sym2.is(Method)) ||
340+
sym1.info.isErroneous
340341

341342
/** Sym preference provided types also override */
342343
def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) =

Diff for: src/dotty/tools/dotc/core/OrderingConstraint.scala

+5
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,11 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
521521
case _ if e1 contains e2 => e2
522522
case _ => mergeError
523523
}
524+
case tv1: TypeVar =>
525+
e2 match {
526+
case tv2: TypeVar if tv1.instanceOpt eq tv2.instanceOpt => e1
527+
case _ => mergeError
528+
}
524529
case _ if e1 eq e2 => e1
525530
case _ => mergeError
526531
}

Diff for: src/dotty/tools/dotc/core/StdNames.scala

-1
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,6 @@ object StdNames {
429429
val isEmpty: N = "isEmpty"
430430
val isInstanceOf_ : N = "isInstanceOf"
431431
val java: N = "java"
432-
val keepUnions: N = "keepUnions"
433432
val key: N = "key"
434433
val lang: N = "lang"
435434
val length: N = "length"

Diff for: src/dotty/tools/dotc/core/TypeComparer.scala

+17-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import StdNames.{nme, tpnme}
88
import collection.mutable
99
import util.{Stats, DotClass, SimpleMap}
1010
import config.Config
11-
import config.Printers.{typr, constr, subtyping}
11+
import config.Printers.{typr, constr, subtyping, noPrinter}
1212
import TypeErasure.{erasedLub, erasedGlb}
1313
import TypeApplications._
1414
import scala.util.control.NonFatal
@@ -324,8 +324,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
324324
case AndType(tp11, tp12) =>
325325
if (tp11.stripTypeVar eq tp12.stripTypeVar) isSubType(tp11, tp2)
326326
else thirdTry(tp1, tp2)
327-
case OrType(tp11, tp12) =>
328-
isSubType(tp11, tp2) && isSubType(tp12, tp2)
327+
case tp1 @ OrType(tp11, tp12) =>
328+
def joinOK = tp2.dealias match {
329+
case tp12: HKApply =>
330+
// If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a
331+
// type parameter, we will instantiate `C` to `A` and then fail when comparing
332+
// with `B[Y]`. To do the right thing, we need to instantiate `C` to the
333+
// common superclass of `A` and `B`.
334+
isSubType(tp1.join, tp2)
335+
case _ =>
336+
false
337+
}
338+
joinOK || isSubType(tp11, tp2) && isSubType(tp12, tp2)
329339
case ErrorType =>
330340
true
331341
case _ =>
@@ -827,8 +837,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
827837
op1 && {
828838
val leftConstraint = constraint
829839
constraint = preConstraint
830-
if (!(op2 && subsumes(leftConstraint, constraint, preConstraint)))
840+
if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) {
841+
if (constr != noPrinter && !subsumes(constraint, leftConstraint, preConstraint))
842+
constr.println(i"CUT - prefer $leftConstraint over $constraint")
831843
constraint = leftConstraint
844+
}
832845
true
833846
} || op2
834847
}

Diff for: src/dotty/tools/dotc/core/TypeOps.scala

+79-55
Original file line numberDiff line numberDiff line change
@@ -173,100 +173,124 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
173173
}
174174

175175
/** Approximate union type by intersection of its dominators.
176-
* See Type#approximateUnion for an explanation.
176+
* That is, replace a union type Tn | ... | Tn
177+
* by the smallest intersection type of base-class instances of T1,...,Tn.
178+
* Example: Given
179+
*
180+
* trait C[+T]
181+
* trait D
182+
* class A extends C[A] with D
183+
* class B extends C[B] with D with E
184+
*
185+
* we approximate `A | B` by `C[A | B] with D`
177186
*/
178-
def approximateUnion(tp: Type): Type = {
187+
def orDominator(tp: Type): Type = {
188+
179189
/** a faster version of cs1 intersect cs2 */
180190
def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = {
181191
val cs2AsSet = new util.HashSet[ClassSymbol](100)
182192
cs2.foreach(cs2AsSet.addEntry)
183193
cs1.filter(cs2AsSet.contains)
184194
}
195+
185196
/** The minimal set of classes in `cs` which derive all other classes in `cs` */
186197
def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match {
187198
case c :: rest =>
188199
val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu
189200
if (cs == c.baseClasses) accu1 else dominators(rest, accu1)
190201
}
202+
203+
def mergeRefined(tp1: Type, tp2: Type): Type = {
204+
def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2")
205+
tp1 match {
206+
case tp1 @ RefinedType(parent1, name1, rinfo1) =>
207+
tp2 match {
208+
case RefinedType(parent2, `name1`, rinfo2) =>
209+
tp1.derivedRefinedType(
210+
mergeRefined(parent1, parent2), name1, rinfo1 | rinfo2)
211+
case _ => fail
212+
}
213+
case tp1 @ TypeRef(pre1, name1) =>
214+
tp2 match {
215+
case tp2 @ TypeRef(pre2, `name1`) =>
216+
tp1.derivedSelect(pre1 | pre2)
217+
case _ => fail
218+
}
219+
case _ => fail
220+
}
221+
}
222+
191223
def approximateOr(tp1: Type, tp2: Type): Type = {
192224
def isClassRef(tp: Type): Boolean = tp match {
193225
case tp: TypeRef => tp.symbol.isClass
194226
case tp: RefinedType => isClassRef(tp.parent)
195227
case _ => false
196228
}
197229

198-
/** If `tp1` and `tp2` are typebounds, try to make one fit into the other
199-
* or to make them equal, by instantiating uninstantiated type variables.
200-
*/
201-
def homogenizedUnion(tp1: Type, tp2: Type): Type = {
202-
tp1 match {
203-
case tp1: TypeBounds =>
204-
tp2 match {
205-
case tp2: TypeBounds =>
206-
def fitInto(tp1: TypeBounds, tp2: TypeBounds): Unit = {
207-
val nestedCtx = ctx.fresh.setNewTyperState
208-
if (tp2.boundsInterval.contains(tp1.boundsInterval)(nestedCtx))
209-
nestedCtx.typerState.commit()
210-
}
211-
fitInto(tp1, tp2)
212-
fitInto(tp2, tp1)
213-
case _ =>
214-
}
215-
case _ =>
216-
}
217-
tp1 | tp2
218-
}
219-
220-
tp1 match {
221-
case tp1: RefinedType =>
222-
tp2 match {
223-
case tp2: RefinedType if tp1.refinedName == tp2.refinedName =>
224-
return tp1.derivedRefinedType(
225-
approximateUnion(OrType(tp1.parent, tp2.parent)),
226-
tp1.refinedName,
227-
homogenizedUnion(tp1.refinedInfo, tp2.refinedInfo))
228-
//.ensuring { x => println(i"approx or $tp1 | $tp2 = $x\n constr = ${ctx.typerState.constraint}"); true } // DEBUG
229-
case _ =>
230-
}
231-
case _ =>
232-
}
233-
234230
tp1 match {
235231
case tp1: RecType =>
236232
tp1.rebind(approximateOr(tp1.parent, tp2))
237233
case tp1: TypeProxy if !isClassRef(tp1) =>
238-
approximateUnion(tp1.superType | tp2)
234+
orDominator(tp1.superType | tp2)
239235
case _ =>
240236
tp2 match {
241237
case tp2: RecType =>
242238
tp2.rebind(approximateOr(tp1, tp2.parent))
243239
case tp2: TypeProxy if !isClassRef(tp2) =>
244-
approximateUnion(tp1 | tp2.superType)
240+
orDominator(tp1 | tp2.superType)
245241
case _ =>
246242
val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect)
247243
val doms = dominators(commonBaseClasses, Nil)
248-
def baseTp(cls: ClassSymbol): Type =
249-
if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls)
250-
else tp.baseTypeWithArgs(cls)
244+
def baseTp(cls: ClassSymbol): Type = {
245+
val base =
246+
if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls)
247+
else tp.baseTypeWithArgs(cls)
248+
base.mapReduceOr(identity)(mergeRefined)
249+
}
251250
doms.map(baseTp).reduceLeft(AndType.apply)
252251
}
253252
}
254253
}
255-
if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp
256-
else tp match {
254+
255+
tp match {
257256
case tp: OrType =>
258-
approximateOr(tp.tp1, tp.tp2) // Maybe refactor using liftToRec?
259-
case tp @ AndType(tp1, tp2) =>
260-
tp derived_& (approximateUnion(tp1), approximateUnion(tp2))
261-
case tp: RefinedType =>
262-
tp.derivedRefinedType(approximateUnion(tp.parent), tp.refinedName, tp.refinedInfo)
263-
case tp: RecType =>
264-
tp.rebind(approximateUnion(tp.parent))
257+
approximateOr(tp.tp1, tp.tp2)
265258
case _ =>
266259
tp
267260
}
268261
}
269262

263+
/** Given a disjunction T1 | ... | Tn of types with potentially embedded
264+
* type variables, constrain type variables further if this eliminates
265+
* some of the branches of the disjunction. Do this also for disjunctions
266+
* embedded in intersections, as parents in refinements, and in recursive types.
267+
*
268+
* For instance, if `A` is an unconstrained type variable, then
269+
*
270+
* ArrayBuffer[Int] | ArrayBuffer[A]
271+
*
272+
* is approximated by constraining `A` to be =:= to `Int` and returning `ArrayBuffer[Int]`
273+
* instead of `ArrayBuffer[_ >: Int | A <: Int & A]`
274+
*/
275+
def harmonizeUnion(tp: Type): Type = tp match {
276+
case tp: OrType =>
277+
joinIfScala2(typeComparer.fluidly(tp.tp1 | tp.tp2))
278+
case tp @ AndType(tp1, tp2) =>
279+
tp derived_& (harmonizeUnion(tp1), harmonizeUnion(tp2))
280+
case tp: RefinedType =>
281+
tp.derivedRefinedType(harmonizeUnion(tp.parent), tp.refinedName, tp.refinedInfo)
282+
case tp: RecType =>
283+
tp.rebind(harmonizeUnion(tp.parent))
284+
case _ =>
285+
tp
286+
}
287+
288+
/** Under -language:Scala2: Replace or-types with their joins */
289+
private def joinIfScala2(tp: Type) = tp match {
290+
case tp: OrType if scala2Mode => tp.join
291+
case _ => tp
292+
}
293+
270294
/** Not currently needed:
271295
*
272296
def liftToRec(f: (Type, Type) => Type)(tp1: Type, tp2: Type)(implicit ctx: Context) = {
@@ -493,7 +517,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
493517
*/
494518
def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = {
495519
def toPrefix(sym: Symbol): String =
496-
if (!sym.exists || (sym eq defn.LanguageModuleClass) || (sym eq defn.Scala2LanguageModuleClass)) ""
520+
if (!sym.exists || (sym eq defn.LanguageModuleClass)) ""
497521
else toPrefix(sym.owner) + sym.name + "."
498522
def featureName = toPrefix(owner) + feature
499523
def hasImport(implicit ctx: Context): Boolean = {
@@ -512,13 +536,13 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
512536

513537
/** Is auto-tupling enabled? */
514538
def canAutoTuple =
515-
!featureEnabled(defn.Scala2LanguageModuleClass, nme.noAutoTupling)
539+
!featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling)
516540

517541
def scala2Mode =
518542
featureEnabled(defn.LanguageModuleClass, nme.Scala2)
519543

520544
def dynamicsEnabled =
521-
featureEnabled(defn.Scala2LanguageModuleClass, nme.dynamics)
545+
featureEnabled(defn.LanguageModuleClass, nme.dynamics)
522546

523547
def testScala2Mode(msg: String, pos: Position) = {
524548
if (scala2Mode) migrationWarning(msg, pos)

0 commit comments

Comments
 (0)