Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #11967: flow typing nullability in pattern matches #18212

Merged
merged 2 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1843,12 +1843,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 @@ -1862,10 +1867,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
noti0na1 marked this conversation as resolved.
Show resolved Hide resolved
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
}
noti0na1 marked this conversation as resolved.
Show resolved Hide resolved
}