Skip to content

Commit 2b15184

Browse files
authored
Merge pull request #7556 from dotty-staging/add-flow-analysis-without-notnull
Nullability Analysis without NotNull
2 parents 73a90dd + 016f471 commit 2b15184

29 files changed

+668
-79
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import util.SourceFile
55
import ast.{tpd, untpd}
66
import tpd.{Tree, TreeTraverser}
77
import typer.PrepareInlineable.InlineAccessors
8+
import typer.Nullables
89
import dotty.tools.dotc.core.Contexts.Context
910
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
1011
import dotty.tools.dotc.core.Symbols._
1112
import dotty.tools.dotc.transform.SymUtils._
1213
import util.{NoSource, SourceFile}
14+
import util.Spans.Span
1315
import core.Decorators._
1416

1517
class CompilationUnit protected (val source: SourceFile) {
@@ -42,6 +44,16 @@ class CompilationUnit protected (val source: SourceFile) {
4244
suspended = true
4345
ctx.run.suspendedUnits += this
4446
throw CompilationUnit.SuspendException()
47+
48+
private var myAssignmentSpans: Map[Int, List[Span]] = null
49+
50+
/** A map from (name-) offsets of all local variables in this compilation unit
51+
* that can be tracked for being not null to the list of spans of assignments
52+
* to these variables.
53+
*/
54+
def assignmentSpans(given Context): Map[Int, List[Span]] =
55+
if myAssignmentSpans == null then myAssignmentSpans = Nullables.assignmentSpans
56+
myAssignmentSpans
4557
}
4658

4759
object CompilationUnit {

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
8888
/** If this is a block, its expression part */
8989
def stripBlock(tree: Tree): Tree = unsplice(tree) match {
9090
case Block(_, expr) => stripBlock(expr)
91+
case Inlined(_, _, expr) => stripBlock(expr)
92+
case _ => tree
93+
}
94+
95+
def stripInlined(tree: Tree): Tree = unsplice(tree) match {
96+
case Inlined(_, _, expr) => stripInlined(expr)
9197
case _ => tree
9298
}
9399

@@ -391,7 +397,9 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
391397
if (fn.symbol.is(Erased) || fn.symbol == defn.InternalQuoted_typeQuote) Pure else exprPurity(fn)
392398
case Apply(fn, args) =>
393399
def isKnownPureOp(sym: Symbol) =
394-
sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass
400+
sym.owner.isPrimitiveValueClass
401+
|| sym.owner == defn.StringClass
402+
|| defn.pureMethods.contains(sym)
395403
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
396404
|| (fn.symbol.isStableMember && !fn.symbol.is(Lazy))
397405
|| fn.symbol.isPrimaryConstructor && fn.symbol.owner.isNoInitsClass) // TODO: include in isStable?

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -711,11 +711,19 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
711711

712712
class TimeTravellingTreeCopier extends TypedTreeCopier {
713713
override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): Apply =
714-
ta.assignType(untpdCpy.Apply(tree)(fun, args), fun, args)
714+
tree match
715+
case tree: Apply
716+
if (tree.fun eq fun) && (tree.args eq args)
717+
&& tree.tpe.isInstanceOf[ConstantType]
718+
&& isPureExpr(tree) => tree
719+
case _ =>
720+
ta.assignType(untpdCpy.Apply(tree)(fun, args), fun, args)
715721
// Note: Reassigning the original type if `fun` and `args` have the same types as before
716-
// does not work here: The computed type depends on the widened function type, not
717-
// the function type itself. A treetransform may keep the function type the
722+
// does not work here in general: The computed type depends on the widened function type, not
723+
// the function type itself. A tree transform may keep the function type the
718724
// same but its widened type might change.
725+
// However, we keep constant types of pure expressions. This uses the underlying assumptions
726+
// that pure functions yielding a constant will not change in later phases.
719727

720728
override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(implicit ctx: Context): TypeApply =
721729
ta.assignType(untpdCpy.TypeApply(tree)(fun, args), fun, args)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ class ScalaSettings extends Settings.SettingGroup {
162162

163163
// Extremely experimental language features
164164
val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Enable kind polymorphism (see https://dotty.epfl.ch/docs/reference/kind-polymorphism.html). Potentially unsound.")
165+
val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.")
165166

166167
/** Area-specific debug output */
167168
val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import ast.Trees._
1515
import ast.untpd
1616
import Flags.GivenOrImplicit
1717
import util.{FreshNameCreator, NoSource, SimpleIdentityMap, SourceFile}
18-
import typer.{Implicits, ImportInfo, Inliner, NamerContextOps, SearchHistory, SearchRoot, TypeAssigner, Typer}
18+
import typer.{Implicits, ImportInfo, Inliner, NamerContextOps, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables}
19+
import Nullables.{NotNullInfo, given}
1920
import Implicits.ContextualImplicits
2021
import config.Settings._
2122
import config.Config
@@ -47,7 +48,11 @@ object Contexts {
4748
private val (compilationUnitLoc, store6) = store5.newLocation[CompilationUnit]()
4849
private val (runLoc, store7) = store6.newLocation[Run]()
4950
private val (profilerLoc, store8) = store7.newLocation[Profiler]()
50-
private val initialStore = store8
51+
private val (notNullInfosLoc, store9) = store8.newLocation[List[NotNullInfo]]()
52+
private val initialStore = store9
53+
54+
/** The current context */
55+
def curCtx(given ctx: Context): Context = ctx
5156

5257
/** A context is passed basically everywhere in dotc.
5358
* This is convenient but carries the risk of captured contexts in
@@ -207,6 +212,9 @@ object Contexts {
207212
/** The current compiler-run profiler */
208213
def profiler: Profiler = store(profilerLoc)
209214

215+
/** The paths currently known to be not null */
216+
def notNullInfos = store(notNullInfosLoc)
217+
210218
/** The new implicit references that are introduced by this scope */
211219
protected var implicitsCache: ContextualImplicits = null
212220
def implicits: ContextualImplicits = {
@@ -556,6 +564,7 @@ object Contexts {
556564
def setRun(run: Run): this.type = updateStore(runLoc, run)
557565
def setProfiler(profiler: Profiler): this.type = updateStore(profilerLoc, profiler)
558566
def setFreshNames(freshNames: FreshNameCreator): this.type = updateStore(freshNamesLoc, freshNames)
567+
def setNotNullInfos(notNullInfos: List[NotNullInfo]): this.type = updateStore(notNullInfosLoc, notNullInfos)
559568

560569
def setProperty[T](key: Key[T], value: T): this.type =
561570
setMoreProperties(moreProperties.updated(key, value))
@@ -587,6 +596,17 @@ object Contexts {
587596
def setDebug: this.type = setSetting(base.settings.Ydebug, true)
588597
}
589598

599+
given (c: Context)
600+
def addNotNullInfo(info: NotNullInfo) =
601+
c.withNotNullInfos(c.notNullInfos.extendWith(info))
602+
603+
def addNotNullRefs(refs: Set[TermRef]) =
604+
c.addNotNullInfo(NotNullInfo(refs, Set()))
605+
606+
def withNotNullInfos(infos: List[NotNullInfo]): Context =
607+
if c.notNullInfos eq infos then c else c.fresh.setNotNullInfos(infos)
608+
609+
// TODO: Fix issue when converting ModeChanges and FreshModeChanges to extension givens
590610
implicit class ModeChanges(val c: Context) extends AnyVal {
591611
final def withModeBits(mode: Mode): Context =
592612
if (mode != c.mode) c.fresh.setMode(mode) else c
@@ -615,7 +635,9 @@ object Contexts {
615635
typeAssigner = TypeAssigner
616636
moreProperties = Map.empty
617637
source = NoSource
618-
store = initialStore.updated(settingsStateLoc, settingsGroup.defaultState)
638+
store = initialStore
639+
.updated(settingsStateLoc, settingsGroup.defaultState)
640+
.updated(notNullInfosLoc, Nil)
619641
typeComparer = new TypeComparer(this)
620642
searchHistory = new SearchRoot
621643
gadt = EmptyGadtConstraint

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ class Definitions {
310310
def ObjectMethods: List[TermSymbol] = List(Object_eq, Object_ne, Object_synchronized, Object_clone,
311311
Object_finalize, Object_notify, Object_notifyAll, Object_wait, Object_waitL, Object_waitLI)
312312

313+
/** Methods in Object and Any that do not have a side effect */
314+
@tu lazy val pureMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode,
315+
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne)
316+
313317
@tu lazy val AnyKindClass: ClassSymbol = {
314318
val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil)
315319
if (!ctx.settings.YnoKindPolymorphism.value)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ object StdNames {
195195
final val ExprApi: N = "ExprApi"
196196
final val Mirror: N = "Mirror"
197197
final val Nothing: N = "Nothing"
198+
final val NotNull: N = "NotNull"
198199
final val Null: N = "Null"
199200
final val Object: N = "Object"
200201
final val Product: N = "Product"
@@ -261,6 +262,7 @@ object StdNames {
261262
val MIRROR_PREFIX: N = "$m."
262263
val MIRROR_SHORT: N = "$m"
263264
val MIRROR_UNTYPED: N = "$m$untyped"
265+
val NOT_NULL: N = "$nn"
264266
val REIFY_FREE_PREFIX: N = "free$"
265267
val REIFY_FREE_THIS_SUFFIX: N = "$this"
266268
val REIFY_FREE_VALUE_SUFFIX: N = "$value"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1838,7 +1838,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
18381838
else if (!tp2.exists) tp1
18391839
else tp.derivedAndType(tp1, tp2)
18401840

1841-
/** If some (&-operand of) this type is a supertype of `sub` replace it with `NoType`.
1841+
/** If some (&-operand of) `tp` is a supertype of `sub` replace it with `NoType`.
18421842
*/
18431843
private def dropIfSuper(tp: Type, sub: Type): Type =
18441844
if (isSubTypeWhenFrozen(sub, tp)) NoType

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
8181
// called which we override to set the `approximated` flag.
8282
range(defn.NothingType, pre)
8383
else pre
84-
else if ((pre.termSymbol is Package) && !(thiscls is Package))
84+
else if (pre.termSymbol.is(Package) && !thiscls.is(Package))
8585
toPrefix(pre.select(nme.PACKAGE), cls, thiscls)
8686
else
8787
toPrefix(pre.baseType(cls).normalizedPrefix, cls.owner, thiscls)

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,8 +1090,9 @@ object Types {
10901090
* instead of `ArrayBuffer[? >: Int | A <: Int & A]`
10911091
*/
10921092
def widenUnion(implicit ctx: Context): Type = widen match {
1093-
case OrType(tp1, tp2) =>
1094-
ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match {
1093+
case tp @ OrType(tp1, tp2) =>
1094+
if tp1.isNull || tp2.isNull then tp
1095+
else ctx.typeComparer.lub(tp1.widenUnion, tp2.widenUnion, canConstrain = true) match {
10951096
case union: OrType => union.join
10961097
case res => res
10971098
}
@@ -1421,6 +1422,11 @@ object Types {
14211422
case _ => true
14221423
}
14231424

1425+
/** Is this (an alias of) the `scala.Null` type? */
1426+
final def isNull(given Context) =
1427+
isRef(defn.NullClass)
1428+
|| classSymbol.name == tpnme.Null // !!! temporary kludge for being able to test without the explicit nulls PR
1429+
14241430
/** The resultType of a LambdaType, or ExprType, the type itself for others */
14251431
def resultType(implicit ctx: Context): Type = this
14261432

@@ -2315,7 +2321,7 @@ object Types {
23152321
}
23162322

23172323
/** The singleton type for path prefix#myDesignator.
2318-
*/
2324+
*/
23192325
abstract case class TermRef(override val prefix: Type,
23202326
private var myDesignator: Designator)
23212327
extends NamedType with SingletonType with ImplicitRef {
@@ -2908,6 +2914,24 @@ object Types {
29082914
else apply(tp1, tp2)
29092915
}
29102916

2917+
/** An extractor for `T | Null` or `Null | T`, returning the `T` */
2918+
object OrNull with
2919+
private def stripNull(tp: Type)(given Context): Type = tp match
2920+
case tp @ OrType(tp1, tp2) =>
2921+
if tp1.isNull then tp2
2922+
else if tp2.isNull then tp1
2923+
else tp.derivedOrType(stripNull(tp1), stripNull(tp2))
2924+
case tp @ AndType(tp1, tp2) =>
2925+
tp.derivedAndType(stripNull(tp1), stripNull(tp2))
2926+
case _ =>
2927+
tp
2928+
def apply(tp: Type)(given Context) =
2929+
OrType(tp, defn.NullType)
2930+
def unapply(tp: Type)(given Context): Option[Type] =
2931+
val tp1 = stripNull(tp)
2932+
if tp1 ne tp then Some(tp1) else None
2933+
end OrNull
2934+
29112935
// ----- ExprType and LambdaTypes -----------------------------------
29122936

29132937
// Note: method types are cached whereas poly types are not. The reason

0 commit comments

Comments
 (0)