From 78c58a070d63925b11441b7095817c9b83c0bfec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 10 Nov 2020 22:17:21 +0100 Subject: [PATCH 1/7] Fix #10217: Avoid exponential behavior in derivesFrom --- .../src/dotty/tools/dotc/core/Types.scala | 5 ++-- tests/pos/i10217.scala | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i10217.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 14547d36e423..eacd34f543e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -242,8 +242,9 @@ object Types { // If the type is `T | Null` or `T | Nothing`, and `T` derivesFrom the class, // then the OrType derivesFrom the class. Otherwise, we need to check both sides // derivesFrom the class. - if defn.isBottomType(tp.tp1) then loop(tp.tp2) - else loop(tp.tp1) && (defn.isBottomType(tp.tp2) || loop(tp.tp2)) + loop(tp.tp1) && loop(tp.tp2) + || tp.tp1.isNullType && loop(tp.tp2) + || tp.tp2.isNullType && loop(tp.tp2) case tp: JavaArrayType => cls == defn.ObjectClass case _ => diff --git a/tests/pos/i10217.scala b/tests/pos/i10217.scala new file mode 100644 index 000000000000..2c64ac6a3bbb --- /dev/null +++ b/tests/pos/i10217.scala @@ -0,0 +1,27 @@ +trait A +trait B +trait C +trait D +trait E +trait F +trait G +trait H +trait I +trait J +trait K +trait L +trait M +trait N +trait O +trait P +trait Q +trait R +trait S +trait T +trait U +trait V +trait W + +class Foo[T] + +val f = Foo[A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U] \ No newline at end of file From 0723400b179949439876dda8c82ef53968a5b0ef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 10 Nov 2020 22:48:30 +0100 Subject: [PATCH 2/7] Fix typo --- compiler/src/dotty/tools/dotc/core/Types.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index eacd34f543e0..db256fe028f6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -243,8 +243,8 @@ object Types { // then the OrType derivesFrom the class. Otherwise, we need to check both sides // derivesFrom the class. loop(tp.tp1) && loop(tp.tp2) - || tp.tp1.isNullType && loop(tp.tp2) - || tp.tp2.isNullType && loop(tp.tp2) + || tp.tp1.isNullOrNothingType && loop(tp.tp2) + || tp.tp2.isNullOrNothingType && loop(tp.tp1) case tp: JavaArrayType => cls == defn.ObjectClass case _ => @@ -1534,6 +1534,9 @@ object Types { /** Is this (an alias of) the `scala.Null` type? */ final def isNullType(using Context) = isRef(defn.NullClass) + /** Is this (an alias of) the `scala.Null` or `scala.Nothing` type? */ + final def isNullOrNothingType(using Context) = isNullType || isRef(defn.NothingClass) + /** The resultType of a LambdaType, or ExprType, the type itself for others */ def resultType(using Context): Type = this From 3d0aa824f0941eb68e32a43ae65ffe1ede55227b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 10 Nov 2020 23:18:27 +0100 Subject: [PATCH 3/7] Alternative fix: Don't use derivesFrom in isBottomType --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/Types.scala | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 23ae40aa4aea..009ac3f66159 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1175,11 +1175,11 @@ class Definitions { def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass def isBottomType(tp: Type): Boolean = - if (ctx.explicitNulls && !ctx.phase.erasedTypes) tp.derivesFrom(NothingClass) + if (ctx.explicitNulls && !ctx.phase.erasedTypes) tp.isCombinedRef(NothingClass) else isBottomTypeAfterErasure(tp) def isBottomTypeAfterErasure(tp: Type): Boolean = - tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) + tp.isCombinedRef(NothingClass) || tp.isCombinedRef(NullClass) /** Is a function class. * - FunctionXXL diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index db256fe028f6..0ed7bc9833be 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -205,6 +205,12 @@ object Types { false } + /** Like isRef, but also extends to unions and intersections */ + def isCombinedRef(sym: Symbol)(using Context): Boolean = stripped match + case AndType(tp1: Type, tp2: Type) => tp1.isRef(sym) || tp2.isRef(sym) + case OrType(tp1: Type, tp2: Type) => tp1.isRef(sym) && tp2.isRef(sym) + case _ => isRef(sym) + def isAny(using Context): Boolean = isRef(defn.AnyClass, skipRefined = false) def isAnyRef(using Context): Boolean = isRef(defn.ObjectClass, skipRefined = false) def isAnyKind(using Context): Boolean = isRef(defn.AnyKindClass, skipRefined = false) @@ -242,9 +248,8 @@ object Types { // If the type is `T | Null` or `T | Nothing`, and `T` derivesFrom the class, // then the OrType derivesFrom the class. Otherwise, we need to check both sides // derivesFrom the class. - loop(tp.tp1) && loop(tp.tp2) - || tp.tp1.isNullOrNothingType && loop(tp.tp2) - || tp.tp2.isNullOrNothingType && loop(tp.tp1) + loop(tp.tp1) && (loop(tp.tp2) || defn.isBottomType(tp.tp2)) + || defn.isBottomType(tp.tp1) && loop(tp.tp2) case tp: JavaArrayType => cls == defn.ObjectClass case _ => From 4c1e38e2438dedbdd547fdbcb4dd198f41503931 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 11 Nov 2020 08:41:16 +0100 Subject: [PATCH 4/7] Add more tests Also test right-associative combinations and combinations with Null --- tests/pos/i10217.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/pos/i10217.scala b/tests/pos/i10217.scala index 2c64ac6a3bbb..931095c2d3e6 100644 --- a/tests/pos/i10217.scala +++ b/tests/pos/i10217.scala @@ -24,4 +24,9 @@ trait W class Foo[T] -val f = Foo[A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U] \ No newline at end of file +val f1 = Foo[A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U] +val f2 = Foo[A | (B | (C | (D | (E | (F | (G | (H | (I | (J | (K | (L | (M | (N | (O | (P | (Q | (R | (S | (T | U)))))))))))))))))))] +val f3 = Foo[Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | A] +val f4 = Foo[A | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null | Null] +val f5 = Foo[Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | A)))))))))))))))))))] +val f6 = Foo[A | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | (Null | Null))))))))))))))))))] From 51f67b44061c310d136fb8ac1b5a5f3591d5fc6e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 11 Nov 2020 08:42:55 +0100 Subject: [PATCH 5/7] Fix isBottomType Need to widen before testing --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 8 +++++--- compiler/src/dotty/tools/dotc/core/Types.scala | 3 --- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 009ac3f66159..32d2c5f5061a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1175,11 +1175,13 @@ class Definitions { def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass def isBottomType(tp: Type): Boolean = - if (ctx.explicitNulls && !ctx.phase.erasedTypes) tp.isCombinedRef(NothingClass) - else isBottomTypeAfterErasure(tp) + val tpw = tp.widen + tpw.isCombinedRef(NothingClass) + || tpw.isCombinedRef(NullClass) && (!ctx.explicitNulls || ctx.phase.erasedTypes) def isBottomTypeAfterErasure(tp: Type): Boolean = - tp.isCombinedRef(NothingClass) || tp.isCombinedRef(NullClass) + val tpw = tp.widen + tpw.isCombinedRef(NothingClass) || tpw.isCombinedRef(NullClass) /** Is a function class. * - FunctionXXL diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0ed7bc9833be..25c192e4a4f4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1539,9 +1539,6 @@ object Types { /** Is this (an alias of) the `scala.Null` type? */ final def isNullType(using Context) = isRef(defn.NullClass) - /** Is this (an alias of) the `scala.Null` or `scala.Nothing` type? */ - final def isNullOrNothingType(using Context) = isNullType || isRef(defn.NothingClass) - /** The resultType of a LambdaType, or ExprType, the type itself for others */ def resultType(using Context): Type = this From 37098b12fcc16ff432b5dc36c4e7dd7a9bfbb5e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 11 Nov 2020 09:55:37 +0100 Subject: [PATCH 6/7] Rework isBottomType Now uses a new method hasClassSymbol instead of recursively calling derivesFrom. --- .../dotty/tools/dotc/core/Definitions.scala | 8 ++-- .../src/dotty/tools/dotc/core/Types.scala | 40 +++++++++++++------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 32d2c5f5061a..08230dee1548 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1175,13 +1175,11 @@ class Definitions { def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass def isBottomType(tp: Type): Boolean = - val tpw = tp.widen - tpw.isCombinedRef(NothingClass) - || tpw.isCombinedRef(NullClass) && (!ctx.explicitNulls || ctx.phase.erasedTypes) + if ctx.explicitNulls && !ctx.phase.erasedTypes then tp.hasClassSymbol(NothingClass) + else isBottomTypeAfterErasure(tp) def isBottomTypeAfterErasure(tp: Type): Boolean = - val tpw = tp.widen - tpw.isCombinedRef(NothingClass) || tpw.isCombinedRef(NullClass) + tp.hasClassSymbol(NothingClass) || tp.hasClassSymbol(NullClass) /** Is a function class. * - FunctionXXL diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 25c192e4a4f4..1237834401a2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -205,12 +205,6 @@ object Types { false } - /** Like isRef, but also extends to unions and intersections */ - def isCombinedRef(sym: Symbol)(using Context): Boolean = stripped match - case AndType(tp1: Type, tp2: Type) => tp1.isRef(sym) || tp2.isRef(sym) - case OrType(tp1: Type, tp2: Type) => tp1.isRef(sym) && tp2.isRef(sym) - case _ => isRef(sym) - def isAny(using Context): Boolean = isRef(defn.AnyClass, skipRefined = false) def isAnyRef(using Context): Boolean = isRef(defn.ObjectClass, skipRefined = false) def isAnyKind(using Context): Boolean = isRef(defn.AnyKindClass, skipRefined = false) @@ -230,7 +224,9 @@ object Types { case _ => false } - /** Is this type an instance of a non-bottom subclass of the given class `cls`? */ + /** True if this type is an instance of the given `cls` or an instance of + * a non-bottom subclass of `cls`. + */ final def derivesFrom(cls: Symbol)(using Context): Boolean = { def loop(tp: Type): Boolean = tp match { case tp: TypeRef => @@ -245,11 +241,15 @@ object Types { case tp: AndType => loop(tp.tp1) || loop(tp.tp2) case tp: OrType => - // If the type is `T | Null` or `T | Nothing`, and `T` derivesFrom the class, - // then the OrType derivesFrom the class. Otherwise, we need to check both sides - // derivesFrom the class. - loop(tp.tp1) && (loop(tp.tp2) || defn.isBottomType(tp.tp2)) - || defn.isBottomType(tp.tp1) && loop(tp.tp2) + // If the type is `T | Null` or `T | Nothing`, the class is != Nothing, + // and `T` derivesFrom the class, then the OrType derivesFrom the class. + // Otherwise, we need to check both sides derivesFrom the class. + if defn.isBottomType(tp.tp1) && cls != defn.NothingClass then + loop(tp.tp2) + else if defn.isBottomType(tp.tp2) && cls != defn.NothingClass then + loop(tp.tp1) + else + loop(tp.tp1) && loop(tp.tp2) case tp: JavaArrayType => cls == defn.ObjectClass case _ => @@ -484,6 +484,22 @@ object Types { final def classSymbols(using Context): List[ClassSymbol] = parentSymbols(_.isClass).asInstanceOf + /** Same as `this.classSymbols.contains(cls)` but more efficient */ + final def hasClassSymbol(cls: Symbol)(using Context): Boolean = this match + case tp: TypeRef => + val sym = tp.symbol + sym == cls || !sym.isClass && tp.superType.hasClassSymbol(cls) + case tp: TypeProxy => + tp.underlying.hasClassSymbol(cls) + case tp: ClassInfo => + tp.cls == cls + case AndType(l, r) => + l.hasClassSymbol(cls) || r.hasClassSymbol(cls) + case OrType(l, r) => + l.hasClassSymbol(cls) && r.hasClassSymbol(cls) + case _ => + false + /** The term symbol associated with the type */ @tailrec final def termSymbol(using Context): Symbol = this match { case tp: TermRef => tp.symbol From 0969035e901b01fe78564f77f149c85ae9cd7250 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 11 Nov 2020 11:38:51 +0100 Subject: [PATCH 7/7] Clean up naming of top and bottom class tests isNothing -> isExactlyNothing isTopType -> isExactlyAny Note: there is alrady an isAny, which is isRef(AnyClass) and therefore more general. Also: Move isBottomType from defn to Type. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +- .../dotty/tools/dotc/core/Definitions.scala | 7 --- .../tools/dotc/core/TypeApplications.scala | 8 +-- .../dotty/tools/dotc/core/TypeComparer.scala | 4 +- .../dotty/tools/dotc/core/TypeErasure.scala | 4 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 58 +++++++++++-------- .../tools/dotc/interactive/Completion.scala | 2 +- .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../tools/dotc/transform/PatternMatcher.scala | 2 +- .../dotc/transform/TransformByNameApply.scala | 2 +- .../tools/dotc/typer/ErrorReporting.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- 13 files changed, 50 insertions(+), 49 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 4528ac8d7bb6..dc8ad95dd0db 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -586,7 +586,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { val tree2: Select = tree.tpe match { case tpe: NamedType => val qualType = qualifier.tpe.widenIfUnstable - if qualType.isNothing then tree1.withTypeUnchecked(tree.tpe) + if qualType.isExactlyNothing then tree1.withTypeUnchecked(tree.tpe) else tree1.withType(tpe.derivedSelect(qualType)) case _ => tree1.withTypeUnchecked(tree.tpe) } @@ -980,7 +980,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** `tree ne null` (might need a cast to be type correct) */ def testNotNull(using Context): Tree = { - val receiver = if (defn.isBottomType(tree.tpe)) + val receiver = if (tree.tpe.isBottomType) // If the receiver is of type `Nothing` or `Null`, add an ascription so that the selection // succeeds: e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does. Typed(tree, TypeTree(defn.AnyRefType)) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 08230dee1548..dc1a632ee08a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1174,13 +1174,6 @@ class Definitions { def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass - def isBottomType(tp: Type): Boolean = - if ctx.explicitNulls && !ctx.phase.erasedTypes then tp.hasClassSymbol(NothingClass) - else isBottomTypeAfterErasure(tp) - - def isBottomTypeAfterErasure(tp: Type): Boolean = - tp.hasClassSymbol(NothingClass) || tp.hasClassSymbol(NullClass) - /** Is a function class. * - FunctionXXL * - FunctionN for N >= 0 diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index d82d5bb92854..d92549acfbf7 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -407,8 +407,8 @@ class TypeApplications(val self: Type) extends AnyVal { if (self.derivesFrom(from)) { def elemType(tp: Type): Type = tp.widenDealias match case tp: OrType => - if defn.isBottomType(tp.tp1) then elemType(tp.tp2) - else if defn.isBottomType(tp.tp2) then elemType(tp.tp1) + if tp.tp1.isBottomType then elemType(tp.tp2) + else if tp.tp2.isBottomType then elemType(tp.tp1) else tp.derivedOrType(elemType(tp.tp1), elemType(tp.tp2)) case tp: AndType => tp.derivedAndType(elemType(tp.tp1), elemType(tp.tp2)) case _ => tp.baseType(from).argInfos.headOption.getOrElse(defn.NothingType) @@ -503,8 +503,8 @@ class TypeApplications(val self: Type) extends AnyVal { def elemType(using Context): Type = self.widenDealias match { case defn.ArrayOf(elemtp) => elemtp case JavaArrayType(elemtp) => elemtp - case tp: OrType if defn.isBottomType(tp.tp1) => tp.tp2.elemType - case tp: OrType if defn.isBottomType(tp.tp2) => tp.tp1.elemType + case tp: OrType if tp.tp1.isBottomType => tp.tp2.elemType + case tp: OrType if tp.tp2.isBottomType => tp.tp1.elemType case _ => self.baseType(defn.SeqClass).argInfos.headOption.getOrElse(NoType) } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index baaa1fb58afa..669fa51260d5 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -715,7 +715,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } compareTypeBounds case tp2: AnnotatedType if tp2.isRefining => - (tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || defn.isBottomType(tp1)) && + (tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || tp1.isBottomType) && recur(tp1, tp2.parent) case ClassInfo(pre2, cls2, _, _, _) => def compareClassInfo = tp1 match { @@ -2338,7 +2338,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling */ def provablyEmpty(tp: Type): Boolean = tp.dealias match { - case tp if tp.isNothing => true + case tp if tp.isExactlyNothing => true case AndType(tp1, tp2) => provablyDisjoint(tp1, tp2) case OrType(tp1, tp2) => provablyEmpty(tp1) && provablyEmpty(tp2) case at @ AppliedType(tycon, args) => diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index e8726f9eb73b..d142ccd5f5ac 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -291,8 +291,8 @@ object TypeErasure { // We need to short-circuit this case here because the regular lub logic below // relies on the class hierarchy, which doesn't properly capture `Null`s subtyping // behaviour. - if (defn.isBottomTypeAfterErasure(tp1) && tp2.derivesFrom(defn.ObjectClass)) return tp2 - if (defn.isBottomTypeAfterErasure(tp2) && tp1.derivesFrom(defn.ObjectClass)) return tp1 + if (tp1.isBottomTypeAfterErasure && tp2.derivesFrom(defn.ObjectClass)) return tp2 + if (tp2.isBottomTypeAfterErasure && tp1.derivesFrom(defn.ObjectClass)) return tp1 tp1 match { case JavaArrayType(elem1) => import dotty.tools.dotc.transform.TypeUtils._ diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1498eec4bbbd..f5814935cfb6 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -152,7 +152,7 @@ object TypeOps: simplify(l, theMap) & simplify(r, theMap) case tp as OrType(l, r) if !ctx.mode.is(Mode.Type) - && (tp.isSoft || defn.isBottomType(l) || defn.isBottomType(r)) => + && (tp.isSoft || l.isBottomType || r.isBottomType) => // Normalize A | Null and Null | A to A even if the union is hard (i.e. // explicitly declared), but not if -Yexplicit-nulls is set. The reason is // that in this case the normal asSeenFrom machinery is not prepared to deal diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1237834401a2..483802d6e673 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -209,7 +209,27 @@ object Types { def isAnyRef(using Context): Boolean = isRef(defn.ObjectClass, skipRefined = false) def isAnyKind(using Context): Boolean = isRef(defn.AnyKindClass, skipRefined = false) - def isFromJavaObject(using Context): Boolean = typeSymbol eq defn.FromJavaObjectSymbol + /** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */ + def isExactlyNothing(using Context): Boolean = this match { + case tp: TypeRef => + tp.name == tpnme.Nothing && (tp.symbol eq defn.NothingClass) + case _ => false + } + + /** Is this type exactly Any (no vars, aliases, refinements etc allowed)? */ + def isExactlyAny(using Context): Boolean = this match { + case tp: TypeRef => + tp.name == tpnme.Any && (tp.symbol eq defn.AnyClass) + case _ => false + } + + def isBottomType(using Context): Boolean = + if ctx.explicitNulls && !ctx.phase.erasedTypes then hasClassSymbol(defn.NothingClass) + else isBottomTypeAfterErasure + + def isBottomTypeAfterErasure(using Context): Boolean = + val d = defn + hasClassSymbol(d.NothingClass) || hasClassSymbol(d.NullClass) /** Does this type refer exactly to class symbol `sym`, instead of to a subclass of `sym`? * Implemented like `isRef`, but follows more types: all type proxies as well as and- and or-types @@ -244,9 +264,9 @@ object Types { // If the type is `T | Null` or `T | Nothing`, the class is != Nothing, // and `T` derivesFrom the class, then the OrType derivesFrom the class. // Otherwise, we need to check both sides derivesFrom the class. - if defn.isBottomType(tp.tp1) && cls != defn.NothingClass then + if tp.tp1.isBottomType && cls != defn.NothingClass then loop(tp.tp2) - else if defn.isBottomType(tp.tp2) && cls != defn.NothingClass then + else if tp.tp2.isBottomType && cls != defn.NothingClass then loop(tp.tp1) else loop(tp.tp1) && loop(tp.tp2) @@ -258,6 +278,8 @@ object Types { loop(this) } + def isFromJavaObject(using Context): Boolean = typeSymbol eq defn.FromJavaObjectSymbol + /** True iff `symd` is a denotation of a class type parameter and the reference * ` . ` is an actual argument reference, i.e. `this` is different * from the ThisType of `symd`'s owner. @@ -271,20 +293,6 @@ object Types { } } - /** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */ - def isNothing(using Context): Boolean = this match { - case tp: TypeRef => - tp.name == tpnme.Nothing && (tp.symbol eq defn.NothingClass) - case _ => false - } - - /** Is this type exactly Any (no vars, aliases, refinements etc allowed)? */ - def isTopType(using Context): Boolean = this match { - case tp: TypeRef => - tp.name == tpnme.Any && (tp.symbol eq defn.AnyClass) - case _ => false - } - /** Is this type a (possibly aliased) singleton type? */ def isSingleton(using Context): Boolean = dealias.isInstanceOf[SingletonType] @@ -2156,8 +2164,8 @@ object Types { case arg: TypeBounds => val v = param.paramVarianceSign val pbounds = param.paramInfo - if (v > 0 && pbounds.loBound.dealiasKeepAnnots.isNothing) TypeAlias(arg.hiBound & rebase(pbounds.hiBound)) - else if (v < 0 && pbounds.hiBound.dealiasKeepAnnots.isTopType) TypeAlias(arg.loBound | rebase(pbounds.loBound)) + if (v > 0 && pbounds.loBound.dealiasKeepAnnots.isExactlyNothing) TypeAlias(arg.hiBound & rebase(pbounds.hiBound)) + else if (v < 0 && pbounds.hiBound.dealiasKeepAnnots.isExactlyAny) TypeAlias(arg.loBound | rebase(pbounds.loBound)) else arg recoverable_& rebase(pbounds) case arg => TypeAlias(arg) } @@ -2319,7 +2327,7 @@ object Types { if (base.isAnd == variance >= 0) tp1 & tp2 else tp1 | tp2 case _ => if (pre.termSymbol.is(Package)) argForParam(pre.select(nme.PACKAGE)) - else if (pre.isNothing) pre + else if (pre.isExactlyNothing) pre else NoType } } @@ -2338,7 +2346,7 @@ object Types { */ def derivedSelect(prefix: Type)(using Context): Type = if (prefix eq this.prefix) this - else if (prefix.isNothing) prefix + else if (prefix.isExactlyNothing) prefix else { if (isType) { val res = @@ -4326,7 +4334,7 @@ object Types { /** For uninstantiated type variables: Is the lower bound different from Nothing? */ def hasLowerBound(using Context): Boolean = - !ctx.typerState.constraint.entry(origin).loBound.isNothing + !ctx.typerState.constraint.entry(origin).loBound.isExactlyNothing /** For uninstantiated type variables: Is the upper bound different from Any? */ def hasUpperBound(using Context): Boolean = @@ -5331,7 +5339,7 @@ object Types { case _ => def propagate(lo: Type, hi: Type) = range(derivedRefinedType(tp, parent, lo), derivedRefinedType(tp, parent, hi)) - if (parent.isNothing) parent + if (parent.isExactlyNothing) parent else info match { case Range(infoLo: TypeBounds, infoHi: TypeBounds) => assert(variance == 0) @@ -5424,7 +5432,7 @@ object Types { case Range(lo, hi) => range(tp.derivedAnnotatedType(lo, annot), tp.derivedAnnotatedType(hi, annot)) case _ => - if (underlying.isNothing) underlying + if (underlying.isExactlyNothing) underlying else tp.derivedAnnotatedType(underlying, annot) } override protected def derivedWildcardType(tp: WildcardType, bounds: Type): WildcardType = @@ -5672,7 +5680,7 @@ object Types { else { seen += tp tp match { - case tp if tp.isTopType || tp.isNothing => + case tp if tp.isExactlyAny || tp.isExactlyNothing => cs case tp: AppliedType => foldOver(cs + tp.typeSymbol, tp) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index a3c84d114b95..41b6ac43c714 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -205,7 +205,7 @@ object Completion { * considered. */ def addMemberCompletions(qual: Tree)(using Context): Unit = - if (!qual.tpe.widenDealias.isNothing) { + if (!qual.tpe.widenDealias.isExactlyNothing) { addAccessibleMembers(qual.tpe) if (!mode.is(Mode.Import) && !qual.tpe.isNullType) // Implicit conversions do not kick in when importing diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 512197e98b3f..16a8dfe0d50f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -269,7 +269,7 @@ import transform.SymUtils._ if expected.isAny || expected.isAnyRef || expected.isRef(defn.AnyValClass) - || defn.isBottomType(found) + || found.isBottomType then "" else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected)) val (where, printCtx) = Formatting.disambiguateTypes(found2, expected2) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 455b8c205619..a20f11e3cf9c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -373,7 +373,7 @@ object PatternMatcher { patternPlan(casted, pat, onSuccess) }) case UnApply(extractor, implicits, args) => - val unappPlan = if (defn.isBottomType(scrutinee.info)) + val unappPlan = if (scrutinee.info.isBottomType) // Generate a throwaway but type-correct plan. // This plan will never execute because it'll be guarded by a `NonNullTest`. ResultPlan(tpd.Throw(tpd.nullLiteral)) diff --git a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala index e4eaf483dc0d..867c3329d944 100644 --- a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala +++ b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala @@ -42,7 +42,7 @@ abstract class TransformByNameApply extends MiniPhase { thisPhase: DenotTransfor def transformArg(arg: Tree, formal: Type): Tree = formal.dealias match { case formalExpr: ExprType => var argType = arg.tpe.widenIfUnstable - if (defn.isBottomType(argType)) argType = formal.widenExpr + if (argType.isBottomType) argType = formal.widenExpr def wrap(arg: Tree) = ref(defn.cbnArg).appliedToType(argType).appliedTo(arg).withSpan(arg.span) arg match { diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 802bb5a6b3ea..e7bc3f120db2 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -165,7 +165,7 @@ object ErrorReporting { |Note that `${tree.name}` is treated as an infix operator in Scala 3. |If you do not want that, insert a `;` or empty line in front |or drop any spaces behind the operator.""" - else if qualType.isNothing then + else if qualType.isExactlyNothing then "" else val add = suggestImports( diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 180be8d83128..06fd0389cd2c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -715,7 +715,7 @@ trait ImplicitRunInfo: seen += t t.underlying match case TypeBounds(lo, hi) => - if defn.isBottomTypeAfterErasure(lo) then apply(hi) + if lo.isBottomTypeAfterErasure then apply(hi) else AndType.make(apply(lo), apply(hi)) case u => apply(u)