Skip to content

Commit 1572968

Browse files
committed
fix #11967: flow typing nullability in pattern matches
1 parent 04eae14 commit 1572968

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

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

+10
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ object Nullables:
190190
// TODO: Add constant pattern if the constant type is not nullable
191191
case _ => false
192192

193+
def matchesNull(cdef: CaseDef)(using Context): Boolean =
194+
cdef.guard.isEmpty && patMatchesNull(cdef.pat)
195+
196+
private def patMatchesNull(pat: Tree)(using Context): Boolean = pat match
197+
case Literal(Constant(null)) => true
198+
case Bind(_, pat) => patMatchesNull(pat)
199+
case Alternative(trees) => trees.exists(patMatchesNull)
200+
case _ if isVarPattern(pat) => true
201+
case _ => false
202+
193203
extension (infos: List[NotNullInfo])
194204

195205
/** 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
@@ -1843,12 +1843,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18431843
/** Special typing of Match tree when the expected type is a MatchType,
18441844
* and the patterns of the Match tree and the MatchType correspond.
18451845
*/
1846-
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
1846+
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType0: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
18471847
var caseCtx = ctx
1848+
var wideSelType = wideSelType0
1849+
var alreadyStripped = false
18481850
val cases1 = tree.cases.zip(pt.cases)
18491851
.map { case (cas, tpe) =>
18501852
val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx)
18511853
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
1854+
if !alreadyStripped && Nullables.matchesNull(case1) then
1855+
wideSelType = wideSelType.stripNull
1856+
alreadyStripped = true
18521857
case1
18531858
}
18541859
.asInstanceOf[List[CaseDef]]
@@ -1862,10 +1867,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18621867
assignType(cpy.Match(tree)(sel, cases1), sel, cases1)
18631868
}
18641869

1865-
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType: Type, pt: Type)(using Context): List[CaseDef] =
1870+
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] =
18661871
var caseCtx = ctx
1872+
var wideSelType = wideSelType0
1873+
var alreadyStripped = false
18671874
cases.mapconserve { cas =>
18681875
val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx)
1876+
if !alreadyStripped && Nullables.matchesNull(case1) then
1877+
wideSelType = wideSelType.stripNull
1878+
alreadyStripped = true
18691879
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
18701880
case1
18711881
}

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

+32
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,36 @@ 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+
}
1547
}

0 commit comments

Comments
 (0)