Skip to content

Commit 7f10ba8

Browse files
committed
Record NotNullInfo for exceptional try-catch
1 parent a9c731c commit 7f10ba8

File tree

2 files changed

+117
-14
lines changed

2 files changed

+117
-14
lines changed

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

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,8 +2436,31 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
24362436
val capabilityProof = caughtExceptions.reduce(OrType(_, _, true))
24372437
untpd.Block(makeCanThrow(capabilityProof), expr)
24382438

2439+
/** Graphic explaination of NotNullInfo logic:
2440+
* Leftward exit indicates exceptional case
2441+
* Downward exit indicates normal case
2442+
*
2443+
* ┌─────┐ α.retractedInfo
2444+
* │ Try ├─────┬────────┬─────┐
2445+
* └──┬──┘ ▼ ▼ │
2446+
* │ ┌───────┐┌───────┐ │
2447+
* α │ │ Catch ││ Catch ├─┤
2448+
* │ └───┬───┘└───┬───┘ │
2449+
* │ β │ │ │
2450+
* └──┬─────┴────────┘ │
2451+
* │ γ = α.alt(β) │ ε = β.retractedInfo
2452+
* ▼ ▼
2453+
* ┌─────────┐ ┌─────────┐
2454+
* δ = type(ε)│ Finally ├──────┐ │ Finally ├─────────┐
2455+
* └────┬────┘ │ └────┬────┘ │
2456+
* │ │ ▼ ▼
2457+
* │ α.seq(γ) └──────────────────────────►
2458+
* ▼ ε.seq(δ)
2459+
* We choose to use α.seq(γ) as the NotNullInfo of the
2460+
* overall tree if all the catch cases have NothingType,
2461+
* otherwise we use ε.seq(δ).
2462+
*/
24392463
def typedTry(tree: untpd.Try, pt: Type)(using Context): Try =
2440-
var nnInfo = NotNullInfo.empty
24412464
val expr2 :: cases2x = harmonic(harmonize, pt) {
24422465
// We want to type check tree.expr first to comput NotNullInfo, but `addCanThrowCapabilities`
24432466
// uses the types of patterns in `tree.cases` to determine the capabilities.
@@ -2450,25 +2473,31 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
24502473
val casesEmptyBody2 = typedCases(casesEmptyBody1, EmptyTree, defn.ThrowableType, WildcardType)
24512474
val expr1 = typed(addCanThrowCapabilities(tree.expr, casesEmptyBody2), pt.dropIfProto)
24522475

2453-
// Since we don't know at which point the the exception is thrown in the body,
2454-
// we have to collect any reference that is once retracted.
2455-
nnInfo = expr1.notNullInfo.retractedInfo
2456-
2457-
val casesCtx = ctx.addNotNullInfo(nnInfo)
2476+
val casesCtx = ctx.addNotNullInfo(expr1.notNullInfo.retractedInfo)
24582477
val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto)(using casesCtx)
24592478
expr1 :: cases1
24602479
}: @unchecked
24612480
val cases2 = cases2x.asInstanceOf[List[CaseDef]]
2462-
2481+
val tryNNInfo = expr2.notNullInfo
24632482
// It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught.
24642483
// Therefore, the code in the finalizer and after the try block can only rely on the retracted
24652484
// info from the cases' body.
2466-
if cases2.nonEmpty then
2467-
nnInfo = nnInfo.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_)))
2468-
2469-
val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nnInfo))
2470-
nnInfo = nnInfo.seq(finalizer1.notNullInfo)
2471-
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nnInfo)
2485+
val catchNNInfo = if cases2.nonEmpty then
2486+
tryNNInfo.seq(cases2.map(_.notNullInfo).reduce(_.alt(_)))
2487+
else
2488+
NotNullInfo.empty
2489+
val normalResolveNNInfo = tryNNInfo.alt(catchNNInfo)
2490+
val exceptionalResolveNNInfo = catchNNInfo.retractedInfo
2491+
2492+
val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(exceptionalResolveNNInfo))
2493+
val normalFinalNNInfo = normalResolveNNInfo.seq(finalizer1.notNullInfo)
2494+
val exceptionalFinalNNInfo = exceptionalResolveNNInfo.seq(finalizer1.notNullInfo)
2495+
val resNNInfo =
2496+
if (cases2.forall(_.tpe == defn.NothingType)) then
2497+
normalFinalNNInfo
2498+
else
2499+
exceptionalFinalNNInfo
2500+
assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(resNNInfo)
24722501

24732502
def typedTry(tree: untpd.ParsedTry, pt: Type)(using Context): Try =
24742503
val cases: List[untpd.CaseDef] = tree.handler match

tests/explicit-nulls/neg/i21619.scala

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,78 @@ def test6 = {
8989
}
9090
}
9191
x.replace("", "") // error
92-
}
92+
}
93+
94+
// From i24296
95+
def test7() =
96+
var x: String | Null = null
97+
try {
98+
x = ""
99+
} catch {
100+
case e =>
101+
throw e
102+
}
103+
x.trim() // ok
104+
105+
def test8() =
106+
var x: String | Null = null
107+
try {
108+
try {
109+
x = ""
110+
} catch {
111+
case e => throw e
112+
}
113+
} catch {
114+
case e => throw e
115+
}
116+
x.trim() // ok
117+
118+
def test9() =
119+
var x: String | Null = null
120+
try {
121+
x = ""
122+
} catch {
123+
case e: AssertionError =>
124+
throw e
125+
case _ =>
126+
}
127+
x.trim() // error
128+
129+
def test10() =
130+
var x: String | Null = null
131+
try {
132+
x = ""
133+
} catch {
134+
case e =>
135+
throw e
136+
} finally {
137+
x = null
138+
}
139+
x.trim() // error
140+
141+
def test11() =
142+
var x: String | Null = null
143+
try {
144+
x = ""
145+
} catch {
146+
case e =>
147+
x = null
148+
throw e
149+
} finally {
150+
x = ""
151+
}
152+
x.trim() // ok
153+
154+
def test12() =
155+
var x: String | Null = null
156+
try {
157+
x = ""
158+
} catch {
159+
case e =>
160+
x = null
161+
throw e
162+
} finally {
163+
throw new Exception
164+
x = ""
165+
}
166+
x.trim() // ???

0 commit comments

Comments
 (0)