-
Notifications
You must be signed in to change notification settings - Fork 1.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
Fix #17467: Limit isNullable widening to stable TermRefs; remove under explicit nulls. #17470
Conversation
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.
Nicely confined changes!
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 agree this is definitely a more refined rule for singleton type!
BTW, is there any specific reason why
val c1: Null = null
val c2: c1.type = null
is disallowed under explicit nulls?
|
||
val b1: String | Null = "foo" | ||
val b2: b1.type = null // error | ||
summon[Null, b1.type] // error |
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.
summon[Null <:< b1.type]
I added a comment with the test you suggested. The reason is that allowing this under explicit nulls would require a dedicated subtyping rule (both in spec and in implementation), and would provide marginal/debatable value. So IMO it is better to leave it out. |
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.
LGTM
The Scala language specification has a peculiar clause about the nullness of singleton types of the form `path.type`. It says that `Null <:< path.type` if the *underlying* type `U` of `path` is nullable itself. The previous implementation of that rule was overly broad, as it indiscrimately widened all types. This resulted in problematic subtyping relationships like `Null <:< "foo"`. We do not widen anymore. Instead, we specifically handle `TermRef`s of stable members, which are how dotc represents singleton types. We also have a rule for `Null <:< null`, which is necessary for pattern matching exhaustivity to keep working in the presence of nulls.
The specified rule that `Null <:< x.type` when the underlying type `U` of `x` is nullable is dubious to begin with. Under explicit nulls, it becomes decidedly out of place. We now disable that rule under `-Yexplicit-nulls`.
1fb2206
to
3945e96
Compare
Yes. The Main is now 3.4. It can be safely merged. |
We've found 1 project (slick/slick) which failed in Open CB after merging this change. The failing code can be minimized to: class Foo:
def foo(flag: Boolean): this.type = if(flag) this else null Failing with: -- [E007] Type Mismatch Error: /Users/wmazur/projects/dotty/bisect/main.scala:4:49
4 | def foo(): this.type = if(flag) this else null
| ^^^^
|Found: Null
|Required: (Foo.this : Foo) The snippet above makes no sense to me so I don't treat it as a regression. |
Indeed, that makes absolutely no sense, and is precisely the kind of soundness holes that this PR is meant to fix. |
I've found 1 more project failing due this change simy4/coregex, which also should not be a regression: type Matching[A >: Null <: String, Regex >: Null <: String with Singleton] <: A
def uuid: Matching[String, "[0-9a-f]"] = ??? // error Type argument ("[0-9a-f]" : String) does not conform to lower bound Null The obious workaround it removing the null bound which allows to compile it: type Matching[A <: String, Regex <: String with Singleton] <: A
def uuid: Matching[String, "[0-9a-f]"] = ???
|
Actually, after thinking about this more, I think that particular case, with the |
…4.x (#2801) After merging scala/scala3#17470 in 3.4.x and running Open Community Build we found out it is no longer compiling - [build logs](https://github.com/VirtusLab/community-build3/actions/runs/5836591202/job/15831389412). However, the compiler team has decided that that reason for that is not a new regression, but rather unsafe usage of `null` for method returning `this.type` which can lead to unsoundness. Without further inspection of code it's hard to decide if returning to null is unsafe for this use case. To allow for further testing this project in the Open Community Build I propose a temporary workaround which explcitly cast null to `this.type` to allow to pass compilation.
The Scala language specification has a peculiar clause about the nullness of singleton types of the form
path.type
. It says thatNull <:< path.type
if the underlying typeU
ofpath
is nullable itself.The previous implementation of that rule was overly broad, as it indiscrimately widened all types. This resulted in problematic subtyping relationships like
Null <:< "foo"
.We do not widen anymore. Instead, we specifically handle
TermRef
s of stable members, which are how dotc represents singleton types. We also have a rule forNull <:< null
, which is necessary for pattern matching exhaustivity to keep working in the presence of nulls.Under explicit nulls, remove the rule Null <:< x.type.
The specified rule that
Null <:< x.type
when the underlying typeU
ofx
is nullable is dubious to begin with. Under explicit nulls, it becomes decidedly out of place. We now disable that rule under-Yexplicit-nulls
.