-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #17467: Limit isNullable widening to stable TermRefs; remove under explicit nulls. #17470
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
Conversation
odersky
left a comment
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!
noti0na1
left a comment
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 = nullis 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. |
noti0na1
left a comment
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 NullThe 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.typeif the underlying typeUofpathis 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
TermRefs 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.typewhen the underlying typeUofxis nullable is dubious to begin with. Under explicit nulls, it becomes decidedly out of place. We now disable that rule under-Yexplicit-nulls.