Skip to content

Commit 6ff6b10

Browse files
noti0na1WojciechMazur
authored andcommitted
Fix NotNullInfo for Case and Try; add more tests
[Cherry-picked 7674fef]
1 parent 51dc1af commit 6ff6b10

File tree

3 files changed

+65
-18
lines changed

3 files changed

+65
-18
lines changed

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

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,7 +1857,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18571857
}
18581858
.asInstanceOf[List[CaseDef]]
18591859
var nni = sel.notNullInfo
1860-
if(cases1.nonEmpty) nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
1860+
if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
18611861
assignType(cpy.Match(tree)(sel, cases1), sel, cases1).cast(pt).withNotNullInfo(nni)
18621862
}
18631863

@@ -1866,7 +1866,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18661866
val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto))
18671867
.asInstanceOf[List[CaseDef]]
18681868
var nni = sel.notNullInfo
1869-
if(cases1.nonEmpty) nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
1869+
if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_)))
18701870
assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nni)
18711871
}
18721872

@@ -1937,13 +1937,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
19371937
// will end up taking too much memory. If it does, we should just limit
19381938
// how much GADT constraints we infer - it's always sound to infer less.
19391939
pat1.putAttachment(InferredGadtConstraints, ctx.gadt)
1940-
if (pt1.isValueType) // insert a cast if body does not conform to expected type if we disregard gadt bounds
1940+
if pt1.isValueType then // insert a cast if body does not conform to expected type if we disregard gadt bounds
19411941
body1 = body1.ensureConforms(pt1)(using originalCtx)
1942-
val nni = pat1.notNullInfo.seq(
1943-
guard1.notNullInfoIf(false).alt(
1944-
guard1.notNullInfoIf(true).seq(body1.notNullInfo)
1945-
)
1946-
)
1942+
val nni = pat1.notNullInfo
1943+
.seq(guard1.notNullInfoIf(false).alt(guard1.notNullInfoIf(true)))
1944+
.seq(body1.notNullInfo)
19471945
assignType(cpy.CaseDef(tree)(pat1, guard1, body1), pat1, body1).withNotNullInfo(nni)
19481946
}
19491947

@@ -2048,16 +2046,25 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
20482046
untpd.Block(makeCanThrow(capabilityProof), expr)
20492047

20502048
def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = {
2049+
// We want to type check tree.expr first to comput NotNullInfo, but `addCanThrowCapabilities`
2050+
// uses the types of patterns in `tree.cases` to determine the capabilities.
2051+
// Hence, we create a copy of cases with empty body and type check that first, then type check
2052+
// the rest of the tree in order.
2053+
val casesEmptyBody1 = tree.cases.mapconserve(cpy.CaseDef(_)(body = EmptyTree))
2054+
val casesEmptyBody2 = typedCases(casesEmptyBody1, EmptyTree, defn.ThrowableType, WildcardType)
2055+
20512056
val expr2 :: cases2x = harmonic(harmonize, pt) {
2052-
val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto)
2053-
val expr1 = typed(addCanThrowCapabilities(tree.expr, cases1), pt.dropIfProto)
2057+
val expr1 = typed(addCanThrowCapabilities(tree.expr, casesEmptyBody2), pt.dropIfProto)
2058+
val casesCtx = ctx.addNotNullInfo(expr1.notNullInfo.retractedInfo)
2059+
val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto)(using casesCtx)
20542060
expr1 :: cases1
20552061
}: @unchecked
2056-
val finalizer1 = typed(tree.finalizer, defn.UnitType)
20572062
val cases2 = cases2x.asInstanceOf[List[CaseDef]]
2058-
val nni = expr2.notNullInfo.retractedInfo.seq(
2059-
cases2.map(_.notNullInfo.retractedInfo).fold(NotNullInfo.empty)(_.alt(_))
2060-
).seq(finalizer1.notNullInfo)
2063+
2064+
var nni = expr2.notNullInfo.retractedInfo
2065+
if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo).reduce(_.alt(_)))
2066+
val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni))
2067+
nni = nni.seq(finalizer1.notNullInfo)
20612068
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni)
20622069
}
20632070

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
@main def test() = {
1+
def test1 =
22
var x: String | Null = null
33
x = ""
4-
1 match {
4+
1 match
55
case 1 => x = null
6-
}
6+
case _ => x = x.trim() // ok
77
x.replace("", "") // error
8-
}
8+
9+
def test2(i: Int) =
10+
var x: String | Null = null
11+
i match
12+
case 1 => x = "1"
13+
case _ => x = " "
14+
x.replace("", "") // ok
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
def test1(i: Int): Int =
2+
var x: String | Null = null
3+
if i == 0 then x = ""
4+
else x = ""
5+
try
6+
x = x.replace(" ", "") // ok
7+
throw new Exception()
8+
catch
9+
case e: Exception =>
10+
x = x.replaceAll(" ", "") // error
11+
x = null
12+
x.length // error
13+
14+
def test2: Int =
15+
var x: String | Null = null
16+
try throw new Exception()
17+
finally x = ""
18+
x.length // ok
19+
20+
def test3 =
21+
var x: String | Null = ""
22+
try throw new Exception()
23+
catch case e: Exception =>
24+
x = (??? : String | Null)
25+
finally
26+
val l = x.length // error
27+
28+
def test4: Int =
29+
var x: String | Null = null
30+
try throw new Exception()
31+
catch
32+
case npe: NullPointerException => x = ""
33+
case _ => x = ""
34+
x.length // ok

0 commit comments

Comments
 (0)