Skip to content

Commit c1e41ed

Browse files
authored
Merge pull request #10271 from dotty-staging/fix-#10217
Fix #10217: Avoid exponential behavior in derivesFrom
2 parents 5c8e6ec + 0969035 commit c1e41ed

File tree

14 files changed

+108
-53
lines changed

14 files changed

+108
-53
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
586586
val tree2: Select = tree.tpe match {
587587
case tpe: NamedType =>
588588
val qualType = qualifier.tpe.widenIfUnstable
589-
if qualType.isNothing then tree1.withTypeUnchecked(tree.tpe)
589+
if qualType.isExactlyNothing then tree1.withTypeUnchecked(tree.tpe)
590590
else tree1.withType(tpe.derivedSelect(qualType))
591591
case _ => tree1.withTypeUnchecked(tree.tpe)
592592
}
@@ -980,7 +980,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
980980

981981
/** `tree ne null` (might need a cast to be type correct) */
982982
def testNotNull(using Context): Tree = {
983-
val receiver = if (defn.isBottomType(tree.tpe))
983+
val receiver = if (tree.tpe.isBottomType)
984984
// If the receiver is of type `Nothing` or `Null`, add an ascription so that the selection
985985
// succeeds: e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does.
986986
Typed(tree, TypeTree(defn.AnyRefType))

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,13 +1174,6 @@ class Definitions {
11741174

11751175
def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass
11761176

1177-
def isBottomType(tp: Type): Boolean =
1178-
if (ctx.explicitNulls && !ctx.phase.erasedTypes) tp.derivesFrom(NothingClass)
1179-
else isBottomTypeAfterErasure(tp)
1180-
1181-
def isBottomTypeAfterErasure(tp: Type): Boolean =
1182-
tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass)
1183-
11841177
/** Is a function class.
11851178
* - FunctionXXL
11861179
* - FunctionN for N >= 0

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,8 @@ class TypeApplications(val self: Type) extends AnyVal {
407407
if (self.derivesFrom(from)) {
408408
def elemType(tp: Type): Type = tp.widenDealias match
409409
case tp: OrType =>
410-
if defn.isBottomType(tp.tp1) then elemType(tp.tp2)
411-
else if defn.isBottomType(tp.tp2) then elemType(tp.tp1)
410+
if tp.tp1.isBottomType then elemType(tp.tp2)
411+
else if tp.tp2.isBottomType then elemType(tp.tp1)
412412
else tp.derivedOrType(elemType(tp.tp1), elemType(tp.tp2))
413413
case tp: AndType => tp.derivedAndType(elemType(tp.tp1), elemType(tp.tp2))
414414
case _ => tp.baseType(from).argInfos.headOption.getOrElse(defn.NothingType)
@@ -503,8 +503,8 @@ class TypeApplications(val self: Type) extends AnyVal {
503503
def elemType(using Context): Type = self.widenDealias match {
504504
case defn.ArrayOf(elemtp) => elemtp
505505
case JavaArrayType(elemtp) => elemtp
506-
case tp: OrType if defn.isBottomType(tp.tp1) => tp.tp2.elemType
507-
case tp: OrType if defn.isBottomType(tp.tp2) => tp.tp1.elemType
506+
case tp: OrType if tp.tp1.isBottomType => tp.tp2.elemType
507+
case tp: OrType if tp.tp2.isBottomType => tp.tp1.elemType
508508
case _ => self.baseType(defn.SeqClass).argInfos.headOption.getOrElse(NoType)
509509
}
510510
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
715715
}
716716
compareTypeBounds
717717
case tp2: AnnotatedType if tp2.isRefining =>
718-
(tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || defn.isBottomType(tp1)) &&
718+
(tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || tp1.isBottomType) &&
719719
recur(tp1, tp2.parent)
720720
case ClassInfo(pre2, cls2, _, _, _) =>
721721
def compareClassInfo = tp1 match {
@@ -2338,7 +2338,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
23382338
*/
23392339
def provablyEmpty(tp: Type): Boolean =
23402340
tp.dealias match {
2341-
case tp if tp.isNothing => true
2341+
case tp if tp.isExactlyNothing => true
23422342
case AndType(tp1, tp2) => provablyDisjoint(tp1, tp2)
23432343
case OrType(tp1, tp2) => provablyEmpty(tp1) && provablyEmpty(tp2)
23442344
case at @ AppliedType(tycon, args) =>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,8 @@ object TypeErasure {
291291
// We need to short-circuit this case here because the regular lub logic below
292292
// relies on the class hierarchy, which doesn't properly capture `Null`s subtyping
293293
// behaviour.
294-
if (defn.isBottomTypeAfterErasure(tp1) && tp2.derivesFrom(defn.ObjectClass)) return tp2
295-
if (defn.isBottomTypeAfterErasure(tp2) && tp1.derivesFrom(defn.ObjectClass)) return tp1
294+
if (tp1.isBottomTypeAfterErasure && tp2.derivesFrom(defn.ObjectClass)) return tp2
295+
if (tp2.isBottomTypeAfterErasure && tp1.derivesFrom(defn.ObjectClass)) return tp1
296296
tp1 match {
297297
case JavaArrayType(elem1) =>
298298
import dotty.tools.dotc.transform.TypeUtils._

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ object TypeOps:
152152
simplify(l, theMap) & simplify(r, theMap)
153153
case tp as OrType(l, r)
154154
if !ctx.mode.is(Mode.Type)
155-
&& (tp.isSoft || defn.isBottomType(l) || defn.isBottomType(r)) =>
155+
&& (tp.isSoft || l.isBottomType || r.isBottomType) =>
156156
// Normalize A | Null and Null | A to A even if the union is hard (i.e.
157157
// explicitly declared), but not if -Yexplicit-nulls is set. The reason is
158158
// that in this case the normal asSeenFrom machinery is not prepared to deal

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

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,27 @@ object Types {
209209
def isAnyRef(using Context): Boolean = isRef(defn.ObjectClass, skipRefined = false)
210210
def isAnyKind(using Context): Boolean = isRef(defn.AnyKindClass, skipRefined = false)
211211

212-
def isFromJavaObject(using Context): Boolean = typeSymbol eq defn.FromJavaObjectSymbol
212+
/** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */
213+
def isExactlyNothing(using Context): Boolean = this match {
214+
case tp: TypeRef =>
215+
tp.name == tpnme.Nothing && (tp.symbol eq defn.NothingClass)
216+
case _ => false
217+
}
218+
219+
/** Is this type exactly Any (no vars, aliases, refinements etc allowed)? */
220+
def isExactlyAny(using Context): Boolean = this match {
221+
case tp: TypeRef =>
222+
tp.name == tpnme.Any && (tp.symbol eq defn.AnyClass)
223+
case _ => false
224+
}
225+
226+
def isBottomType(using Context): Boolean =
227+
if ctx.explicitNulls && !ctx.phase.erasedTypes then hasClassSymbol(defn.NothingClass)
228+
else isBottomTypeAfterErasure
229+
230+
def isBottomTypeAfterErasure(using Context): Boolean =
231+
val d = defn
232+
hasClassSymbol(d.NothingClass) || hasClassSymbol(d.NullClass)
213233

214234
/** Does this type refer exactly to class symbol `sym`, instead of to a subclass of `sym`?
215235
* Implemented like `isRef`, but follows more types: all type proxies as well as and- and or-types
@@ -224,7 +244,9 @@ object Types {
224244
case _ => false
225245
}
226246

227-
/** Is this type an instance of a non-bottom subclass of the given class `cls`? */
247+
/** True if this type is an instance of the given `cls` or an instance of
248+
* a non-bottom subclass of `cls`.
249+
*/
228250
final def derivesFrom(cls: Symbol)(using Context): Boolean = {
229251
def loop(tp: Type): Boolean = tp match {
230252
case tp: TypeRef =>
@@ -239,11 +261,15 @@ object Types {
239261
case tp: AndType =>
240262
loop(tp.tp1) || loop(tp.tp2)
241263
case tp: OrType =>
242-
// If the type is `T | Null` or `T | Nothing`, and `T` derivesFrom the class,
243-
// then the OrType derivesFrom the class. Otherwise, we need to check both sides
244-
// derivesFrom the class.
245-
if defn.isBottomType(tp.tp1) then loop(tp.tp2)
246-
else loop(tp.tp1) && (defn.isBottomType(tp.tp2) || loop(tp.tp2))
264+
// If the type is `T | Null` or `T | Nothing`, the class is != Nothing,
265+
// and `T` derivesFrom the class, then the OrType derivesFrom the class.
266+
// Otherwise, we need to check both sides derivesFrom the class.
267+
if tp.tp1.isBottomType && cls != defn.NothingClass then
268+
loop(tp.tp2)
269+
else if tp.tp2.isBottomType && cls != defn.NothingClass then
270+
loop(tp.tp1)
271+
else
272+
loop(tp.tp1) && loop(tp.tp2)
247273
case tp: JavaArrayType =>
248274
cls == defn.ObjectClass
249275
case _ =>
@@ -252,6 +278,8 @@ object Types {
252278
loop(this)
253279
}
254280

281+
def isFromJavaObject(using Context): Boolean = typeSymbol eq defn.FromJavaObjectSymbol
282+
255283
/** True iff `symd` is a denotation of a class type parameter and the reference
256284
* `<this> . <symd>` is an actual argument reference, i.e. `this` is different
257285
* from the ThisType of `symd`'s owner.
@@ -265,20 +293,6 @@ object Types {
265293
}
266294
}
267295

268-
/** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */
269-
def isNothing(using Context): Boolean = this match {
270-
case tp: TypeRef =>
271-
tp.name == tpnme.Nothing && (tp.symbol eq defn.NothingClass)
272-
case _ => false
273-
}
274-
275-
/** Is this type exactly Any (no vars, aliases, refinements etc allowed)? */
276-
def isTopType(using Context): Boolean = this match {
277-
case tp: TypeRef =>
278-
tp.name == tpnme.Any && (tp.symbol eq defn.AnyClass)
279-
case _ => false
280-
}
281-
282296
/** Is this type a (possibly aliased) singleton type? */
283297
def isSingleton(using Context): Boolean = dealias.isInstanceOf[SingletonType]
284298

@@ -478,6 +492,22 @@ object Types {
478492
final def classSymbols(using Context): List[ClassSymbol] =
479493
parentSymbols(_.isClass).asInstanceOf
480494

495+
/** Same as `this.classSymbols.contains(cls)` but more efficient */
496+
final def hasClassSymbol(cls: Symbol)(using Context): Boolean = this match
497+
case tp: TypeRef =>
498+
val sym = tp.symbol
499+
sym == cls || !sym.isClass && tp.superType.hasClassSymbol(cls)
500+
case tp: TypeProxy =>
501+
tp.underlying.hasClassSymbol(cls)
502+
case tp: ClassInfo =>
503+
tp.cls == cls
504+
case AndType(l, r) =>
505+
l.hasClassSymbol(cls) || r.hasClassSymbol(cls)
506+
case OrType(l, r) =>
507+
l.hasClassSymbol(cls) && r.hasClassSymbol(cls)
508+
case _ =>
509+
false
510+
481511
/** The term symbol associated with the type */
482512
@tailrec final def termSymbol(using Context): Symbol = this match {
483513
case tp: TermRef => tp.symbol
@@ -2134,8 +2164,8 @@ object Types {
21342164
case arg: TypeBounds =>
21352165
val v = param.paramVarianceSign
21362166
val pbounds = param.paramInfo
2137-
if (v > 0 && pbounds.loBound.dealiasKeepAnnots.isNothing) TypeAlias(arg.hiBound & rebase(pbounds.hiBound))
2138-
else if (v < 0 && pbounds.hiBound.dealiasKeepAnnots.isTopType) TypeAlias(arg.loBound | rebase(pbounds.loBound))
2167+
if (v > 0 && pbounds.loBound.dealiasKeepAnnots.isExactlyNothing) TypeAlias(arg.hiBound & rebase(pbounds.hiBound))
2168+
else if (v < 0 && pbounds.hiBound.dealiasKeepAnnots.isExactlyAny) TypeAlias(arg.loBound | rebase(pbounds.loBound))
21392169
else arg recoverable_& rebase(pbounds)
21402170
case arg => TypeAlias(arg)
21412171
}
@@ -2297,7 +2327,7 @@ object Types {
22972327
if (base.isAnd == variance >= 0) tp1 & tp2 else tp1 | tp2
22982328
case _ =>
22992329
if (pre.termSymbol.is(Package)) argForParam(pre.select(nme.PACKAGE))
2300-
else if (pre.isNothing) pre
2330+
else if (pre.isExactlyNothing) pre
23012331
else NoType
23022332
}
23032333
}
@@ -2316,7 +2346,7 @@ object Types {
23162346
*/
23172347
def derivedSelect(prefix: Type)(using Context): Type =
23182348
if (prefix eq this.prefix) this
2319-
else if (prefix.isNothing) prefix
2349+
else if (prefix.isExactlyNothing) prefix
23202350
else {
23212351
if (isType) {
23222352
val res =
@@ -4304,7 +4334,7 @@ object Types {
43044334

43054335
/** For uninstantiated type variables: Is the lower bound different from Nothing? */
43064336
def hasLowerBound(using Context): Boolean =
4307-
!ctx.typerState.constraint.entry(origin).loBound.isNothing
4337+
!ctx.typerState.constraint.entry(origin).loBound.isExactlyNothing
43084338

43094339
/** For uninstantiated type variables: Is the upper bound different from Any? */
43104340
def hasUpperBound(using Context): Boolean =
@@ -5309,7 +5339,7 @@ object Types {
53095339
case _ =>
53105340
def propagate(lo: Type, hi: Type) =
53115341
range(derivedRefinedType(tp, parent, lo), derivedRefinedType(tp, parent, hi))
5312-
if (parent.isNothing) parent
5342+
if (parent.isExactlyNothing) parent
53135343
else info match {
53145344
case Range(infoLo: TypeBounds, infoHi: TypeBounds) =>
53155345
assert(variance == 0)
@@ -5402,7 +5432,7 @@ object Types {
54025432
case Range(lo, hi) =>
54035433
range(tp.derivedAnnotatedType(lo, annot), tp.derivedAnnotatedType(hi, annot))
54045434
case _ =>
5405-
if (underlying.isNothing) underlying
5435+
if (underlying.isExactlyNothing) underlying
54065436
else tp.derivedAnnotatedType(underlying, annot)
54075437
}
54085438
override protected def derivedWildcardType(tp: WildcardType, bounds: Type): WildcardType =
@@ -5650,7 +5680,7 @@ object Types {
56505680
else {
56515681
seen += tp
56525682
tp match {
5653-
case tp if tp.isTopType || tp.isNothing =>
5683+
case tp if tp.isExactlyAny || tp.isExactlyNothing =>
56545684
cs
56555685
case tp: AppliedType =>
56565686
foldOver(cs + tp.typeSymbol, tp)

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ object Completion {
205205
* considered.
206206
*/
207207
def addMemberCompletions(qual: Tree)(using Context): Unit =
208-
if (!qual.tpe.widenDealias.isNothing) {
208+
if (!qual.tpe.widenDealias.isExactlyNothing) {
209209
addAccessibleMembers(qual.tpe)
210210
if (!mode.is(Mode.Import) && !qual.tpe.isNullType)
211211
// Implicit conversions do not kick in when importing

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ import transform.SymUtils._
269269
if expected.isAny
270270
|| expected.isAnyRef
271271
|| expected.isRef(defn.AnyValClass)
272-
|| defn.isBottomType(found)
272+
|| found.isBottomType
273273
then ""
274274
else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected))
275275
val (where, printCtx) = Formatting.disambiguateTypes(found2, expected2)

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ object PatternMatcher {
373373
patternPlan(casted, pat, onSuccess)
374374
})
375375
case UnApply(extractor, implicits, args) =>
376-
val unappPlan = if (defn.isBottomType(scrutinee.info))
376+
val unappPlan = if (scrutinee.info.isBottomType)
377377
// Generate a throwaway but type-correct plan.
378378
// This plan will never execute because it'll be guarded by a `NonNullTest`.
379379
ResultPlan(tpd.Throw(tpd.nullLiteral))

compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ abstract class TransformByNameApply extends MiniPhase { thisPhase: DenotTransfor
4242
def transformArg(arg: Tree, formal: Type): Tree = formal.dealias match {
4343
case formalExpr: ExprType =>
4444
var argType = arg.tpe.widenIfUnstable
45-
if (defn.isBottomType(argType)) argType = formal.widenExpr
45+
if (argType.isBottomType) argType = formal.widenExpr
4646
def wrap(arg: Tree) =
4747
ref(defn.cbnArg).appliedToType(argType).appliedTo(arg).withSpan(arg.span)
4848
arg match {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ object ErrorReporting {
165165
|Note that `${tree.name}` is treated as an infix operator in Scala 3.
166166
|If you do not want that, insert a `;` or empty line in front
167167
|or drop any spaces behind the operator."""
168-
else if qualType.isNothing then
168+
else if qualType.isExactlyNothing then
169169
""
170170
else
171171
val add = suggestImports(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ trait ImplicitRunInfo:
715715
seen += t
716716
t.underlying match
717717
case TypeBounds(lo, hi) =>
718-
if defn.isBottomTypeAfterErasure(lo) then apply(hi)
718+
if lo.isBottomTypeAfterErasure then apply(hi)
719719
else AndType.make(apply(lo), apply(hi))
720720
case u => apply(u)
721721

tests/pos/i10217.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
trait A
2+
trait B
3+
trait C
4+
trait D
5+
trait E
6+
trait F
7+
trait G
8+
trait H
9+
trait I
10+
trait J
11+
trait K
12+
trait L
13+
trait M
14+
trait N
15+
trait O
16+
trait P
17+
trait Q
18+
trait R
19+
trait S
20+
trait T
21+
trait U
22+
trait V
23+
trait W
24+
25+
class Foo[T]
26+
27+
val f1 = Foo[A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U]
28+
val f2 = Foo[A | (B | (C | (D | (E | (F | (G | (H | (I | (J | (K | (L | (M | (N | (O | (P | (Q | (R | (S | (T | U)))))))))))))))))))]
29+
val f3 = Foo[Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | A]
30+
val f4 = Foo[A | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null]
31+
val f5 = Foo[Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | A)))))))))))))))))))]
32+
val f6 = Foo[A | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | Null))))))))))))))))))]

0 commit comments

Comments
 (0)