Skip to content

Commit ac5fcfb

Browse files
authored
fix #11967: flow typing nullability in pattern matches (#18212)
Fixes #11967
2 parents 5c47c5e + 9143889 commit ac5fcfb

File tree

4 files changed

+80
-2
lines changed

4 files changed

+80
-2
lines changed

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

+10
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ object Nullables:
201201
// TODO: Add constant pattern if the constant type is not nullable
202202
case _ => false
203203

204+
def matchesNull(cdef: CaseDef)(using Context): Boolean =
205+
cdef.guard.isEmpty && patMatchesNull(cdef.pat)
206+
207+
private def patMatchesNull(pat: Tree)(using Context): Boolean = pat match
208+
case Literal(Constant(null)) => true
209+
case Bind(_, pat) => patMatchesNull(pat)
210+
case Alternative(trees) => trees.exists(patMatchesNull)
211+
case _ if isVarPattern(pat) => true
212+
case _ => false
213+
204214
extension (infos: List[NotNullInfo])
205215

206216
/** Do the current not-null infos imply that `ref` is not null?

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

+12-2
Original file line numberDiff line numberDiff line change
@@ -1846,12 +1846,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18461846
/** Special typing of Match tree when the expected type is a MatchType,
18471847
* and the patterns of the Match tree and the MatchType correspond.
18481848
*/
1849-
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
1849+
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType0: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
18501850
var caseCtx = ctx
1851+
var wideSelType = wideSelType0
1852+
var alreadyStripped = false
18511853
val cases1 = tree.cases.zip(pt.cases)
18521854
.map { case (cas, tpe) =>
18531855
val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx)
18541856
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
1857+
if !alreadyStripped && Nullables.matchesNull(case1) then
1858+
wideSelType = wideSelType.stripNull
1859+
alreadyStripped = true
18551860
case1
18561861
}
18571862
.asInstanceOf[List[CaseDef]]
@@ -1865,11 +1870,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18651870
assignType(cpy.Match(tree)(sel, cases1), sel, cases1)
18661871
}
18671872

1868-
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType: Type, pt: Type)(using Context): List[CaseDef] =
1873+
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] =
18691874
var caseCtx = ctx
1875+
var wideSelType = wideSelType0
1876+
var alreadyStripped = false
18701877
cases.mapconserve { cas =>
18711878
val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx)
18721879
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
1880+
if !alreadyStripped && Nullables.matchesNull(case1) then
1881+
wideSelType = wideSelType.stripNull
1882+
alreadyStripped = true
18731883
case1
18741884
}
18751885

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Test flow-typing when NotNullInfos are from cases
2+
3+
object MatchTest {
4+
def f6(s: String | Null): String = s match {
5+
case s2 => s2 // error
6+
case null => "other" // error
7+
case s3 => s3
8+
}
9+
10+
def f7(s: String | Null): String = s match {
11+
case null => "other"
12+
case null => "other" // error
13+
case s3 => s3
14+
}
15+
}

tests/explicit-nulls/pos/flow-match.scala

+43
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,47 @@ object MatchTest {
1212
// after the null case, s becomes non-nullable
1313
case _ => s
1414
}
15+
16+
def f(s: String | Null): String = s match {
17+
case null => "other"
18+
case s2 => s2
19+
case s3 => s3
20+
}
21+
22+
class Foo
23+
24+
def f2(s: String | Null): String = s match {
25+
case n @ null => "other"
26+
case s2 => s2
27+
case s3 => s3
28+
}
29+
30+
def f3(s: String | Null): String = s match {
31+
case null | "foo" => "other"
32+
case s2 => s2
33+
case s3 => s3
34+
}
35+
36+
def f4(s: String | Null): String = s match {
37+
case _ => "other"
38+
case s2 => s2
39+
case s3 => s3
40+
}
41+
42+
def f5(s: String | Null): String = s match {
43+
case x => "other"
44+
case s2 => s2
45+
case s3 => s3
46+
}
47+
48+
def f6(s: String | Null): String = s match {
49+
case s3: String => s3
50+
case null => "other"
51+
case s4 => s4
52+
}
53+
54+
def f7(s: String | Null): String = s match {
55+
case s2 => s2.nn
56+
case s3 => s3
57+
}
1558
}

0 commit comments

Comments
 (0)