-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Improve warning for wildcard matching only null under the explicit nulls flag (scala#21577) #21623
base: main
Are you sure you want to change the base?
Conversation
…cala#21577) Adds a more detailed warning message when a wildcard case is only reachable by null under explict nulls flag. Fixes scala#21577
The reason why there is a warning for the "unreachable wildcard except null" cases is: this kind of cases we normally don't write. If there is an extra wildcard case at the end, it much match with For example, if
For |
|
||
def f2(s: String | Null) = | ||
val s2 = s.nn.trim() | ||
s2 match |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indent need to be fixed.
|
||
def f2(s: String | Null) = | ||
val s2 = s.nn.trim() | ||
s2 match |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess f
and f2
are testing the same thing? Both selectors are flexible types.
val s2 = s | ||
s2 match | ||
case s3: String => println(1) | ||
case _ => println(2) // warn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect no warning here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this case only reachable by Null? In which case we should emit a warning that it's unreachable except for null. Or is it allowed because we explicitly have Null in the selector type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, with explcit nullls, we can really consider String
and Null
as two independent types, similar to String
and Int
, so the semantic is clear the second case matches null
value. What do you think? cc @olhotak
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without explicit nulls, this warns. I think we can keep the warning. People can still write case null =>
for this, which is more explicit. I think it would get tricky trying to distinguish between String | Null
and a flexible String
type for the scrutinee. If we start getting complaints about the warning, we can revisit it in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the type is String | Null
with explcit nullls, I just find it confusing to call the wildcard case "unreachable", because the types clearly indicate what values are covered or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is the intention for this warning to issue it only when the scrutinee has a flexible type?
How would we best test for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the isNullable
is the problem. There are some other things I don't like about this part of code. I will try to do
a refactor to this part.
val s2 = s | ||
s2 match | ||
case s3: String => println(1) | ||
case _ => println(2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please considering adding more tests:
def f(s: String) = s match
case _: String =>
case _ =>
def f(s: String) = s match
case _: String =>
case null =>
def f(s: String | Null) = s match
case _: String =>
def f(s: String | Null) = s match
case _: String =>
case null =>
def f(s: String | Int | Null) = s match
case _: String =>
case null =>
// etc...
Another issue which can be improved: class A
class B
def f(s: A | B) = s match
case _: A => 0
case null => 1
case _: B => 2
case _ => 3 If we compile without explicit nulls, it says the last wildcard case is "Unreachable case except for null". If we change to It should be unreachable from the begining. |
I did some refactoring to def checkReachability(m: Match)(using Context): Unit = trace(i"checkReachability($m)"):
val selTyp = toUnderlying(m.selector.tpe).dealias
val isNullable = selTyp.isInstanceOf[FlexibleType] || selTyp.classSymbol.isNullableClass
val targetSpace = trace(i"targetSpace($selTyp)"):
if isNullable && !ctx.mode.is(Mode.SafeNulls)
then project(OrType(selTyp, ConstantType(Constant(null)), soft = false))
else project(selTyp)
@tailrec def recur(cases: List[CaseDef], prevs: List[Space], deferred: List[Tree]): Unit =
cases match
case Nil =>
case CaseDef(pat, guard, _) :: rest =>
val curr = trace(i"project($pat)")(project(pat))
val covered = trace("covered")(simplify(intersect(curr, targetSpace)))
val prev = trace("prev")(simplify(Or(prevs)))
if prev == Empty && covered == Empty then // defer until a case is reachable
recur(rest, prevs, pat :: deferred)
else
for pat <- deferred.reverseIterator
do report.warning(MatchCaseUnreachable(), pat.srcPos)
if pat != EmptyTree // rethrow case of catch uses EmptyTree
&& !pat.symbol.isAllOf(SyntheticCase, butNot=Method) // ExpandSAMs default cases use SyntheticCase
&& isSubspace(covered, prev)
then
val nullOnly = isNullable && rest.isEmpty && isWildcardArg(pat)
val msg = if nullOnly then MatchCaseOnlyNullWarning() else MatchCaseUnreachable()
report.warning(msg, pat.srcPos)
val newPrev = if guard.isEmpty then covered :: prevs else prevs
recur(rest, newPrev, Nil)
recur(m.cases, Nil, Nil)
end checkReachability I think you also need to add a case to case tp: FlexibleType => List(tp.underlying, ConstantType(Constant(null))) and a case to case tp: FlexibleType => tp.derivedFlexibleType(toUnderlying(tp.underlying)) A flexible type is considered "nullable" as it may contain null value, and it is key to keep the flexible type in the space. I don't think |
Improve warning for wildcard matching only null under the explicit nulls flag
Fixes #21577
I have signed the CLA