Skip to content

Commit 30e270d

Browse files
committed
fix scala#11967: flow typing nullability in pattern matches
1 parent 9552373 commit 30e270d

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ object Nullables:
194194
// TODO: Add constant pattern if the constant type is not nullable
195195
case _ => false
196196

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

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

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,12 +1841,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18411841
/** Special typing of Match tree when the expected type is a MatchType,
18421842
* and the patterns of the Match tree and the MatchType correspond.
18431843
*/
1844-
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
1844+
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType0: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
18451845
var caseCtx = ctx
1846+
var wideSelType = wideSelType0
1847+
var alreadyStripped = false
18461848
val cases1 = tree.cases.zip(pt.cases)
18471849
.map { case (cas, tpe) =>
18481850
val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx)
18491851
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
1852+
if !alreadyStripped && Nullables.matchesNull(case1) then
1853+
wideSelType = wideSelType.stripNull
1854+
alreadyStripped = true
18501855
case1
18511856
}
18521857
.asInstanceOf[List[CaseDef]]
@@ -1860,10 +1865,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
18601865
assignType(cpy.Match(tree)(sel, cases1), sel, cases1)
18611866
}
18621867

1863-
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType: Type, pt: Type)(using Context): List[CaseDef] =
1868+
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] =
18641869
var caseCtx = ctx
1870+
var wideSelType = wideSelType0
1871+
var alreadyStripped = false
18651872
cases.mapconserve { cas =>
18661873
val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx)
1874+
if !alreadyStripped && Nullables.matchesNull(case1) then
1875+
wideSelType = wideSelType.stripNull
1876+
alreadyStripped = true
18671877
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
18681878
case1
18691879
}

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

Lines changed: 32 additions & 0 deletions
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)