Skip to content

Add mode bit to track whether we are in safe nulls mode #11195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import printing.XprintMode
import parsing.Parsers.Parser
import parsing.JavaParsers.JavaParser
import typer.ImplicitRunInfo
import config.Feature
import StdNames.nme

import java.io.{BufferedWriter, OutputStreamWriter}
import java.nio.charset.StandardCharsets
Expand Down Expand Up @@ -71,11 +73,13 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
.setPeriod(Period(comp.nextRunId, FirstPhaseId))
.setScope(rootScope)
rootScope.enter(ctx.definitions.RootPackage)(using bootstrap)
val start = bootstrap.fresh
var start = bootstrap.fresh
.setOwner(defn.RootClass)
.setTyper(new Typer)
.addMode(Mode.ImplicitsEnabled)
.setTyperState(ctx.typerState.fresh(ctx.reporter))
if ctx.settings.YexplicitNulls.value && !Feature.enabledBySetting(nme.unsafeNulls) then
start = start.addMode(Mode.SafeNulls)
ctx.initialize()(using start) // re-initialize the base context with start
start.setRun(this)
}
Expand Down
22 changes: 22 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,28 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
case TypeDefs(_) => true
case _ => isUsingClause(params)

/** If `path` looks like a language import, `Some(name)` where name
* is `experimental` if that sub-module is imported, and the empty
* term name otherwise.
*/
def languageImport(path: Tree): Option[TermName] = path match
case Select(p1, nme.experimental) =>
languageImport(p1) match
case Some(EmptyTermName) => Some(nme.experimental)
case _ => None
case p1: RefTree if p1.name == nme.language =>
p1.qualifier match
case EmptyTree => Some(EmptyTermName)
case p2: RefTree if p2.name == nme.scala =>
p2.qualifier match
case EmptyTree => Some(EmptyTermName)
case Ident(nme.ROOTPKG) => Some(EmptyTermName)
case _ => None
case _ => None
case _ => None

def isLanguageImport(path: Tree): Boolean = languageImport(path).isDefined

/** The underlying pattern ignoring any bindings */
def unbind(x: Tree): Tree = unsplice(x) match {
case Bind(_, y) => unbind(y)
Expand Down
51 changes: 22 additions & 29 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ import Decorators.{_, given}
import util.SrcPos
import SourceVersion._
import reporting.Message
import NameKinds.QualifiedName

object Feature:

private val dependent = "dependent".toTermName
private val namedTypeArguments = "namedTypeArguments".toTermName
private val genericNumberLiterals = "genericNumberLiterals".toTermName
private def experimental(str: String): TermName =
QualifiedName(nme.experimental, str.toTermName)

private val Xdependent = experimental("dependent")
private val XnamedTypeArguments = experimental("namedTypeArguments")
private val XgenericNumberLiterals = experimental("genericNumberLiterals")
private val Xmacros = experimental("macros")

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

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

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

/** Is auto-tupling enabled? */
def autoTuplingEnabled(using Context): Boolean =
!enabled(nme.noAutoTupling)
def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling)

def dynamicsEnabled(using Context): Boolean =
enabled(nme.dynamics)
def dynamicsEnabled(using Context): Boolean = enabled(nme.dynamics)

def dependentEnabled(using Context) =
enabled(dependent, defn.LanguageExperimentalModule.moduleClass)
def dependentEnabled(using Context) = enabled(Xdependent)

def namedTypeArgsEnabled(using Context) =
enabled(namedTypeArguments, defn.LanguageExperimentalModule.moduleClass)
def namedTypeArgsEnabled(using Context) = enabled(XnamedTypeArguments)

def genericNumberLiteralsEnabled(using Context) =
enabled(genericNumberLiterals, defn.LanguageExperimentalModule.moduleClass)
def genericNumberLiteralsEnabled(using Context) = enabled(XgenericNumberLiterals)

def scala2ExperimentalMacroEnabled(using Context) =
enabled("macros".toTermName, defn.LanguageExperimentalModule.moduleClass)
def scala2ExperimentalMacroEnabled(using Context) = enabled(Xmacros)

def sourceVersionSetting(using Context): SourceVersion =
SourceVersion.valueOf(ctx.settings.source.value)
Expand Down
19 changes: 11 additions & 8 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import collection.mutable
import printing._
import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings}
import classfile.ReusableDataReader
import StdNames.nme

import scala.annotation.internal.sharable

Expand Down Expand Up @@ -474,13 +475,8 @@ object Contexts {
else fresh.setOwner(exprOwner)

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

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

def setProperty[T](key: Key[T], value: T): this.type =
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,7 @@ object Mode {

/** Are we resolving a TypeTest node? */
val InTypeTest: Mode = newMode(27, "InTypeTest")

/** Are we enforcing null safety */
val SafeNulls = newMode(28, "SafeNulls")
}
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ object StdNames {
val unapplySeq: N = "unapplySeq"
val unbox: N = "unbox"
val universe: N = "universe"
val unsafeNulls: N = "unsafeNulls"
val update: N = "update"
val updateDynamic: N = "updateDynamic"
val using: N = "using"
Expand Down
6 changes: 1 addition & 5 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3068,11 +3068,7 @@ object Parsers {

/** Create an import node and handle source version imports */
def mkImport(outermost: Boolean = false): ImportConstr = (tree, selectors) =>
val isLanguageImport = tree match
case Ident(nme.language) => true
case Select(Ident(nme.scala), nme.language) => true
case _ => false
if isLanguageImport then
if isLanguageImport(tree) then
for
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
if allSourceVersionNames.contains(imported)
Expand Down
16 changes: 14 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -734,9 +734,21 @@ trait Checking {
}

/** Check that `path` is a legal prefix for an import clause */
def checkLegalImportPath(path: Tree)(using Context): Unit = {
def checkLegalImportPath(path: Tree)(using Context): Unit =
checkLegalImportOrExportPath(path, "import prefix")
}
languageImport(path) match
case Some(prefix) =>
val required =
if prefix == nme.experimental then defn.LanguageExperimentalModule
else defn.LanguageModule
if path.symbol != required then
report.error(em"import looks like a language import, but refers to something else: ${path.symbol.showLocated}", path.srcPos)
case None =>
val foundClasses = path.tpe.classSymbols
if foundClasses.contains(defn.LanguageModule.moduleClass)
|| foundClasses.contains(defn.LanguageExperimentalModule.moduleClass)
then
report.error(em"no aliases can be used to refer to a language import", path.srcPos)

/** Check that `path` is a legal prefix for an export clause */
def checkLegalExportPath(path: Tree, selectors: List[untpd.ImportSelector])(using Context): Unit =
Expand Down
79 changes: 53 additions & 26 deletions compiler/src/dotty/tools/dotc/typer/ImportInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import config.SourceVersion
import StdNames.nme
import printing.Texts.Text
import ProtoTypes.NoViewsAllowed.normalizedCompatible
import NameKinds.QualifiedName
import Decorators._

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

ImportInfo(sym, selectors, None, isRootImport = true)
ImportInfo(sym, selectors, untpd.EmptyTree, isRootImport = true)

extension (c: Context)
def withRootImports(rootRefs: List[RootRef])(using Context): Context =
Expand All @@ -42,23 +43,27 @@ object ImportInfo {
def withRootImports: Context =
given Context = c
c.withRootImports(defn.rootImportFns)

}

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

def sym(using Context): Symbol = {
private def symNameOpt = qualifier match {
case ref: untpd.RefTree => Some(ref.name.asTermName)
case _ => None
}

def importSym(using Context): Symbol = {
if (mySym == null) {
mySym = symf
assert(mySym != null)
Expand All @@ -68,7 +73,7 @@ class ImportInfo(symf: Context ?=> Symbol,
private var mySym: Symbol = _

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

private var myUnimported: Symbol = _
private val isLanguageImport: Boolean = untpd.isLanguageImport(qualifier)

private var myOwner: Symbol = null
private var myResults: SimpleIdentityMap[TermName, java.lang.Boolean] = SimpleIdentityMap.empty
private var myUnimported: Symbol = _

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

def compute =
val isImportOwner = site.typeSymbol.eq(owner)
if isImportOwner && forwardMapping.contains(feature) then true
else if isImportOwner && excluded.contains(feature) then false
else
var c = ctx.outer
while c.importInfo eq ctx.importInfo do c = c.outer
(c.importInfo != null) && c.importInfo.featureImported(feature, owner)(using c)

if myOwner.ne(owner) || !myResults.contains(feature) then
myOwner = owner
myResults = myResults.updated(feature, compute)
myResults(feature)
end featureImported
/** Does this import clause or a preceding import clause enable or disable `feature`?
* @param feature See featureImported for a description
* @return Some(true) if `feature` is imported
* Some(false) if `feature` is excluded
* None if `feature` is not mentioned, or this is not a language import
*/
def mentionsFeature(feature: TermName)(using Context): Option[Boolean] =
def test(prefix: TermName, feature: TermName): Option[Boolean] =
untpd.languageImport(qualifier) match
case Some(`prefix`) =>
if forwardMapping.contains(feature) then Some(true)
else if excluded.contains(feature) then Some(false)
else None
case _ => None
feature match
case QualifiedName(prefix, name) => test(prefix, name)
case _ => test(EmptyTermName, feature)

/** Does this import clause or a preceding import clause enable `feature`?
*
* @param feature a possibly quailified name, e.g.
* strictEquality
* experimental.genericNumberLiterals
*
* An excluded feature such as `strictEquality => _` in a language import
* means that preceding imports are not considered and the feature is not imported.
*/
def featureImported(feature: TermName)(using Context): Boolean =
if !featureCache.contains(feature) then
featureCache = featureCache.updated(feature,
mentionsFeature(feature) match
case Some(bv) => bv
case None =>
var c = ctx.outer
while c.importInfo eq ctx.importInfo do c = c.outer
(c.importInfo != null) && c.importInfo.featureImported(feature)(using c)
)
featureCache(feature)

def toText(printer: Printer): Text = printer.toText(this)
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ trait ImportSuggestions:
.flatMap(sym => rootsIn(sym.termRef))
val imported =
if ctx.importInfo eq ctx.outer.importInfo then Nil
else ctx.importInfo.sym.info match
else ctx.importInfo.importSym.info match
case ImportType(expr) => rootsOnPath(expr.tpe)
case _ => Nil
defined ++ imported ++ recur(using ctx.outer)
Expand Down
Loading