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

Emit deferred reachability warnings & fix boxing/unboxing adapting #13485

Merged
merged 7 commits into from Sep 13, 2021

Conversation

dwijnand
Copy link
Member

@dwijnand dwijnand commented Sep 7, 2021

Emit deferred reachability warnings
There are three kinds of unreachable cases:

  1. the cases that are provably disjoint from the scrutinee type
  2. the cases that are not subtypes of the scrutinee type
  3. the cases that are covered by a previous cases, aka an overlap

The first one should be (I didn't review or stress test it) handled when
type-checking the pattern.

The third one is the safest, in terms of not emitting false positives to
the user, because it's within the conservative approximation of the
scrutinee space and the previous cases spaces.

The second one is where it gets tricky, because we don't know they're
part of the scrutinee type but we also don't know they're not part of
the scrutinee type. Erasure is the simplest example: if the scrutinee
type is type ThisTree <: Tree then if you write cases for subtypes of
Tree that aren't subtypes of ThisTree, then they're not subtypes nor are
they provably disjoint from the scrutinee type.

Martin mentioned that it's been tried a few times to restrict patterns
to subtypes, but that it's been found too restrictive and provably
disjoint is the right choice for type errors.

So the logic that I recently reimplemented was to only emit reachability
warnings after a reachable a case was found, therefore avoiding to emit
any warnings in the ThisTree case, emitting for all the overlap
unreachable cases.

The problem was that accidentally introduced a dependency on the
ordering of the cases, where unreachable initial cases aren't warned but
reordered they would. So I hold onto those references and unbuffer as
soon as we find a provably reachable case (covered != Empty) or a
provably unreachable case (prev != Empty).

Also, I've had separate conversations with Seth and Lukas about whether
match analysis should just widen those non-class types, but it's also
plausible that a user has created a whole type hierarchy with type
members and would prefer for it to not discard it all by widening to
class/object types, but instead reason within the bounds of the given
scrutinee type.


Emitting deferred reachability warnings (the previous commit) caused
tests/patmat/boxing.scala to start failing, because my logic on boxing
and unboxing wasn't actually right.

So this fixes how the case type (tp1) gets adapted for the scrutinee
type (tp2), with two examples in the comments from that boxing test
case. Also organise that constant types are converted if boxing isn't
involved.

@dwijnand dwijnand force-pushed the emit-deferred-reachability-warnings branch from 1033e04 to 3d8472c Compare September 7, 2021 17:35
@dwijnand dwijnand marked this pull request as ready for review September 7, 2021 19:04
smarter
smarter previously requested changes Sep 9, 2021
Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main commit in this PR just says "Emit deferred reachability warnings" but doesn't change any test so it's not clear to me what it does, it'd be good to have a more detailed commit message and tests in the same commit if possible.

@dwijnand dwijnand marked this pull request as draft September 9, 2021 17:28
@dwijnand dwijnand force-pushed the emit-deferred-reachability-warnings branch from 14c0875 to 08ef279 Compare September 10, 2021 09:15
Copy link
Contributor

@liufengyun liufengyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@dwijnand dwijnand marked this pull request as ready for review September 11, 2021 16:30
@@ -533,7 +533,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
/** Is `tp1` a subtype of `tp2`? */
def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) {
if tp1 == constantNullType && !ctx.explicitNulls then tp2 == constantNullType
else adaptType(tp1, tp2) <:< tp2
else tp1 <:< tp2 || adaptType(tp1, tp2) <:< tp2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: I'm wondering if it is possible to push the 2nd <:< tp2 into adaptType(tp1, tp2) to make the new "subtyping" rule more explicit? (I haven't thought through the details, please ignore if it does not make sense technically).

Copy link
Member Author

@dwijnand dwijnand Sep 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't tell if I'm being cautious or just short-sighted and bone-headed, but I kind of don't want to get into the business of trying to integrate the type-fudging we're doing here, tightly into the subtyping rules. Re-reading your earlier "augment the rules" comment, realising it's what you meant, rather than this. We tried an earlier suggestion from Guillaume of using "isPrimToBox" alone, but that only considers the classSymbol alone, incorrectly making Integer <:< (1: Int) true. So maybe the "augment" idea isn't a good idea because the subtyping rules are already so complex? Or, again, I'm being short-sighted.

Adapting types has a lot of precedent in the codebase, such as in typer's tree adapting (where I lifted the ConstantType converting code) but also in TypeOps where there's a lot of type mapping to approximate things and try subtyping multiple times.

In many ways the subtyping rules are fixed, but I get the sense that even though fundamental changes don't happen often, there are changes in encoding or implementation detail that put all these auxiliary type mapping and approximating code paths at risk of diverging from their original intent (when unguarded by tests).

WDYT? Am I being too paranoid? Do you think it's reasonable easy for me to just sit down for a second and figure out how to get these silly cases to give the right answer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of don't want to get into the business of trying to integrate the type-fudging we're doing here, tightly into the subtyping rules

Sorry I wasn't clear. I don't mean change <:<. What I was proposing is some refactoring. Instead of tp1 <:< tp2 || adapt(tp1, tp2) <:< tp2, what about tp1 <:< tp2 || primitiveSubtype(tp1, tp2)?

As I mentioned, this is a minor thing, more of a style --- the current PR is good as it is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I had understood that. By integrating I meant primitiveSubtype must be some subset of the subtyping rules, which at least handles that Integer <:< (1: Int) is false.

@dwijnand dwijnand force-pushed the emit-deferred-reachability-warnings branch from fbc2e6f to 08ef279 Compare September 12, 2021 19:50
dwijnand and others added 7 commits September 13, 2021 09:29
There are three kinds of unreachable cases:

1. the cases that are provably disjoint from the scrutinee type
2. the cases that are not subtypes of the scrutinee type
3. the cases that are covered by a previous cases, aka an overlap

The first one should be (I didn't review or stress test it) handled when
type-checking the pattern.

The third one is the safest, in terms of not emitting false positives to
the user, because it's within the conservative approximation of the
scrutinee space and the previous cases spaces.

The second one is where it gets tricky, because we don't know they're
part of the scrutinee type but we also don't know they're _not_ part of
the scrutinee type.  Erasure is the simplest example: if the scrutinee
type is `type ThisTree <: Tree` then if you write cases for subtypes of
Tree that aren't subtypes of ThisTree, then they're not subtypes nor are
they provably disjoint from the scrutinee type.

Martin mentioned that it's been tried a few times to restrict patterns
to subtypes, but that it's been found too restrictive and provably
disjoint is the right choice for type errors.

So the logic that I recently reimplemented was to only emit reachability
warnings after a reachable a case was found, therefore avoiding to emit
any warnings in the ThisTree case, emitting for all the overlap
unreachable cases.

The problem was that accidentally introduced a dependency on the
ordering of the cases, where unreachable initial cases aren't warned but
reordered they would.  So I hold onto those references and unbuffer as
soon as we find a provably reachable case (covered != Empty) or a
provably unreachable case (prev != Empty).

Also, I've had separate conversations with Seth and Lukas about whether
match analysis should just widen those non-class types, but it's also
plausible that a user has created a whole type hierarchy with type
members and would prefer for it to not discard it all by widening to
class/object types, but instead reason within the bounds of the given
scrutinee type.
Emitting deferred reachability warnings (the previous commit) caused
tests/patmat/boxing.scala to start failing, because my logic on boxing
and unboxing wasn't actually right.

So this fixes how the case type (tp1) gets adapted for the scrutinee
type (tp2), with two examples in the comments from that boxing test
case.  Also organise that constant types are converted if boxing isn't
involved.
Co-authored-by: Guillaume Martres <smarter@ubuntu.com>
@dwijnand dwijnand force-pushed the emit-deferred-reachability-warnings branch from 08ef279 to 48790dd Compare September 13, 2021 08:32
@dwijnand dwijnand merged commit 74ebee0 into scala:master Sep 13, 2021
@dwijnand dwijnand deleted the emit-deferred-reachability-warnings branch September 13, 2021 13:14
@Kordyjan Kordyjan added this to the 3.1.1 milestone Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants