Skip to content

Commit 3b741d6

Browse files
authored
Merge pull request #11195 from dotty-staging/add-safenull-mode
Add mode bit to track whether we are in safe nulls mode
2 parents c540e94 + 1f993b7 commit 3b741d6

File tree

13 files changed

+162
-76
lines changed

13 files changed

+162
-76
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import printing.XprintMode
2424
import parsing.Parsers.Parser
2525
import parsing.JavaParsers.JavaParser
2626
import typer.ImplicitRunInfo
27+
import config.Feature
28+
import StdNames.nme
2729

2830
import java.io.{BufferedWriter, OutputStreamWriter}
2931
import java.nio.charset.StandardCharsets
@@ -71,11 +73,13 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
7173
.setPeriod(Period(comp.nextRunId, FirstPhaseId))
7274
.setScope(rootScope)
7375
rootScope.enter(ctx.definitions.RootPackage)(using bootstrap)
74-
val start = bootstrap.fresh
76+
var start = bootstrap.fresh
7577
.setOwner(defn.RootClass)
7678
.setTyper(new Typer)
7779
.addMode(Mode.ImplicitsEnabled)
7880
.setTyperState(ctx.typerState.fresh(ctx.reporter))
81+
if ctx.settings.YexplicitNulls.value && !Feature.enabledBySetting(nme.unsafeNulls) then
82+
start = start.addMode(Mode.SafeNulls)
7983
ctx.initialize()(using start) // re-initialize the base context with start
8084
start.setRun(this)
8185
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,28 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
251251
case TypeDefs(_) => true
252252
case _ => isUsingClause(params)
253253

254+
/** If `path` looks like a language import, `Some(name)` where name
255+
* is `experimental` if that sub-module is imported, and the empty
256+
* term name otherwise.
257+
*/
258+
def languageImport(path: Tree): Option[TermName] = path match
259+
case Select(p1, nme.experimental) =>
260+
languageImport(p1) match
261+
case Some(EmptyTermName) => Some(nme.experimental)
262+
case _ => None
263+
case p1: RefTree if p1.name == nme.language =>
264+
p1.qualifier match
265+
case EmptyTree => Some(EmptyTermName)
266+
case p2: RefTree if p2.name == nme.scala =>
267+
p2.qualifier match
268+
case EmptyTree => Some(EmptyTermName)
269+
case Ident(nme.ROOTPKG) => Some(EmptyTermName)
270+
case _ => None
271+
case _ => None
272+
case _ => None
273+
274+
def isLanguageImport(path: Tree): Boolean = languageImport(path).isDefined
275+
254276
/** The underlying pattern ignoring any bindings */
255277
def unbind(x: Tree): Tree = unsplice(x) match {
256278
case Bind(_, y) => unbind(y)

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ import Decorators.{_, given}
99
import util.SrcPos
1010
import SourceVersion._
1111
import reporting.Message
12+
import NameKinds.QualifiedName
1213

1314
object Feature:
1415

15-
private val dependent = "dependent".toTermName
16-
private val namedTypeArguments = "namedTypeArguments".toTermName
17-
private val genericNumberLiterals = "genericNumberLiterals".toTermName
16+
private def experimental(str: String): TermName =
17+
QualifiedName(nme.experimental, str.toTermName)
18+
19+
private val Xdependent = experimental("dependent")
20+
private val XnamedTypeArguments = experimental("namedTypeArguments")
21+
private val XgenericNumberLiterals = experimental("genericNumberLiterals")
22+
private val Xmacros = experimental("macros")
1823

1924
/** Is `feature` enabled by by a command-line setting? The enabling setting is
2025
*
@@ -23,12 +28,8 @@ object Feature:
2328
* where <prefix> is the fully qualified name of `owner`, followed by a ".",
2429
* but subtracting the prefix `scala.language.` at the front.
2530
*/
26-
def enabledBySetting(feature: TermName, owner: Symbol = NoSymbol)(using Context): Boolean =
27-
def toPrefix(sym: Symbol): String =
28-
if !sym.exists || sym == defn.LanguageModule.moduleClass then ""
29-
else toPrefix(sym.owner) + sym.name.stripModuleClassSuffix + "."
30-
val prefix = if owner ne NoSymbol then toPrefix(owner) else ""
31-
ctx.base.settings.language.value.contains(prefix + feature)
31+
def enabledBySetting(feature: TermName)(using Context): Boolean =
32+
ctx.base.settings.language.value.contains(feature.toString)
3233

3334
/** Is `feature` enabled by by an import? This is the case if the feature
3435
* is imported by a named import
@@ -39,39 +40,31 @@ object Feature:
3940
*
4041
* import owner.{ feature => _ }
4142
*/
42-
def enabledByImport(feature: TermName, owner: Symbol = NoSymbol)(using Context): Boolean =
43-
atPhase(typerPhase) {
44-
ctx.importInfo != null
45-
&& ctx.importInfo.featureImported(feature,
46-
if owner.exists then owner else defn.LanguageModule.moduleClass)
47-
}
43+
def enabledByImport(feature: TermName)(using Context): Boolean =
44+
//atPhase(typerPhase) {
45+
ctx.importInfo != null && ctx.importInfo.featureImported(feature)
46+
//}
4847

4948
/** Is `feature` enabled by either a command line setting or an import?
5049
* @param feature The name of the feature
5150
* @param owner The prefix symbol (nested in `scala.language`) where the
5251
* feature is defined.
5352
*/
54-
def enabled(feature: TermName, owner: Symbol = NoSymbol)(using Context): Boolean =
55-
enabledBySetting(feature, owner) || enabledByImport(feature, owner)
53+
def enabled(feature: TermName)(using Context): Boolean =
54+
enabledBySetting(feature) || enabledByImport(feature)
5655

5756
/** Is auto-tupling enabled? */
58-
def autoTuplingEnabled(using Context): Boolean =
59-
!enabled(nme.noAutoTupling)
57+
def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling)
6058

61-
def dynamicsEnabled(using Context): Boolean =
62-
enabled(nme.dynamics)
59+
def dynamicsEnabled(using Context): Boolean = enabled(nme.dynamics)
6360

64-
def dependentEnabled(using Context) =
65-
enabled(dependent, defn.LanguageExperimentalModule.moduleClass)
61+
def dependentEnabled(using Context) = enabled(Xdependent)
6662

67-
def namedTypeArgsEnabled(using Context) =
68-
enabled(namedTypeArguments, defn.LanguageExperimentalModule.moduleClass)
63+
def namedTypeArgsEnabled(using Context) = enabled(XnamedTypeArguments)
6964

70-
def genericNumberLiteralsEnabled(using Context) =
71-
enabled(genericNumberLiterals, defn.LanguageExperimentalModule.moduleClass)
65+
def genericNumberLiteralsEnabled(using Context) = enabled(XgenericNumberLiterals)
7266

73-
def scala2ExperimentalMacroEnabled(using Context) =
74-
enabled("macros".toTermName, defn.LanguageExperimentalModule.moduleClass)
67+
def scala2ExperimentalMacroEnabled(using Context) = enabled(Xmacros)
7568

7669
def sourceVersionSetting(using Context): SourceVersion =
7770
SourceVersion.valueOf(ctx.settings.source.value)

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import collection.mutable
2727
import printing._
2828
import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings}
2929
import classfile.ReusableDataReader
30+
import StdNames.nme
3031

3132
import scala.annotation.internal.sharable
3233

@@ -474,13 +475,8 @@ object Contexts {
474475
else fresh.setOwner(exprOwner)
475476

476477
/** A new context that summarizes an import statement */
477-
def importContext(imp: Import[?], sym: Symbol): FreshContext = {
478-
val impNameOpt = imp.expr match {
479-
case ref: RefTree[?] => Some(ref.name.asTermName)
480-
case _ => None
481-
}
482-
fresh.setImportInfo(ImportInfo(sym, imp.selectors, impNameOpt))
483-
}
478+
def importContext(imp: Import[?], sym: Symbol): FreshContext =
479+
fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr))
484480

485481
/** Is the debug option set? */
486482
def debug: Boolean = base.settings.Ydebug.value
@@ -632,7 +628,14 @@ object Contexts {
632628
def setRun(run: Run): this.type = updateStore(runLoc, run)
633629
def setProfiler(profiler: Profiler): this.type = updateStore(profilerLoc, profiler)
634630
def setNotNullInfos(notNullInfos: List[NotNullInfo]): this.type = updateStore(notNullInfosLoc, notNullInfos)
635-
def setImportInfo(importInfo: ImportInfo): this.type = updateStore(importInfoLoc, importInfo)
631+
def setImportInfo(importInfo: ImportInfo): this.type =
632+
importInfo.mentionsFeature(nme.unsafeNulls) match
633+
case Some(true) =>
634+
setMode(this.mode &~ Mode.SafeNulls)
635+
case Some(false) if ctx.settings.YexplicitNulls.value =>
636+
setMode(this.mode | Mode.SafeNulls)
637+
case _ =>
638+
updateStore(importInfoLoc, importInfo)
636639
def setTypeAssigner(typeAssigner: TypeAssigner): this.type = updateStore(typeAssignerLoc, typeAssigner)
637640

638641
def setProperty[T](key: Key[T], value: T): this.type =

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,7 @@ object Mode {
119119

120120
/** Are we resolving a TypeTest node? */
121121
val InTypeTest: Mode = newMode(27, "InTypeTest")
122+
123+
/** Are we enforcing null safety */
124+
val SafeNulls = newMode(28, "SafeNulls")
122125
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ object StdNames {
613613
val unapplySeq: N = "unapplySeq"
614614
val unbox: N = "unbox"
615615
val universe: N = "universe"
616+
val unsafeNulls: N = "unsafeNulls"
616617
val update: N = "update"
617618
val updateDynamic: N = "updateDynamic"
618619
val using: N = "using"

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3068,11 +3068,7 @@ object Parsers {
30683068

30693069
/** Create an import node and handle source version imports */
30703070
def mkImport(outermost: Boolean = false): ImportConstr = (tree, selectors) =>
3071-
val isLanguageImport = tree match
3072-
case Ident(nme.language) => true
3073-
case Select(Ident(nme.scala), nme.language) => true
3074-
case _ => false
3075-
if isLanguageImport then
3071+
if isLanguageImport(tree) then
30763072
for
30773073
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
30783074
if allSourceVersionNames.contains(imported)

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,9 +734,21 @@ trait Checking {
734734
}
735735

736736
/** Check that `path` is a legal prefix for an import clause */
737-
def checkLegalImportPath(path: Tree)(using Context): Unit = {
737+
def checkLegalImportPath(path: Tree)(using Context): Unit =
738738
checkLegalImportOrExportPath(path, "import prefix")
739-
}
739+
languageImport(path) match
740+
case Some(prefix) =>
741+
val required =
742+
if prefix == nme.experimental then defn.LanguageExperimentalModule
743+
else defn.LanguageModule
744+
if path.symbol != required then
745+
report.error(em"import looks like a language import, but refers to something else: ${path.symbol.showLocated}", path.srcPos)
746+
case None =>
747+
val foundClasses = path.tpe.classSymbols
748+
if foundClasses.contains(defn.LanguageModule.moduleClass)
749+
|| foundClasses.contains(defn.LanguageExperimentalModule.moduleClass)
750+
then
751+
report.error(em"no aliases can be used to refer to a language import", path.srcPos)
740752

741753
/** Check that `path` is a legal prefix for an export clause */
742754
def checkLegalExportPath(path: Tree, selectors: List[untpd.ImportSelector])(using Context): Unit =

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

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import config.SourceVersion
1313
import StdNames.nme
1414
import printing.Texts.Text
1515
import ProtoTypes.NoViewsAllowed.normalizedCompatible
16+
import NameKinds.QualifiedName
1617
import Decorators._
1718

1819
object ImportInfo {
@@ -33,7 +34,7 @@ object ImportInfo {
3334
val expr = tpd.Ident(ref.refFn()) // refFn must be called in the context of ImportInfo.sym
3435
tpd.Import(expr, selectors).symbol
3536

36-
ImportInfo(sym, selectors, None, isRootImport = true)
37+
ImportInfo(sym, selectors, untpd.EmptyTree, isRootImport = true)
3738

3839
extension (c: Context)
3940
def withRootImports(rootRefs: List[RootRef])(using Context): Context =
@@ -42,23 +43,27 @@ object ImportInfo {
4243
def withRootImports: Context =
4344
given Context = c
4445
c.withRootImports(defn.rootImportFns)
45-
4646
}
4747

4848
/** Info relating to an import clause
49-
* @param sym The import symbol defined by the clause
49+
* @param symf A function that computes the import symbol defined by the clause
5050
* @param selectors The selector clauses
51-
* @param symNameOpt Optionally, the name of the import symbol. None for root imports.
51+
* @param qualifier The import qualifier, or EmptyTree for root imports.
5252
* Defined for all explicit imports from ident or select nodes.
5353
* @param isRootImport true if this is one of the implicit imports of scala, java.lang,
5454
* scala.Predef in the start context, false otherwise.
5555
*/
5656
class ImportInfo(symf: Context ?=> Symbol,
5757
val selectors: List[untpd.ImportSelector],
58-
symNameOpt: Option[TermName],
58+
val qualifier: untpd.Tree,
5959
val isRootImport: Boolean = false) extends Showable {
6060

61-
def sym(using Context): Symbol = {
61+
private def symNameOpt = qualifier match {
62+
case ref: untpd.RefTree => Some(ref.name.asTermName)
63+
case _ => None
64+
}
65+
66+
def importSym(using Context): Symbol = {
6267
if (mySym == null) {
6368
mySym = symf
6469
assert(mySym != null)
@@ -68,7 +73,7 @@ class ImportInfo(symf: Context ?=> Symbol,
6873
private var mySym: Symbol = _
6974

7075
/** The (TermRef) type of the qualifier of the import clause */
71-
def site(using Context): Type = sym.info match {
76+
def site(using Context): Type = importSym.info match {
7277
case ImportType(expr) => expr.tpe
7378
case _ => NoType
7479
}
@@ -177,28 +182,50 @@ class ImportInfo(symf: Context ?=> Symbol,
177182
assert(myUnimported != null)
178183
myUnimported
179184

180-
private var myUnimported: Symbol = _
185+
private val isLanguageImport: Boolean = untpd.isLanguageImport(qualifier)
181186

182-
private var myOwner: Symbol = null
183-
private var myResults: SimpleIdentityMap[TermName, java.lang.Boolean] = SimpleIdentityMap.empty
187+
private var myUnimported: Symbol = _
184188

185-
/** Does this import clause or a preceding import clause import `owner.feature`? */
186-
def featureImported(feature: TermName, owner: Symbol)(using Context): Boolean =
189+
private var featureCache: SimpleIdentityMap[TermName, java.lang.Boolean] = SimpleIdentityMap.empty
187190

188-
def compute =
189-
val isImportOwner = site.typeSymbol.eq(owner)
190-
if isImportOwner && forwardMapping.contains(feature) then true
191-
else if isImportOwner && excluded.contains(feature) then false
192-
else
193-
var c = ctx.outer
194-
while c.importInfo eq ctx.importInfo do c = c.outer
195-
(c.importInfo != null) && c.importInfo.featureImported(feature, owner)(using c)
196-
197-
if myOwner.ne(owner) || !myResults.contains(feature) then
198-
myOwner = owner
199-
myResults = myResults.updated(feature, compute)
200-
myResults(feature)
201-
end featureImported
191+
/** Does this import clause or a preceding import clause enable or disable `feature`?
192+
* @param feature See featureImported for a description
193+
* @return Some(true) if `feature` is imported
194+
* Some(false) if `feature` is excluded
195+
* None if `feature` is not mentioned, or this is not a language import
196+
*/
197+
def mentionsFeature(feature: TermName)(using Context): Option[Boolean] =
198+
def test(prefix: TermName, feature: TermName): Option[Boolean] =
199+
untpd.languageImport(qualifier) match
200+
case Some(`prefix`) =>
201+
if forwardMapping.contains(feature) then Some(true)
202+
else if excluded.contains(feature) then Some(false)
203+
else None
204+
case _ => None
205+
feature match
206+
case QualifiedName(prefix, name) => test(prefix, name)
207+
case _ => test(EmptyTermName, feature)
208+
209+
/** Does this import clause or a preceding import clause enable `feature`?
210+
*
211+
* @param feature a possibly quailified name, e.g.
212+
* strictEquality
213+
* experimental.genericNumberLiterals
214+
*
215+
* An excluded feature such as `strictEquality => _` in a language import
216+
* means that preceding imports are not considered and the feature is not imported.
217+
*/
218+
def featureImported(feature: TermName)(using Context): Boolean =
219+
if !featureCache.contains(feature) then
220+
featureCache = featureCache.updated(feature,
221+
mentionsFeature(feature) match
222+
case Some(bv) => bv
223+
case None =>
224+
var c = ctx.outer
225+
while c.importInfo eq ctx.importInfo do c = c.outer
226+
(c.importInfo != null) && c.importInfo.featureImported(feature)(using c)
227+
)
228+
featureCache(feature)
202229

203230
def toText(printer: Printer): Text = printer.toText(this)
204231
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ trait ImportSuggestions:
122122
.flatMap(sym => rootsIn(sym.termRef))
123123
val imported =
124124
if ctx.importInfo eq ctx.outer.importInfo then Nil
125-
else ctx.importInfo.sym.info match
125+
else ctx.importInfo.importSym.info match
126126
case ImportType(expr) => rootsOnPath(expr.tpe)
127127
case _ => Nil
128128
defined ++ imported ++ recur(using ctx.outer)

0 commit comments

Comments
 (0)