Skip to content

Commit 0059d1d

Browse files
authored
Merge pull request #15350 from dotty-staging/fix-check-ctx
Fix checking ctx to carry correct modes
2 parents de3a82c + 6095a12 commit 0059d1d

13 files changed

+152
-28
lines changed

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,12 +1010,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
10101010

10111011
/** `tree ne null` (might need a cast to be type correct) */
10121012
def testNotNull(using Context): Tree = {
1013-
val receiver = if (tree.tpe.isBottomType)
1014-
// If the receiver is of type `Nothing` or `Null`, add an ascription so that the selection
1015-
// succeeds: e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does.
1016-
Typed(tree, TypeTree(defn.AnyRefType))
1017-
else tree.ensureConforms(defn.ObjectType)
1018-
receiver.select(defn.Object_ne).appliedTo(nullLiteral).withSpan(tree.span)
1013+
// If the receiver is of type `Nothing` or `Null`, add an ascription or cast
1014+
// so that the selection succeeds.
1015+
// e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does.
1016+
val receiver =
1017+
if tree.tpe.isBottomType then
1018+
if ctx.explicitNulls then tree.cast(defn.AnyRefType)
1019+
else Typed(tree, TypeTree(defn.AnyRefType))
1020+
else tree.ensureConforms(defn.ObjectType)
1021+
// also need to cast the null literal to AnyRef in explicit nulls
1022+
val nullLit = if ctx.explicitNulls then nullLiteral.cast(defn.AnyRefType) else nullLiteral
1023+
receiver.select(defn.Object_ne).appliedTo(nullLit).withSpan(tree.span)
10191024
}
10201025

10211026
/** If inititializer tree is `_`, the default value of its type,

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ class Erasure extends Phase with DenotTransformer {
153153
override def checkPostCondition(tree: tpd.Tree)(using Context): Unit = {
154154
assertErased(tree)
155155
tree match {
156+
case _: tpd.Import => assert(false, i"illegal tree: $tree")
156157
case res: tpd.This =>
157158
assert(!ExplicitOuter.referencesOuter(ctx.owner.lexicallyEnclosingClass, res),
158159
i"Reference to $res from ${ctx.owner.showLocated}")
@@ -1034,14 +1035,21 @@ object Erasure {
10341035
typed(tree.arg, pt)
10351036

10361037
override def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(using Context): (List[Tree], Context) = {
1037-
val stats0 = addRetainedInlineBodies(stats)(using preErasureCtx)
1038+
// discard Imports first, since Bridges will use tree's symbol
1039+
val stats0 = addRetainedInlineBodies(stats.filter(!_.isInstanceOf[untpd.Import]))(using preErasureCtx)
10381040
val stats1 =
10391041
if (takesBridges(ctx.owner)) new Bridges(ctx.owner.asClass, erasurePhase).add(stats0)
10401042
else stats0
10411043
val (stats2, finalCtx) = super.typedStats(stats1, exprOwner)
10421044
(stats2.filterConserve(!_.isEmpty), finalCtx)
10431045
}
10441046

1047+
/** Finally drops all (language-) imports in erasure.
1048+
* Since some of the language imports change the subtyping,
1049+
* we cannot check the trees before erasure.
1050+
*/
1051+
override def typedImport(tree: untpd.Import)(using Context) = EmptyTree
1052+
10451053
override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree =
10461054
trace(i"adapting ${tree.showSummary()}: ${tree.tpe} to $pt", show = true) {
10471055
if ctx.phase != erasurePhase && ctx.phase != erasurePhase.next then

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Symbols._, Types._, Contexts._, DenotTransformers._, Flags._
66
import util.Spans._
77
import SymUtils._
88
import StdNames._, NameOps._
9+
import typer.Nullables
910

1011
class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
1112
import ast.tpd._
@@ -80,13 +81,20 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
8081
prefss =>
8182
val (targs, vargss) = splitArgs(prefss)
8283
val tapp = superRef(target).appliedToTypeTrees(targs)
83-
vargss match
84+
val rhs = vargss match
8485
case Nil | List(Nil) =>
8586
// Overriding is somewhat loose about `()T` vs `=> T`, so just pick
8687
// whichever makes sense for `target`
8788
tapp.ensureApplied
8889
case _ =>
8990
tapp.appliedToArgss(vargss)
91+
if ctx.explicitNulls && target.is(JavaDefined) && !ctx.phase.erasedTypes then
92+
// We may forward to a super Java member in resolveSuper phase.
93+
// Since this is still before erasure, the type can be nullable
94+
// and causes error during checking. So we need to enable
95+
// unsafe-nulls to construct the rhs.
96+
Block(Nullables.importUnsafeNulls :: Nil, rhs)
97+
else rhs
9098

9199
private def competingMethodsIterator(meth: Symbol): Iterator[Symbol] =
92100
cls.baseClasses.iterator

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import Decorators.*
2020
* The phase also replaces all expressions that appear in an erased context by
2121
* default values. This is necessary so that subsequent checking phases such
2222
* as IsInstanceOfChecker don't give false negatives.
23-
* Finally, the phase drops (language-) imports.
2423
*/
2524
class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
2625
import tpd._
@@ -56,18 +55,10 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
5655
checkErasedInExperimental(tree.symbol)
5756
tree
5857

59-
override def transformOther(tree: Tree)(using Context): Tree = tree match
60-
case tree: Import => EmptyTree
61-
case _ => tree
62-
6358
def checkErasedInExperimental(sym: Symbol)(using Context): Unit =
6459
// Make an exception for Scala 2 experimental macros to allow dual Scala 2/3 macros under non experimental mode
6560
if sym.is(Erased, butNot = Macro) && sym != defn.Compiletime_erasedValue && !sym.isInExperimentalScope then
6661
Feature.checkExperimentalFeature("erased", sym.sourcePos)
67-
68-
override def checkPostCondition(tree: Tree)(using Context): Unit = tree match
69-
case _: tpd.Import => assert(false, i"illegal tree: $tree")
70-
case _ =>
7162
}
7263

7364
object PruneErasedDefs {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
122122

123123
def nameRef: Tree =
124124
if isJavaEnumValue then
125-
Select(This(clazz), nme.name).ensureApplied
125+
val name = Select(This(clazz), nme.name).ensureApplied
126+
if ctx.explicitNulls then name.cast(defn.StringType) else name
126127
else
127128
identifierRef
128129

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class TreeChecker extends Phase with SymTransformer {
134134

135135
val checkingCtx = ctx
136136
.fresh
137-
.setMode(Mode.ImplicitsEnabled)
137+
.addMode(Mode.ImplicitsEnabled)
138138
.setReporter(new ThrowingReporter(ctx.reporter))
139139

140140
val checker = inContext(ctx) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import ast.Trees.mods
2020
object Nullables:
2121
import ast.tpd._
2222

23+
def importUnsafeNulls(using Context): Import = Import(
24+
ref(defn.LanguageModule),
25+
List(untpd.ImportSelector(untpd.Ident(nme.unsafeNulls), EmptyTree, EmptyTree)))
26+
2327
inline def unsafeNullsEnabled(using Context): Boolean =
2428
ctx.explicitNulls && !ctx.mode.is(Mode.SafeNulls)
2529

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
5151
override def typedSuper(tree: untpd.Super, pt: Type)(using Context): Tree =
5252
promote(tree)
5353

54-
override def typedImport(tree: untpd.Import, sym: Symbol)(using Context): Tree =
54+
override def typedImport(tree: untpd.Import)(using Context): Tree =
5555
promote(tree)
5656

5757
override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = {

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
10591059
val (stats1, exprCtx) = withoutMode(Mode.Pattern) {
10601060
typedBlockStats(tree.stats)
10611061
}
1062-
val expr1 = typedExpr(tree.expr, pt.dropIfProto)(using exprCtx)
1062+
var expr1 = typedExpr(tree.expr, pt.dropIfProto)(using exprCtx)
1063+
1064+
// If unsafe nulls is enabled inside a block but not enabled outside
1065+
// and the type does not conform the expected type without unsafe nulls,
1066+
// we will cast the last expression to the expected type.
1067+
// See: tests/explicit-nulls/pos/unsafe-block.scala
1068+
if ctx.mode.is(Mode.SafeNulls)
1069+
&& !exprCtx.mode.is(Mode.SafeNulls)
1070+
&& pt.isValueType
1071+
&& !inContext(exprCtx.addMode(Mode.SafeNulls))(expr1.tpe <:< pt) then
1072+
expr1 = expr1.cast(pt)
1073+
10631074
ensureNoLocalRefs(
10641075
cpy.Block(tree)(stats1, expr1)
10651076
.withType(expr1.tpe)
@@ -2602,7 +2613,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
26022613
|The selector is not a member of an object or package.""")
26032614
else typd(imp.expr, AnySelectionProto)
26042615

2605-
def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Tree =
2616+
def typedImport(imp: untpd.Import)(using Context): Tree =
2617+
val sym = retrieveSym(imp)
26062618
val expr1 = typedImportQualifier(imp, typedExpr(_, _)(using ctx.withOwner(sym)))
26072619
checkLegalImportPath(expr1)
26082620
val selectors1 = typedSelectors(imp.selectors)
@@ -2869,7 +2881,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28692881
case tree: untpd.If => typedIf(tree, pt)
28702882
case tree: untpd.Function => typedFunction(tree, pt)
28712883
case tree: untpd.Closure => typedClosure(tree, pt)
2872-
case tree: untpd.Import => typedImport(tree, retrieveSym(tree))
2884+
case tree: untpd.Import => typedImport(tree)
28732885
case tree: untpd.Export => typedExport(tree)
28742886
case tree: untpd.Match => typedMatch(tree, pt)
28752887
case tree: untpd.Return => typedReturn(tree)

tests/explicit-nulls/pos/enums.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMessageID]:
2+
3+
case NoExplanationID // errorNumber: -1
4+
case EmptyCatchOrFinallyBlockID extends ErrorMessageID(isActive = false) // errorNumber: 0
5+
6+
def errorNumber = ordinal - 1
7+
8+
enum Color(val rgb: Int):
9+
case Red extends Color(0xFF0000)
10+
case Green extends Color(0x00FF00)
11+
case Blue extends Color(0x0000FF)
12+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// testNotNull can be inserted during PatternMatcher
2+
def f(xs: List[String]) =
3+
xs.zipWithIndex.collect {
4+
case (arg, idx) => idx
5+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
def trim(x: String | Null): String =
2+
import scala.language.unsafeNulls
3+
// The type of `x.trim()` is `String | Null`.
4+
// Although `String | Null` conforms the expected type `String`,
5+
// we still need to cast the expression to the expected type here,
6+
// because outside the scope we don't have `unsafeNulls` anymore.
7+
x.trim()
8+
9+
class TestDefs:
10+
11+
def f1: String | Null = null
12+
def f2: Array[String | Null] | Null = null
13+
def f3: Array[String] | Null = null
14+
15+
def h1a: String =
16+
import scala.language.unsafeNulls
17+
f1
18+
19+
def h1b: String | Null =
20+
import scala.language.unsafeNulls
21+
f1
22+
23+
def h2a: Array[String] =
24+
import scala.language.unsafeNulls
25+
f2
26+
27+
def h2b: Array[String | Null] =
28+
import scala.language.unsafeNulls
29+
f2
30+
31+
def h3a: Array[String] =
32+
import scala.language.unsafeNulls
33+
f3
34+
35+
def h3b: Array[String | Null] =
36+
import scala.language.unsafeNulls
37+
f3
38+
39+
class TestVals:
40+
41+
val f1: String | Null = null
42+
val f2: Array[String | Null] | Null = null
43+
val f3: Array[String] | Null = null
44+
45+
val h1a: String =
46+
import scala.language.unsafeNulls
47+
f1
48+
49+
val h1b: String | Null =
50+
import scala.language.unsafeNulls
51+
f1
52+
53+
val h2a: Array[String] =
54+
import scala.language.unsafeNulls
55+
f2
56+
57+
val h2b: Array[String | Null] =
58+
import scala.language.unsafeNulls
59+
f2
60+
61+
val h3a: Array[String] =
62+
import scala.language.unsafeNulls
63+
f3
64+
65+
val h3b: Array[String | Null] =
66+
import scala.language.unsafeNulls
67+
f3
Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
import java.nio.file.FileSystems
22
import java.util.ArrayList
33

4-
def directorySeparator: String =
5-
import scala.language.unsafeNulls
6-
FileSystems.getDefault().getSeparator()
4+
class A:
5+
6+
def directorySeparator: String =
7+
import scala.language.unsafeNulls
8+
FileSystems.getDefault().getSeparator()
9+
10+
def getFirstOfFirst(xs: ArrayList[ArrayList[ArrayList[String]]]): String =
11+
import scala.language.unsafeNulls
12+
xs.get(0).get(0).get(0)
713

8-
def getFirstOfFirst(xs: ArrayList[ArrayList[ArrayList[String]]]): String =
14+
class B:
915
import scala.language.unsafeNulls
10-
xs.get(0).get(0).get(0)
16+
17+
def directorySeparator: String =
18+
FileSystems.getDefault().getSeparator()
19+
20+
def getFirstOfFirst(xs: ArrayList[ArrayList[ArrayList[String]]]): String =
21+
xs.get(0).get(0).get(0)

0 commit comments

Comments
 (0)