Skip to content

Commit

Permalink
fix scala#11967: flow typing nullability in pattern matches
Browse files Browse the repository at this point in the history
  • Loading branch information
olhotak committed Jul 14, 2023
1 parent ca83183 commit 2988c1c
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 2 deletions.
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Nullables.scala
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,16 @@ object Nullables:
// TODO: Add constant pattern if the constant type is not nullable
case _ => false

def matchesNull(cdef: CaseDef)(using Context): Boolean =
cdef.guard.isEmpty && patMatchesNull(cdef.pat)

private def patMatchesNull(pat: Tree)(using Context): Boolean = pat match
case Literal(Constant(null)) => true
case Bind(_, pat) => patMatchesNull(pat)
case Alternative(trees) => trees.exists(patMatchesNull)
case _ if isVarPattern(pat) => true
case _ => false

extension (infos: List[NotNullInfo])

/** Do the current not-null infos imply that `ref` is not null?
Expand Down
14 changes: 12 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1841,12 +1841,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
/** Special typing of Match tree when the expected type is a MatchType,
* and the patterns of the Match tree and the MatchType correspond.
*/
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType0: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = {
var caseCtx = ctx
var wideSelType = wideSelType0
var alreadyStripped = false
val cases1 = tree.cases.zip(pt.cases)
.map { case (cas, tpe) =>
val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx)
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
if !alreadyStripped && Nullables.matchesNull(case1) then
wideSelType = wideSelType.stripNull
alreadyStripped = true
case1
}
.asInstanceOf[List[CaseDef]]
Expand All @@ -1860,10 +1865,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
assignType(cpy.Match(tree)(sel, cases1), sel, cases1)
}

def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType: Type, pt: Type)(using Context): List[CaseDef] =
def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] =
var caseCtx = ctx
var wideSelType = wideSelType0
var alreadyStripped = false
cases.mapconserve { cas =>
val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx)
if !alreadyStripped && Nullables.matchesNull(case1) then
wideSelType = wideSelType.stripNull
alreadyStripped = true
caseCtx = Nullables.afterPatternContext(sel, case1.pat)
case1
}
Expand Down
32 changes: 32 additions & 0 deletions tests/explicit-nulls/pos/flow-match.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,36 @@ object MatchTest {
// after the null case, s becomes non-nullable
case _ => s
}

def f(s: String | Null): String = s match {
case null => "other"
case s2 => s2
case s3 => s3
}

class Foo

def f2(s: String | Null): String = s match {
case n @ null => "other"
case s2 => s2
case s3 => s3
}

def f3(s: String | Null): String = s match {
case null | "foo" => "other"
case s2 => s2
case s3 => s3
}

def f4(s: String | Null): String = s match {
case _ => "other"
case s2 => s2
case s3 => s3
}

def f5(s: String | Null): String = s match {
case x => "other"
case s2 => s2
case s3 => s3
}
}

0 comments on commit 2988c1c

Please sign in to comment.