From c48bf46f3d4c2e042274141619ccfd7a3e8d125b Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 1 Jun 2022 05:30:09 -0400 Subject: [PATCH 1/5] Fix checking ctx to carry correct modes --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../tools/dotc/transform/DropImports.scala | 30 +++++++++ .../dotty/tools/dotc/transform/Erasure.scala | 1 + .../dotty/tools/dotc/transform/MixinOps.scala | 10 ++- .../dotc/transform/PruneErasedDefs.scala | 9 --- .../tools/dotc/transform/TreeChecker.scala | 2 +- .../dotty/tools/dotc/typer/Nullables.scala | 4 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 13 +++- tests/explicit-nulls/pos/unsafe-block.scala | 67 +++++++++++++++++++ tests/explicit-nulls/pos/unsafe-chain.scala | 21 ++++-- 10 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/DropImports.scala create mode 100644 tests/explicit-nulls/pos/unsafe-block.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index ce4ed2d4e4e8..3dad756eb350 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -101,6 +101,7 @@ class Compiler { new TupleOptimizations, // Optimize generic operations on tuples new LetOverApply, // Lift blocks from receivers of applications new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify. + List(new DropImports) :: // Drop all (language-) imports List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements. List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types new PureStats, // Remove pure stats from blocks diff --git a/compiler/src/dotty/tools/dotc/transform/DropImports.scala b/compiler/src/dotty/tools/dotc/transform/DropImports.scala new file mode 100644 index 000000000000..7959f156c63e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DropImports.scala @@ -0,0 +1,30 @@ +package dotty.tools.dotc +package transform + +import ast.tpd +import core.* +import Contexts.* +import MegaPhase.MiniPhase +import Decorators.* + +/** This phase finally drops all (language-) imports. + * Since some of the language imports change the subtyping, + * we cannot check the trees before erasure. Therefore, this + * phase should be the last phase before erasure. + */ +object DropImports: + val name: String = "dropImports" + val description: String = "drop all (language-) imports." + +class DropImports extends MiniPhase: + import tpd._ + + override def phaseName: String = DropImports.name + + override def description: String = DropImports.description + + override def isCheckable: Boolean = false + + override def transformOther(tree: Tree)(using Context): Tree = tree match + case tree: Import => EmptyTree + case _ => tree diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 241e18e67880..b19d8fc13e48 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -153,6 +153,7 @@ class Erasure extends Phase with DenotTransformer { override def checkPostCondition(tree: tpd.Tree)(using Context): Unit = { assertErased(tree) tree match { + case _: tpd.Import => assert(false, i"illegal tree: $tree") case res: tpd.This => assert(!ExplicitOuter.referencesOuter(ctx.owner.lexicallyEnclosingClass, res), i"Reference to $res from ${ctx.owner.showLocated}") diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index 787a0d5be1df..fa1c09806893 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -6,6 +6,7 @@ import Symbols._, Types._, Contexts._, DenotTransformers._, Flags._ import util.Spans._ import SymUtils._ import StdNames._, NameOps._ +import typer.Nullables class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(using Context) { import ast.tpd._ @@ -80,13 +81,20 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(using Context) { prefss => val (targs, vargss) = splitArgs(prefss) val tapp = superRef(target).appliedToTypeTrees(targs) - vargss match + val rhs = vargss match case Nil | List(Nil) => // Overriding is somewhat loose about `()T` vs `=> T`, so just pick // whichever makes sense for `target` tapp.ensureApplied case _ => tapp.appliedToArgss(vargss) + if ctx.explicitNulls && target.is(JavaDefined) && !ctx.phase.erasedTypes then + // We may forward to a super Java member in resolveSuper phase. + // Since this is still before erasure, the type can be nullable + // and causes error during checking. So we need to enable + // unsafe-nulls to construct the rhs. + Block(Nullables.importUnsafeNulls :: Nil, rhs) + else rhs private def competingMethodsIterator(meth: Symbol): Iterator[Symbol] = cls.baseClasses.iterator diff --git a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala index 0ab852398049..568512207fde 100644 --- a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala @@ -20,7 +20,6 @@ import Decorators.* * The phase also replaces all expressions that appear in an erased context by * default values. This is necessary so that subsequent checking phases such * as IsInstanceOfChecker don't give false negatives. - * Finally, the phase drops (language-) imports. */ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform => import tpd._ @@ -56,18 +55,10 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform => checkErasedInExperimental(tree.symbol) tree - override def transformOther(tree: Tree)(using Context): Tree = tree match - case tree: Import => EmptyTree - case _ => tree - def checkErasedInExperimental(sym: Symbol)(using Context): Unit = // Make an exception for Scala 2 experimental macros to allow dual Scala 2/3 macros under non experimental mode if sym.is(Erased, butNot = Macro) && sym != defn.Compiletime_erasedValue && !sym.isInExperimentalScope then Feature.checkExperimentalFeature("erased", sym.sourcePos) - - override def checkPostCondition(tree: Tree)(using Context): Unit = tree match - case _: tpd.Import => assert(false, i"illegal tree: $tree") - case _ => } object PruneErasedDefs { diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 4f38af4d6198..695392c0ca60 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -134,7 +134,7 @@ class TreeChecker extends Phase with SymTransformer { val checkingCtx = ctx .fresh - .setMode(Mode.ImplicitsEnabled) + .addMode(Mode.ImplicitsEnabled) .setReporter(new ThrowingReporter(ctx.reporter)) val checker = inContext(ctx) { diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 08ab428e7203..0d4d23243f46 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -20,6 +20,10 @@ import ast.Trees.mods object Nullables: import ast.tpd._ + def importUnsafeNulls(using Context): Import = Import( + ref(defn.LanguageModule), + List(untpd.ImportSelector(untpd.Ident(nme.unsafeNulls), EmptyTree, EmptyTree))) + inline def unsafeNullsEnabled(using Context): Boolean = ctx.explicitNulls && !ctx.mode.is(Mode.SafeNulls) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9a62ac480c30..d181fdc999f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1059,7 +1059,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val (stats1, exprCtx) = withoutMode(Mode.Pattern) { typedBlockStats(tree.stats) } - val expr1 = typedExpr(tree.expr, pt.dropIfProto)(using exprCtx) + var expr1 = typedExpr(tree.expr, pt.dropIfProto)(using exprCtx) + + // If unsafe nulls is enabled inside a block but not enabled outside + // and the type does not conform the expected type without unsafe nulls, + // we will cast the last expression to the expected type. + // See: tests/explicit-nulls/pos/unsafe-block.scala + if ctx.mode.is(Mode.SafeNulls) + && !exprCtx.mode.is(Mode.SafeNulls) + && pt.isValueType + && !inContext(exprCtx.addMode(Mode.SafeNulls))(expr1.tpe <:< pt) then + expr1 = expr1.cast(pt) + ensureNoLocalRefs( cpy.Block(tree)(stats1, expr1) .withType(expr1.tpe) diff --git a/tests/explicit-nulls/pos/unsafe-block.scala b/tests/explicit-nulls/pos/unsafe-block.scala new file mode 100644 index 000000000000..1b103cf33269 --- /dev/null +++ b/tests/explicit-nulls/pos/unsafe-block.scala @@ -0,0 +1,67 @@ +def trim(x: String | Null): String = + import scala.language.unsafeNulls + // The type of `x.trim()` is `String | Null`. + // Although `String | Null` conforms the expected type `String`, + // we still need to cast the expression to the expected type here, + // because outside the scope we don't have `unsafeNulls` anymore. + x.trim() + +class TestDefs: + + def f1: String | Null = null + def f2: Array[String | Null] | Null = null + def f3: Array[String] | Null = null + + def h1a: String = + import scala.language.unsafeNulls + f1 + + def h1b: String | Null = + import scala.language.unsafeNulls + f1 + + def h2a: Array[String] = + import scala.language.unsafeNulls + f2 + + def h2b: Array[String | Null] = + import scala.language.unsafeNulls + f2 + + def h3a: Array[String] = + import scala.language.unsafeNulls + f3 + + def h3b: Array[String | Null] = + import scala.language.unsafeNulls + f3 + +class TestVals: + + val f1: String | Null = null + val f2: Array[String | Null] | Null = null + val f3: Array[String] | Null = null + + val h1a: String = + import scala.language.unsafeNulls + f1 + + val h1b: String | Null = + import scala.language.unsafeNulls + f1 + + val h2a: Array[String] = + import scala.language.unsafeNulls + f2 + + val h2b: Array[String | Null] = + import scala.language.unsafeNulls + f2 + + val h3a: Array[String] = + import scala.language.unsafeNulls + f3 + + val h3b: Array[String | Null] = + import scala.language.unsafeNulls + f3 \ No newline at end of file diff --git a/tests/explicit-nulls/pos/unsafe-chain.scala b/tests/explicit-nulls/pos/unsafe-chain.scala index 76c80d0c53fe..0ba52602d2dd 100644 --- a/tests/explicit-nulls/pos/unsafe-chain.scala +++ b/tests/explicit-nulls/pos/unsafe-chain.scala @@ -1,10 +1,21 @@ import java.nio.file.FileSystems import java.util.ArrayList -def directorySeparator: String = - import scala.language.unsafeNulls - FileSystems.getDefault().getSeparator() +class A: + + def directorySeparator: String = + import scala.language.unsafeNulls + FileSystems.getDefault().getSeparator() + + def getFirstOfFirst(xs: ArrayList[ArrayList[ArrayList[String]]]): String = + import scala.language.unsafeNulls + xs.get(0).get(0).get(0) -def getFirstOfFirst(xs: ArrayList[ArrayList[ArrayList[String]]]): String = +class B: import scala.language.unsafeNulls - xs.get(0).get(0).get(0) \ No newline at end of file + + def directorySeparator: String = + FileSystems.getDefault().getSeparator() + + def getFirstOfFirst(xs: ArrayList[ArrayList[ArrayList[String]]]): String = + xs.get(0).get(0).get(0) \ No newline at end of file From 0988a8d5254b5c7972a3b2d561d07d6d9fd8bee5 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 2 Jun 2022 19:40:17 -0400 Subject: [PATCH 2/5] Add DropImports to Erasure's runsAfter --- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index b19d8fc13e48..153a59266fe8 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -43,7 +43,7 @@ class Erasure extends Phase with DenotTransformer { override def description: String = Erasure.description /** List of names of phases that should precede this phase */ - override def runsAfter: Set[String] = Set(InterceptedMethods.name, ElimRepeated.name) + override def runsAfter: Set[String] = Set(InterceptedMethods.name, ElimRepeated.name, DropImports.name) override def changesMembers: Boolean = true // the phase adds bridges override def changesParents: Boolean = true // the phase drops Any From f37acc8286a08b5c94103cf666baab7426c5e2a3 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 14 Jun 2022 22:01:45 -0400 Subject: [PATCH 3/5] Remove DropImports; add Java Enum test --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 - .../tools/dotc/transform/DropImports.scala | 30 ------------------- .../dotty/tools/dotc/transform/Erasure.scala | 8 ++++- .../dotc/transform/SyntheticMembers.scala | 3 +- .../src/dotty/tools/dotc/typer/ReTyper.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 5 ++-- tests/explicit-nulls/pos/enums.scala | 12 ++++++++ 7 files changed, 25 insertions(+), 36 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/transform/DropImports.scala create mode 100644 tests/explicit-nulls/pos/enums.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 3dad756eb350..ce4ed2d4e4e8 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -101,7 +101,6 @@ class Compiler { new TupleOptimizations, // Optimize generic operations on tuples new LetOverApply, // Lift blocks from receivers of applications new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify. - List(new DropImports) :: // Drop all (language-) imports List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements. List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types new PureStats, // Remove pure stats from blocks diff --git a/compiler/src/dotty/tools/dotc/transform/DropImports.scala b/compiler/src/dotty/tools/dotc/transform/DropImports.scala deleted file mode 100644 index 7959f156c63e..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/DropImports.scala +++ /dev/null @@ -1,30 +0,0 @@ -package dotty.tools.dotc -package transform - -import ast.tpd -import core.* -import Contexts.* -import MegaPhase.MiniPhase -import Decorators.* - -/** This phase finally drops all (language-) imports. - * Since some of the language imports change the subtyping, - * we cannot check the trees before erasure. Therefore, this - * phase should be the last phase before erasure. - */ -object DropImports: - val name: String = "dropImports" - val description: String = "drop all (language-) imports." - -class DropImports extends MiniPhase: - import tpd._ - - override def phaseName: String = DropImports.name - - override def description: String = DropImports.description - - override def isCheckable: Boolean = false - - override def transformOther(tree: Tree)(using Context): Tree = tree match - case tree: Import => EmptyTree - case _ => tree diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 153a59266fe8..331857db67d3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -43,7 +43,7 @@ class Erasure extends Phase with DenotTransformer { override def description: String = Erasure.description /** List of names of phases that should precede this phase */ - override def runsAfter: Set[String] = Set(InterceptedMethods.name, ElimRepeated.name, DropImports.name) + override def runsAfter: Set[String] = Set(InterceptedMethods.name, ElimRepeated.name) override def changesMembers: Boolean = true // the phase adds bridges override def changesParents: Boolean = true // the phase drops Any @@ -1043,6 +1043,12 @@ object Erasure { (stats2.filterConserve(!_.isEmpty), finalCtx) } + /** Finally drops all (language-) imports in erasure. + * Since some of the language imports change the subtyping, + * we cannot check the trees before erasure. + */ + override def typedImport(tree: untpd.Import)(using Context) = EmptyTree + override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = trace(i"adapting ${tree.showSummary()}: ${tree.tpe} to $pt", show = true) { if ctx.phase != erasurePhase && ctx.phase != erasurePhase.next then diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index faccb79f3c9a..d5a17c2248f9 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -122,7 +122,8 @@ class SyntheticMembers(thisPhase: DenotTransformer) { def nameRef: Tree = if isJavaEnumValue then - Select(This(clazz), nme.name).ensureApplied + val name = Select(This(clazz), nme.name).ensureApplied + if ctx.explicitNulls then name.cast(defn.StringType) else name else identifierRef diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 385d71b570ca..ee5a156ca5c7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -51,7 +51,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def typedSuper(tree: untpd.Super, pt: Type)(using Context): Tree = promote(tree) - override def typedImport(tree: untpd.Import, sym: Symbol)(using Context): Tree = + override def typedImport(tree: untpd.Import)(using Context): Tree = promote(tree) override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d181fdc999f5..860ece62efab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2613,7 +2613,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer |The selector is not a member of an object or package.""") else typd(imp.expr, AnySelectionProto) - def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Tree = + def typedImport(imp: untpd.Import)(using Context): Tree = + val sym = retrieveSym(imp) val expr1 = typedImportQualifier(imp, typedExpr(_, _)(using ctx.withOwner(sym))) checkLegalImportPath(expr1) val selectors1 = typedSelectors(imp.selectors) @@ -2879,7 +2880,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.If => typedIf(tree, pt) case tree: untpd.Function => typedFunction(tree, pt) case tree: untpd.Closure => typedClosure(tree, pt) - case tree: untpd.Import => typedImport(tree, retrieveSym(tree)) + case tree: untpd.Import => typedImport(tree) case tree: untpd.Export => typedExport(tree) case tree: untpd.Match => typedMatch(tree, pt) case tree: untpd.Return => typedReturn(tree) diff --git a/tests/explicit-nulls/pos/enums.scala b/tests/explicit-nulls/pos/enums.scala new file mode 100644 index 000000000000..6d323331fa34 --- /dev/null +++ b/tests/explicit-nulls/pos/enums.scala @@ -0,0 +1,12 @@ +enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMessageID]: + + case NoExplanationID // errorNumber: -1 + case EmptyCatchOrFinallyBlockID extends ErrorMessageID(isActive = false) // errorNumber: 0 + + def errorNumber = ordinal - 1 + +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) + case Green extends Color(0x00FF00) + case Blue extends Color(0x0000FF) + From f5c01c1da7ff1feb6be6907c7997420382dd202a Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Wed, 15 Jun 2022 17:48:09 -0400 Subject: [PATCH 4/5] discard Imports first in typedStats --- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 331857db67d3..dd511f996f46 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -1035,7 +1035,8 @@ object Erasure { typed(tree.arg, pt) override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(using Context): (List[Tree], Context) = { - val stats0 = addRetainedInlineBodies(stats)(using preErasureCtx) + // discard Imports first, since Bridges will use tree's symbol + val stats0 = addRetainedInlineBodies(stats.filter(!_.isInstanceOf[untpd.Import]))(using preErasureCtx) val stats1 = if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats0) else stats0 From 6095a12994bd54c966be21daa2349b0e79d3fe38 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Thu, 16 Jun 2022 16:45:13 -0400 Subject: [PATCH 5/5] Fix generated testNotNull --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 17 +++++++++++------ tests/explicit-nulls/pos/test-not-null.scala | 5 +++++ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 tests/explicit-nulls/pos/test-not-null.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index d4cf61f35829..16604c9e83b1 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1010,12 +1010,17 @@ 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 (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)) - else tree.ensureConforms(defn.ObjectType) - receiver.select(defn.Object_ne).appliedTo(nullLiteral).withSpan(tree.span) + // If the receiver is of type `Nothing` or `Null`, add an ascription or cast + // so that the selection succeeds. + // e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does. + val receiver = + if tree.tpe.isBottomType then + if ctx.explicitNulls then tree.cast(defn.AnyRefType) + else Typed(tree, TypeTree(defn.AnyRefType)) + else tree.ensureConforms(defn.ObjectType) + // also need to cast the null literal to AnyRef in explicit nulls + val nullLit = if ctx.explicitNulls then nullLiteral.cast(defn.AnyRefType) else nullLiteral + receiver.select(defn.Object_ne).appliedTo(nullLit).withSpan(tree.span) } /** If inititializer tree is `_`, the default value of its type, diff --git a/tests/explicit-nulls/pos/test-not-null.scala b/tests/explicit-nulls/pos/test-not-null.scala new file mode 100644 index 000000000000..3cf17916dd40 --- /dev/null +++ b/tests/explicit-nulls/pos/test-not-null.scala @@ -0,0 +1,5 @@ +// testNotNull can be inserted during PatternMatcher +def f(xs: List[String]) = + xs.zipWithIndex.collect { + case (arg, idx) => idx + } \ No newline at end of file