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 reachability of non-reducing match type children #13251

Merged
merged 1 commit into from Sep 7, 2021

Conversation

dwijnand
Copy link
Member

@dwijnand dwijnand commented Aug 4, 2021

The reduction of a match type gets "stuck" when a case doesn't match but
is also not provably disjoint from it. In these situations the match
type reduction returns NoType, which means that in A <:< B if B
reduces to NoType, then false will be returned.

In the reachability/Space logic subtype checks are used to refine the
candidate children of the scrutinee type so that impossible children
aren't marked as missing and possible cases aren't marked as
unreachable. To achieve that there is already some type mapping that
runs before subtyping to approximate the parent.

We extend that parent approximating logic so that non-reducing match
types in the parent don't result in all the candidate children being
rejected.

The motivating case is NonEmptyTuple, its single-child *:, and the
Tuple.Tail match type. In Tuple.Tail[NonEmptyTuple], case _ *: xs =>
won't match because NonEmptyTuple isn't a subtype of *: but it's
also not provably disjoint from it (indeed it's *: parent)! So the
reduction gets stuck, leading to NoType, leading to not a subtype.

I had initially looked to fix this for single-child abstract sealed
types, but that would still cause false positives in legitimate
multi-child sealed types and an exhaustive match type (see
WithExhaustiveMatch in patmat/i13189.scala). Also it's less scary to
change the subtyping wrapping logic rather than the actually match
type's reduction/matching logic.

In a way the problem is that subtyping checks is too boolean: the
candidate rejection wants to drop children that are definitely not
subtypes, but the match type reduction is too conservative by returning
NoType as soon as the obvious case isn't met. What subtyping should say
there is ¯_(ツ)_/¯

Copy link
Contributor

@OlivierBlanvillain OlivierBlanvillain left a comment

Choose a reason for hiding this comment

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

LGTM

compiler/src/dotty/tools/dotc/core/TypeOps.scala Outdated Show resolved Hide resolved
The reduction of a match type gets "stuck" when a case doesn't match but
is also not provably disjoint from it.  In these situations the match
type reduction returns NoType, which means that in `A <:< B` if B
reduces to NoType, then false will be returned.

In the reachability/Space logic subtype checks are used to refine the
candidate children of the scrutinee type so that impossible children
aren't marked as missing and possible cases aren't marked as
unreachable.  To achieve that there is already some type mapping that
runs before subtyping to approximate the parent.

We extend that parent approximating logic so that non-reducing match
types in the parent don't result in all the candidate children being
rejected.

The motivating case is NonEmptyTuple, its single-child `*:`, and the
`Tuple.Tail` match type.  In `Tuple.Tail[NonEmptyTuple]`, `case _ *: xs
=>` won't match because NonEmptyTuple isn't a subtype of `*:` but it's
also not provably disjoint from it (indeed it's `*:` parent)!  So the
reduction gets stuck, leading to NoType, leading to not a subtype.

I had initially looked to fix this for single-child abstract sealed
types, but that would still cause false positives in legitimate
multi-child sealed types and an exhaustive match type (see
WithExhaustiveMatch in patmat/i13189.scala).  Also it's less scary to
change the subtyping wrapping logic rather than the actually match
type's reduction/matching logic.

In a way the problem is that subtyping checks is too boolean: the
candidate rejection wants to drop children that are definitely not
subtypes, but the match type reduction is too conservative by returning
NoType as soon as the obvious case isn't met.  What subtyping should say
there is ¯\_(ツ)_/¯
@dwijnand dwijnand force-pushed the unreachable-match-type-subpattern branch from 6a85b09 to fb5b562 Compare September 7, 2021 17:31
@dwijnand dwijnand merged commit 4eae6fd into scala:master Sep 7, 2021
@dwijnand dwijnand deleted the unreachable-match-type-subpattern branch September 9, 2021 13:56
@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.

Unreachable case when matching with a match type subpattern
3 participants