Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
bc972b2
remove flow typing from main branch
noti0na1 Nov 8, 2019
b48643f
remove NonNullTermRef
noti0na1 Nov 8, 2019
d59a792
Merge pull request #43 from noti0na1/dotty-explicit-nulls-only
abeln Nov 12, 2019
763b05c
add extractor for Null ops; remove useless imports; reduce side effects
noti0na1 Nov 14, 2019
00d6607
merge upstream (Nullability Analysis without NotNull #7556)
noti0na1 Nov 15, 2019
49a35e3
rewrite widenUnion
noti0na1 Nov 15, 2019
2df913f
Update types in DottyPredef
noti0na1 Nov 15, 2019
f009775
add comments for extractors
noti0na1 Nov 20, 2019
56cdadc
optimize widenUnion
noti0na1 Nov 20, 2019
5c7c312
modify eq tests
noti0na1 Nov 20, 2019
1b28c63
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 Nov 20, 2019
5ddcab6
remove redundent test
noti0na1 Nov 20, 2019
35dda77
Merge pull request #44 from noti0na1/dotty-explicit-nulls-only
noti0na1 Nov 20, 2019
334a430
fix normalizing nullable intersection type
noti0na1 Nov 21, 2019
b9cbc1f
remove JavaEnumValue from AfterLoadFlags
noti0na1 Nov 28, 2019
539051a
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 Nov 28, 2019
a5b6447
Merge pull request #45 from noti0na1/dotty-explicit-nulls-only
noti0na1 Nov 28, 2019
2a8cc49
Disallow comparison between object and null
noti0na1 Nov 29, 2019
423ef59
Fix case process
noti0na1 Dec 2, 2019
708250c
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 Dec 2, 2019
b12415f
Move explicit null case
noti0na1 Dec 2, 2019
a423df5
Merge pull request #46 from noti0na1/dotty-explicit-nulls-only
noti0na1 Dec 2, 2019
d12a9f1
add notNull to tree
noti0na1 Nov 20, 2019
b49aca9
add null check for paths
noti0na1 Nov 22, 2019
6a321ad
fix long stable path
noti0na1 Nov 25, 2019
b813439
better return type for Var; inline without extra val assign; better n…
noti0na1 Nov 26, 2019
ef5864e
add flow 'tests
noti0na1 Nov 29, 2019
7d40d5c
Fix isStable; fix var track in lazy val
noti0na1 Nov 29, 2019
ce606b6
Fix closure check
noti0na1 Dec 2, 2019
50b70ca
Add while, match tests
noti0na1 Dec 3, 2019
1eed780
Remove unused code
noti0na1 Dec 3, 2019
c5aab82
Update comments
noti0na1 Dec 3, 2019
465823d
Update doc, WIP
noti0na1 Dec 3, 2019
49d550e
Add flow typing to doc
noti0na1 Dec 4, 2019
bdbde1d
Simplify case
noti0na1 Dec 4, 2019
58db0c0
Refine comments
noti0na1 Dec 4, 2019
91b54aa
fix assert
noti0na1 Dec 4, 2019
61ca5e6
Add more comments and examples
noti0na1 Dec 4, 2019
46fc5cd
Merge pull request #47 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 Dec 5, 2019
0230180
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 Dec 5, 2019
3bb1b82
Merge pull request #48 from noti0na1/dotty-explicit-nulls-notNull
abeln Dec 5, 2019
77df754
add usedOutOfOrder
noti0na1 Dec 9, 2019
6bdb9f0
Add tests
noti0na1 Dec 10, 2019
7dc2e76
Optimize NullOps; add helper functions
noti0na1 Dec 11, 2019
0acba32
Edit comments
noti0na1 Dec 11, 2019
dea268a
Add suggested NotNull annots
noti0na1 Dec 12, 2019
d26b424
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 Dec 12, 2019
a8f3dc1
Merge pull request #49 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 Dec 12, 2019
2f42d1b
Update comments
noti0na1 Dec 12, 2019
1c15bef
Merge pull request #50 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 Dec 12, 2019
9a3a625
Fix typos
noti0na1 Dec 13, 2019
2af339f
Merge remote-tracking branch 'upstream/master' into dotty-explicit-nu…
noti0na1 Dec 13, 2019
c41b926
Rewrite the if stat
noti0na1 Dec 13, 2019
85c0855
Merge pull request #51 from noti0na1/dotty-explicit-nulls-notNull
noti0na1 Dec 13, 2019
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ class Definitions {
@tu lazy val Any_asInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, _.paramRefs(0), Final)
@tu lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact)
@tu lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable)
// generated by pattern matcher, eliminated by erasure
// generated by pattern matcher and exlicit nulls, eliminated by erasure

/** def getClass[A >: this.type](): Class[? <: A] */
@tu lazy val Any_getClass: TermSymbol =
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import SymDenotations._
import Decorators._
import Denotations._
import Periods._
import CheckRealizable._
import util.Stats._
import util.SimpleIdentitySet
import reporting.diagnostic.Message
Expand Down Expand Up @@ -163,6 +164,9 @@ object Types {
case tp: RefinedOrRecType => tp.parent.isStable
case tp: ExprType => tp.resultType.isStable
case tp: AnnotatedType => tp.parent.isStable
case tp: AndType =>
tp.tp1.isStable && (realizability(tp.tp2) eq Realizable) ||
tp.tp2.isStable && (realizability(tp.tp1) eq Realizable)
case _ => false
}

Expand Down
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/typer/ConstFold.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Names._
import StdNames._
import Contexts._
import Nullables.{CompareNull, TrackedRef}
import NullOpsDecorator._

object ConstFold {

Expand All @@ -20,10 +21,6 @@ object ConstFold {
/** If tree is a constant operation, replace with result. */
def apply[T <: Tree](tree: T)(implicit ctx: Context): T = finish(tree) {
tree match {
case CompareNull(TrackedRef(ref), testEqual)
if ctx.settings.YexplicitNulls.value && ctx.notNullInfos.impliesNotNull(ref) =>
// TODO maybe drop once we have general Nullability?
Constant(!testEqual)
case Apply(Select(xt, op), yt :: Nil) =>
xt.tpe.widenTermRefExpr.normalized match
case ConstantType(x) =>
Expand Down
55 changes: 53 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Nullables.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import util.Property
import Names.Name
import util.Spans.Span
import Flags.Mutable
import NullOpsDecorator._
import collection.mutable

/** Operations for implementing a flow analysis for nullability */
Expand Down Expand Up @@ -104,13 +105,57 @@ object Nullables with
* This is the case if the reference is a path to an immutable val, or if it refers
* to a local mutable variable where all assignments to the variable are _reachable_
* (in the sense of how it is defined in assignmentSpans).
*
* When dealing with local mutable variables, there are two questions:
*
* 1. Whether to track a local mutable variable during flow typing.
* We track a local mutable variable iff the variable is not assigned in a closure.
* For example, in the following code `x` is assigned to by the closure `y`, so we do not
* do flow typing on `x`.
* ```scala
* var x: String|Null = ???
* def y = {
* x = null
* }
* if (x != null) {
* // y can be called here, which break the fact
* val a: String = x // error: x is captured and mutated by the closure, not tackable
* }
* ```
*
* 2. Whether to generate and use flow typing on a specific _use_ of a local mutable variable.
* We only want to do flow typing on a use that belongs to the same method as the definition
* of the local variable.
* For example, in the following code, even `x` is not assigned to by a closure, but we can only
* use flow typing in one of the occurrences (because the other occurrence happens within a nested
* closure).
* ```scala
* var x: String|Null = ???
* def y = {
* if (x != null) {
* // not safe to use the fact (x != null) here
* // since y can be executed at the same time as the outer block
* val _: String = x
* }
* }
* if (x != null) {
* val a: String = x // ok to use the fact here
* x = null
* }
* ```
*
* See more examples in `tests/explicit-nulls/neg/var-ref-in-closure.scala`.
*/
def isTracked(ref: TermRef)(given Context) =
ref.isStable
|| { val sym = ref.symbol
sym.is(Mutable)
&& sym.owner.isTerm
&& sym.owner.enclosingMethod == curCtx.owner.enclosingMethod
&& ( sym.owner == curCtx.owner
|| !curCtx.owner.is(Flags.Lazy) // not at the rhs of lazy ValDef
&& sym.owner.enclosingMethod == curCtx.owner.enclosingMethod // not in different methods
// TODO: need to check by-name paramter
)
&& sym.span.exists
&& curCtx.compilationUnit != null // could be null under -Ytest-pickler
&& curCtx.compilationUnit.assignmentSpans.contains(sym.span.start)
Expand Down Expand Up @@ -254,7 +299,13 @@ object Nullables with
given assignOps: (tree: Assign)
def computeAssignNullable()(given Context): tree.type = tree.lhs match
case TrackedRef(ref) =>
tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) // TODO: refine with nullability type info
val rhstp = tree.rhs.typeOpt
if (rhstp.isNullType || (curCtx.explicitNulls && rhstp.isNullableUnion))
// If the type of rhs is nullable (`T|Null` or `Null`), then the nullability of the
// lhs variable is no longer trackable. We don't need to check whether the type `T`
// is correct here, as typer will check it.
tree.withNotNullInfo(NotNullInfo(Set(), Set(ref)))
else tree
case _ => tree

private val analyzedOps = Set(nme.EQ, nme.NE, nme.eq, nme.ne, nme.ZAND, nme.ZOR, nme.UNARY_!)
Expand Down
61 changes: 42 additions & 19 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import transform.SymUtils._
import transform.TypeUtils._
import reporting.trace
import Nullables.{NotNullInfo, given}
import NullOpsDecorator._

object Typer {

Expand Down Expand Up @@ -347,6 +348,16 @@ class Typer extends Namer
findRefRecur(NoType, BindingPrec.NothingBound, NoContext)
}

// If `tree`'s type is a `TermRef` identified by flow typing to be non-null, then
// cast away `tree`s nullability. Otherwise, `tree` remains unchanged.
def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match
case ref @ OrNull(tpnn) : TermRef
if pt != AssignProto && // Ensure it is not the lhs of Assign
ctx.notNullInfos.impliesNotNull(ref) =>
tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn))
case _ =>
tree

/** Attribute an identifier consisting of a simple name or wildcard
*
* @param tree The tree representing the identifier.
Expand Down Expand Up @@ -417,7 +428,9 @@ class Typer extends Namer
tree.withType(ownType)
}

checkStableIdentPattern(tree1, pt)
val tree2 = toNotNullTermRef(tree1, pt)

checkStableIdentPattern(tree2, pt)
}

/** Check that a stable identifier pattern is indeed stable (SLS 8.1.5)
Expand All @@ -442,8 +455,11 @@ class Typer extends Namer
case qual =>
if (tree.name.isTypeName) checkStable(qual.tpe, qual.sourcePos)
val select = assignType(cpy.Select(tree)(qual, tree.name), qual)
if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt))
else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select

val select1 = toNotNullTermRef(select, pt)

if (select1.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select1, pt))
else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select1
else typedDynamicSelect(tree, Nil, pt)
}

Expand Down Expand Up @@ -1554,16 +1570,6 @@ class Typer extends Namer
typed(annot, defn.AnnotationClass.typeRef)

def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree = {
sym.infoOrCompleter match
case completer: Namer#Completer
if completer.creationContext.notNullInfos ne ctx.notNullInfos =>
// The RHS of a val def should know about not null facts established
// in preceding statements (unless the ValDef is completed ahead of time,
// then it is impossible).
vdef.symbol.info = Completer(completer.original)(
given completer.creationContext.withNotNullInfos(ctx.notNullInfos))
case _ =>

val ValDef(name, tpt, _) = vdef
completeAnnotations(vdef, sym)
if (sym.isOneOf(GivenOrImplicit)) checkImplicitConversionDefOK(sym)
Expand Down Expand Up @@ -2216,14 +2222,31 @@ class Typer extends Namer
case Some(xtree) =>
traverse(xtree :: rest)
case none =>
val defCtx = mdef match
def defCtx = ctx.withNotNullInfos(initialNotNullInfos)
val newCtx = if (ctx.owner.isTerm) {
// Keep preceding not null facts in the current context only if `mdef`
// cannot be executed out-of-sequence.
case _: ValDef if !mdef.mods.is(Lazy) && ctx.owner.isTerm =>
ctx // all preceding statements will have been executed in this case
case _ =>
ctx.withNotNullInfos(initialNotNullInfos)
typed(mdef)(given defCtx) match {
// We have to check the Completer of symbol befor typedValDef,
// otherwise the symbol is already completed using creation context.
mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match {
case Some((sym, completer: Namer#Completer)) =>
if (completer.creationContext.notNullInfos ne ctx.notNullInfos)
// The RHS of a val def should know about not null facts established
// in preceding statements (unless the DefTree is completed ahead of time,
// then it is impossible).
sym.info = Completer(completer.original)(
given completer.creationContext.withNotNullInfos(ctx.notNullInfos))
ctx // all preceding statements will have been executed in this case
case _ =>
// If it has been completed, then it must be because there is a forward reference
// to the definition in the program. Hence, we don't Keep preceding not null facts
// in the current context.
defCtx
}
}
else defCtx

typed(mdef)(given newCtx) match {
case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty =>
buf += inlineExpansion(mdef1)
// replace body with expansion, because it will be used as inlined body
Expand Down
Loading