Skip to content

Commit ef5864e

Browse files
committed
add flow 'tests
1 parent b813439 commit ef5864e

20 files changed

+820
-8
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -352,15 +352,15 @@ class Typer extends Namer
352352
case ref @ OrNull(tpnn) : TermRef
353353
if pt != AssignProto && // Ensure it is not the lhs of Assign
354354
ctx.notNullInfos.impliesNotNull(ref) =>
355-
// def $notNull[A, B](x: A | Null): B = x.asInstanceOf
356-
// For a TermRef x: T|Null,
357-
// if x is inmutable: $notNull[T, x.type & T](x),
358-
// if x is mutable: $notNull[T, T](x).
359355
val tpeA = TypeTree(tpnn)
360-
val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn))
361-
Apply(
362-
TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil),
363-
tree :: Nil)
356+
val tpeB = TypeTree(AndType(ref, tpnn))
357+
//val tpeB = if (ref.symbol.is(Mutable)) tpeA else TypeTree(AndType(ref, tpnn))
358+
val newTree = TypeApply(Select(tree, defn.Any_typeCast.namedType), tpeB :: Nil)
359+
//newTree.symbol.setFlag(Erased)
360+
// val newTree = Apply(
361+
// TypeApply(Ident(defn.Compiletime_notNull.namedType), tpeA :: tpeB :: Nil),
362+
// tree :: Nil)
363+
newTree
364364
case _ =>
365365
tree
366366

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
// Show that the static analysis behind flow typing is conservative.
3+
4+
class Test {
5+
6+
val x: String|Null = ???
7+
8+
// Why is the then branch ok, but the else problematic?
9+
// The problem is that we're computing a "must not be null analysis".
10+
// So we know that
11+
// 1) if the condition x == null && x != null, then both sides of the
12+
// and must be true. Then it must be the case that x != null, so we
13+
// know that x cannot be null and x.length is allowed.
14+
// Of course, the then branch will never execute, but the analysis doesn't
15+
// know (so it's ok to say that x won't be null).
16+
// 2) if the condition is false, then we only know that _one_ or more
17+
// of the operands failed, but we don't know _which_.
18+
// This means that we can only pick the flow facts that hold for _both_
19+
// operands. In particular, we look at x == null, and see that if the condition
20+
// is false, then x must _not_ be null. But then we look at what happens if
21+
// x != null is false, and we can't conclude that any variables must be non-null.
22+
// When we intersect the two sets {x} and \empty, we get the empty set, which
23+
// correctly approximates reality, which is that we can get to the else branch
24+
// regardless of whether x is null.
25+
26+
if (x == null && x != null) {
27+
val y = x.length // ok
28+
} else {
29+
val y = x.length // error
30+
}
31+
32+
// Next we show how strengthening the condition can backfire in an
33+
// unintuitive way.
34+
if (x != null && 1 == 1) {
35+
val y = x.length // ok
36+
}
37+
38+
if (x == null) {
39+
} else {
40+
val y = x.length // ok
41+
}
42+
43+
// But
44+
if (x == null && 1 == 1) { // logically equivalent to `x == null`, but the
45+
// analysis doesn't known
46+
} else {
47+
val y = x.length // error
48+
}
49+
50+
// The problem here is the same. If the condition is false
51+
// then we know the l.h.s implies that x must not be null.
52+
// But the r.h.s doesn't tell us anything about x, so we can't
53+
// assume that x is non-null. Then the fact that x is non-null can't
54+
// be propagated to the else branch.
55+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
// Test that flow typing works well with implicit resolution.
3+
class Test {
4+
implicit val x: String | Null = ???
5+
implicitly[x.type <:< String] // error: x.type is widened String|Null
6+
7+
if (x != null) {
8+
implicitly[x.type <:< String] // ok: x.type is widened to String
9+
}
10+
}

tests/explicit-nulls/neg/flow.scala

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
2+
// Flow-sensitive type inference
3+
class Foo {
4+
5+
def basic() = {
6+
class Bar {
7+
val s: String = ???
8+
}
9+
10+
// Basic
11+
val b: Bar|Null = ???
12+
if (b != null) {
13+
val s = b.s // ok: type of `b` inferred as `Bar`
14+
val s2: Bar = b
15+
} else {
16+
val s = b.s // error: `b` is `Bar|Null`
17+
}
18+
val s = b.s // error: `b` is `Bar|Null`
19+
}
20+
21+
def notStable() = {
22+
class Bar {
23+
var s: String = ???
24+
}
25+
26+
var b2: Bar|Null = ???
27+
if (b2 != null) {
28+
val s = b2.s
29+
}
30+
}
31+
32+
def nested() = {
33+
class Bar2 {
34+
val x: Bar2|Null = ???
35+
}
36+
37+
val bar2: Bar2|Null = ???
38+
if (bar2 != null) {
39+
if (bar2.x != null) {
40+
if (bar2.x.x != null) {
41+
if (bar2.x.x.x != null) {
42+
val b2: Bar2 = bar2.x.x.x
43+
}
44+
val b2: Bar2 = bar2.x.x
45+
val b2err: Bar2 = bar2.x.x.x // error: expected Bar2 but got Bar2|Null
46+
}
47+
val b2: Bar2 = bar2.x
48+
}
49+
val b2: Bar2 = bar2
50+
}
51+
}
52+
53+
def ifThenElse() = {
54+
val s: String|Null = ???
55+
if (s == null) {
56+
} else {
57+
val len: Int = s.length
58+
val len2 = s.length
59+
}
60+
}
61+
62+
def elseIf() = {
63+
val s1: String|Null = ???
64+
val s2: String|Null = ???
65+
val s3: String|Null = ???
66+
if (s1 != null) {
67+
val len = s1.length
68+
val err1 = s2.length // error
69+
val err2 = s3.length // error
70+
} else if (s2 != null) {
71+
val len = s2.length
72+
val err1 = s1.length // error
73+
val err2 = s3.length // error
74+
} else if (s3 != null) {
75+
val len = s3.length
76+
val err1 = s1.length // error
77+
val err2 = s2.length // error
78+
}
79+
80+
// Accumulation in elseif
81+
if (s1 == null) {
82+
} else if (s2 == null) {
83+
val len = s1.length
84+
} else if (s3 == null) {
85+
val len1 = s1.length
86+
val len2 = s2.length
87+
} else {
88+
val len1 = s1.length
89+
val len2 = s2.length
90+
val len3 = s3.length
91+
}
92+
}
93+
94+
def commonIdioms() = {
95+
val s1: String|Null = ???
96+
val s2: String|Null = ???
97+
val s3: String|Null = ???
98+
99+
if (s1 == null || s2 == null || s3 == null) {
100+
} else {
101+
val len1: Int = s1.length
102+
val len2: Int = s2.length
103+
val len3: Int = s3.length
104+
}
105+
106+
if (s1 != null && s2 != null && s3 != null) {
107+
val len1: Int = s1.length
108+
val len2: Int = s2.length
109+
val len3: Int = s3.length
110+
}
111+
}
112+
113+
def basicNegation() = {
114+
val s1: String|Null = ???
115+
if (!(s1 != null)) {
116+
val len = s1.length // error
117+
} else {
118+
val len = s1.length
119+
}
120+
121+
if (!(!(!(!(s1 != null))))) {
122+
val len1 = s1.length
123+
}
124+
}
125+
126+
def parens() = {
127+
val s1: String|Null = ???
128+
val s2: String|Null = ???
129+
if ((((s1 == null))) || s2 == null) {
130+
} else {
131+
val len1 = s1.length
132+
val len2 = s2.length
133+
}
134+
}
135+
136+
def operatorPrec() = {
137+
val s1: String|Null = ???
138+
val s2: String|Null = ???
139+
val s3: String|Null = ???
140+
141+
if (s1 != null || s2 != null && s3 != null) {
142+
val len = s3.length // error
143+
}
144+
145+
if (s1 != null && s2 != null || s3 != null) {
146+
val len1 = s1.length // error
147+
val len2 = s2.length // error
148+
val len3 = s3.length // error
149+
}
150+
151+
if (s1 != null && (s2 != null || s3 != null)) {
152+
val len1 = s1.length
153+
val len2 = s2.length // error
154+
val len3 = s3.length // error
155+
}
156+
}
157+
158+
def insideCond() = {
159+
val x: String|Null = ???
160+
if (x != null && x.length > 0) {
161+
val len = x.length
162+
} else {
163+
val len = x.length // error
164+
}
165+
166+
if (x == null || x.length > 0) {
167+
val len = x.length // error
168+
} else {
169+
val len = x.length
170+
}
171+
172+
class Rec {
173+
val r: Rec|Null = ???
174+
}
175+
176+
val r: Rec|Null = ???
177+
if (r != null && r.r != null && (r.r.r == null || r.r.r.r == r)) {
178+
val err = r.r.r.r // error
179+
}
180+
}
181+
}
182+

tests/explicit-nulls/neg/flow2.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
// Test that flow inference can handle blocks.
3+
class Foo {
4+
val x: String|Null = "hello"
5+
if ({val z = 10; {1 + 1 == 2; x != null}}) {
6+
val l = x.length
7+
}
8+
9+
if ({x != null; true}) {
10+
val l = x.length // error
11+
}
12+
13+
val x2: String|Null = "world"
14+
if ({{{{1 + 1 == 2; x != null}}}} && x2 != null) {
15+
val l = x.length
16+
val l2 = x2.length
17+
}
18+
}

tests/explicit-nulls/neg/flow5.scala

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
// Test that flow-sensitive type inference handles
3+
// early exists from blocks.
4+
class Foo(x: String|Null) {
5+
6+
// Test within constructor
7+
if (x == null) throw new NullPointerException()
8+
val x2: String = x // error: flow inference for blocks doesn't work inside constructors
9+
10+
def foo(): Unit = {
11+
val y: String|Null = ???
12+
if (y == null) return ()
13+
val y2: String = y // ok
14+
}
15+
16+
def bar(): Unit = {
17+
val y: String|Null = ???
18+
if (y != null) {
19+
} else {
20+
return ()
21+
}
22+
val y2: String = y // ok
23+
}
24+
25+
def fooInExprPos(): String = {
26+
val y: String|Null = ???
27+
if (y == null) return "foo"
28+
y // ok
29+
}
30+
31+
def nonLocalInBlock(): String = {
32+
val y: String|Null = ???
33+
if (y == null) { println("foo"); return "foo" }
34+
y
35+
}
36+
37+
def barWrong(): Unit = {
38+
val y: String|Null = ???
39+
if (y != null) {
40+
return ()
41+
} else {
42+
}
43+
val y2: String = y // error: can't infer that y is non-null (actually, it's the opposite)
44+
}
45+
46+
def err(msg: String): Nothing = {
47+
throw new RuntimeException(msg)
48+
}
49+
50+
def retTypeNothing(): String = {
51+
val y: String|Null = ???
52+
if (y == null) err("y is null!")
53+
y
54+
}
55+
56+
def errRetUnit(msg: String): Unit = {
57+
throw new RuntimeException(msg)
58+
()
59+
}
60+
61+
def retTypeUnit(): String = {
62+
val y: String|Null = ???
63+
if (y == null) errRetUnit("y is null!")
64+
y // error: previous statement returned unit so can't infer non-nullability
65+
}
66+
}

0 commit comments

Comments
 (0)