Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
add flow 'tests
  • Loading branch information
noti0na1 committed Dec 3, 2019
commit ef5864ea90db84c79b383510ab7251da135328af
16 changes: 8 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -352,15 +352,15 @@ class Typer extends Namer
case ref @ OrNull(tpnn) : TermRef
if pt != AssignProto && // Ensure it is not the lhs of Assign
ctx.notNullInfos.impliesNotNull(ref) =>
// def $notNull[A, B](x: A | Null): B = x.asInstanceOf
// For a TermRef x: T|Null,
// if x is inmutable: $notNull[T, x.type & T](x),
// if x is mutable: $notNull[T, T](x).
val tpeA = TypeTree(tpnn)
val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn))
Apply(
TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil),
tree :: Nil)
val tpeB = TypeTree(AndType(ref, tpnn))
//val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn))
val newTree = TypeApply(Select(tree, defn.Any_typeCast.namedType), tpeB :: Nil)
//newTree.symbol.setFlag(Erased)
// val newTree = Apply(
// TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil),
// tree :: Nil)
newTree
case _ =>
tree

Expand Down
55 changes: 55 additions & 0 deletions tests/explicit-nulls/neg/flow-conservative.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

// Show that the static analysis behind flow typing is conservative.

class Test {

val x: String|Null = ???

// Why is the then branch ok, but the else problematic?
// The problem is that we're computing a "must not be null analysis".
// So we know that
// 1) if the condition x == null && x != null, then both sides of the
// and must be true. Then it must be the case that x != null, so we
// know that x cannot be null and x.length is allowed.
// Of course, the then branch will never execute, but the analysis doesn't
// know (so it's ok to say that x won't be null).
// 2) if the condition is false, then we only know that _one_ or more
// of the operands failed, but we don't know _which_.
// This means that we can only pick the flow facts that hold for _both_
// operands. In particular, we look at x == null, and see that if the condition
// is false, then x must _not_ be null. But then we look at what happens if
// x != null is false, and we can't conclude that any variables must be non-null.
// When we intersect the two sets {x} and \empty, we get the empty set, which
// correctly approximates reality, which is that we can get to the else branch
// regardless of whether x is null.

if (x == null && x != null) {
val y = x.length // ok
} else {
val y = x.length // error
}

// Next we show how strengthening the condition can backfire in an
// unintuitive way.
if (x != null && 1 == 1) {
val y = x.length // ok
}

if (x == null) {
} else {
val y = x.length // ok
}

// But
if (x == null && 1 == 1) { // logically equivalent to `x == null`, but the
// analysis doesn't known
} else {
val y = x.length // error
}

// The problem here is the same. If the condition is false
// then we know the l.h.s implies that x must not be null.
// But the r.h.s doesn't tell us anything about x, so we can't
// assume that x is non-null. Then the fact that x is non-null can't
// be propagated to the else branch.
}
10 changes: 10 additions & 0 deletions tests/explicit-nulls/neg/flow-implicitly.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

// Test that flow typing works well with implicit resolution.
class Test {
implicit val x: String | Null = ???
implicitly[x.type <:< String] // error: x.type is widened String|Null

if (x != null) {
implicitly[x.type <:< String] // ok: x.type is widened to String
}
}
182 changes: 182 additions & 0 deletions tests/explicit-nulls/neg/flow.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@

// Flow-sensitive type inference
class Foo {

def basic() = {
class Bar {
val s: String = ???
}

// Basic
val b: Bar|Null = ???
if (b != null) {
val s = b.s // ok: type of `b` inferred as `Bar`
val s2: Bar = b
} else {
val s = b.s // error: `b` is `Bar|Null`
}
val s = b.s // error: `b` is `Bar|Null`
}

def notStable() = {
class Bar {
var s: String = ???
}

var b2: Bar|Null = ???
if (b2 != null) {
val s = b2.s
}
}

def nested() = {
class Bar2 {
val x: Bar2|Null = ???
}

val bar2: Bar2|Null = ???
if (bar2 != null) {
if (bar2.x != null) {
if (bar2.x.x != null) {
if (bar2.x.x.x != null) {
val b2: Bar2 = bar2.x.x.x
}
val b2: Bar2 = bar2.x.x
val b2err: Bar2 = bar2.x.x.x // error: expected Bar2 but got Bar2|Null
}
val b2: Bar2 = bar2.x
}
val b2: Bar2 = bar2
}
}

def ifThenElse() = {
val s: String|Null = ???
if (s == null) {
} else {
val len: Int = s.length
val len2 = s.length
}
}

def elseIf() = {
val s1: String|Null = ???
val s2: String|Null = ???
val s3: String|Null = ???
if (s1 != null) {
val len = s1.length
val err1 = s2.length // error
val err2 = s3.length // error
} else if (s2 != null) {
val len = s2.length
val err1 = s1.length // error
val err2 = s3.length // error
} else if (s3 != null) {
val len = s3.length
val err1 = s1.length // error
val err2 = s2.length // error
}

// Accumulation in elseif
if (s1 == null) {
} else if (s2 == null) {
val len = s1.length
} else if (s3 == null) {
val len1 = s1.length
val len2 = s2.length
} else {
val len1 = s1.length
val len2 = s2.length
val len3 = s3.length
}
}

def commonIdioms() = {
val s1: String|Null = ???
val s2: String|Null = ???
val s3: String|Null = ???

if (s1 == null || s2 == null || s3 == null) {
} else {
val len1: Int = s1.length
val len2: Int = s2.length
val len3: Int = s3.length
}

if (s1 != null && s2 != null && s3 != null) {
val len1: Int = s1.length
val len2: Int = s2.length
val len3: Int = s3.length
}
}

def basicNegation() = {
val s1: String|Null = ???
if (!(s1 != null)) {
val len = s1.length // error
} else {
val len = s1.length
}

if (!(!(!(!(s1 != null))))) {
val len1 = s1.length
}
}

def parens() = {
val s1: String|Null = ???
val s2: String|Null = ???
if ((((s1 == null))) || s2 == null) {
} else {
val len1 = s1.length
val len2 = s2.length
}
}

def operatorPrec() = {
val s1: String|Null = ???
val s2: String|Null = ???
val s3: String|Null = ???

if (s1 != null || s2 != null && s3 != null) {
val len = s3.length // error
}

if (s1 != null && s2 != null || s3 != null) {
val len1 = s1.length // error
val len2 = s2.length // error
val len3 = s3.length // error
}

if (s1 != null && (s2 != null || s3 != null)) {
val len1 = s1.length
val len2 = s2.length // error
val len3 = s3.length // error
}
}

def insideCond() = {
val x: String|Null = ???
if (x != null && x.length > 0) {
val len = x.length
} else {
val len = x.length // error
}

if (x == null || x.length > 0) {
val len = x.length // error
} else {
val len = x.length
}

class Rec {
val r: Rec|Null = ???
}

val r: Rec|Null = ???
if (r != null && r.r != null && (r.r.r == null || r.r.r.r == r)) {
val err = r.r.r.r // error
}
}
}

18 changes: 18 additions & 0 deletions tests/explicit-nulls/neg/flow2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

// Test that flow inference can handle blocks.
class Foo {
val x: String|Null = "hello"
if ({val z = 10; {1 + 1 == 2; x != null}}) {
val l = x.length
}

if ({x != null; true}) {
val l = x.length // error
}

val x2: String|Null = "world"
if ({{{{1 + 1 == 2; x != null}}}} && x2 != null) {
val l = x.length
val l2 = x2.length
}
}
66 changes: 66 additions & 0 deletions tests/explicit-nulls/neg/flow5.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

// Test that flow-sensitive type inference handles
// early exists from blocks.
class Foo(x: String|Null) {

// Test within constructor
if (x == null) throw new NullPointerException()
val x2: String = x // error: flow inference for blocks doesn't work inside constructors

def foo(): Unit = {
val y: String|Null = ???
if (y == null) return ()
val y2: String = y // ok
}

def bar(): Unit = {
val y: String|Null = ???
if (y != null) {
} else {
return ()
}
val y2: String = y // ok
}

def fooInExprPos(): String = {
val y: String|Null = ???
if (y == null) return "foo"
y // ok
}

def nonLocalInBlock(): String = {
val y: String|Null = ???
if (y == null) { println("foo"); return "foo" }
y
}

def barWrong(): Unit = {
val y: String|Null = ???
if (y != null) {
return ()
} else {
}
val y2: String = y // error: can't infer that y is non-null (actually, it's the opposite)
}

def err(msg: String): Nothing = {
throw new RuntimeException(msg)
}

def retTypeNothing(): String = {
val y: String|Null = ???
if (y == null) err("y is null!")
y
}

def errRetUnit(msg: String): Unit = {
throw new RuntimeException(msg)
()
}

def retTypeUnit(): String = {
val y: String|Null = ???
if (y == null) errRetUnit("y is null!")
y // error: previous statement returned unit so can't infer non-nullability
}
}
Loading