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

Non exhaustiveness of patterns #1318

Draft
wants to merge 36 commits into
base: main
Choose a base branch
from

Conversation

gzanitti
Copy link
Contributor

@gzanitti gzanitti commented Apr 25, 2024

This PR implements non-exhaustiveness check for matchs (issue #1287). Based on the usefulness implementation proposed in the rustc_pattern_analysis[1] module of the Rust compiler. A match is exhaustive iff the wildcard _ pattern is not useful with respect to the patterns in the match. The values returned by usefulness are used to tell the user which values are missing ([] where the match is exhaustive).

Still in draft until I finish analyzing some edge cases.

[1] https://doc.rust-lang.org/nightly/nightly-rustc/rustc_pattern_analysis/usefulness/index.html

ast/src/parsed/mod.rs Outdated Show resolved Hide resolved
@chriseth
Copy link
Member

Ah nice! I asked because I was not sure if the other function is maybe different. One small note: I think currently an enum variant is always marked as refutable, but with this checker, I think we should be more exact: It is irrefutable if and only if it is the only variant of the enum.

@gzanitti
Copy link
Contributor Author

gzanitti commented Apr 25, 2024

Ah nice! I asked because I was not sure if the other function is maybe different. One small note: I think currently an enum variant is always marked as refutable, but with this checker, I think we should be more exact: It is irrefutable if and only if it is the only variant of the enum.

Cool! Just to confirm:

enum Name {
    Variant1,
    Variant2,
}

is represented as Enum(Name, [Variant1, Variant2]), right?

Btw, I need to check/fix an edge cases with respect to the use of elipsis inside arrays, and I think this PR will be ready to be reviewed :)

ast/src/parsed/mod.rs Outdated Show resolved Hide resolved
@chriseth
Copy link
Member

chriseth commented Apr 25, 2024

Cool! Just to confirm:

enum Name {
    Variant1,
    Variant2,
}

is represented as Enum(Name, [Variant1, Variant2]), right?

Not sure I understood correctly but given that enum declaration, we could use it in a pattern like this:

match x {
  Name::Variant1 => ...,
  Name::Variant2 => ...,
}

The second parameter of Pattern::Enum is the data fields of an enum if it has them, i.e. Name::Variant1(x, 2) => ....

If you consider the following enum definition:

enum SingleVarEnum {
  Var(int, int)
}

Then we could do the following because the pattern is irrefutable:

let SingleVarEnum::Var(x, y) = m;

Of course we still need to check that the inner patterns are all irrefutable, so it would not work for let SingleVar:Enum::Var(1, y) = m.

ast/src/parsed/pattern.rs Outdated Show resolved Hide resolved
ast/src/parsed/pattern.rs Outdated Show resolved Hide resolved
@gzanitti
Copy link
Contributor Author

Thanks for your comments @chriseth. Last night I found a small bug when specializing patterns with internal parameters (like arrays and tuples). I'll fix that and take care of all your comments. It should be ready by tonight, sorry if it's taking longer than expected.

@chriseth
Copy link
Member

No worries, take your time, it's not easy navigating in a new codebase.

ast/src/parsed/pattern.rs Outdated Show resolved Hide resolved

use super::{asm::SymbolPath, visitor::Children};

/// When a pattern is specialised, in some cases the algorithm will return
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it makes sense to move this a bit down so that Pattern is the first element in this file?


/// Check for irrefutable patterns in pattern tuples. Since pattern tuples can contain
/// internal patterns, irrefutability only applies in cases of unique patterns (len == 1).

Copy link
Member

Choose a reason for hiding this comment

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

Does this work fine if you have an empty line without comment?

/// If (_, _) was obtained from specialization, it is not true that the pattern is irrefutable
/// because having more than one element implies that the evaluation is performed on internal patterns.
/// If, on the other hand, the pattern tuple is `(Pattern::Tuple(_,_), )``, irrefutability applies in the standard way.
pub fn is_irrefutable(&self) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

Hm, I cannot find any use of this function...

Comment on lines +42 to +44
/// If the constructor is shared with the pattern, specialize the pattern removing
/// "one layer" (the constructor) and returning internal patterns.
/// Return None if the pattern can't be specialized with the constructor or if the pattern is empty.
Copy link
Member

Choose a reason for hiding this comment

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

I still don't really understand this routine, but maybe adding the following items to the docstring helps:

We are only interested in the outermost type-constructor of the constructor pattern and ignore the fields and the inner patterns.

And maybe also a variant of the following:

matches!(c(v_1, .., v_n), p)
<=> specialize(c, p) returns something
    && matches!((v_1, .., v_n), specialize(c, p))

}
}

impl Default for PatternTuple {
Copy link
Member

Choose a reason for hiding this comment

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

Isn't that the same as derive(Default)?

Comment on lines +192 to +194
/// If the constructor is shared with the pattern, specialize the pattern removing
/// "one layer" (the constructor) and returning internal patterns.
/// Return None if the pattern can't be specialized using the constructor.
Copy link
Member

@chriseth chriseth Apr 30, 2024

Choose a reason for hiding this comment

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

Ok, I think this part I understand now. Let me rephrase it in my own words and then we can see how we want to combine the docs.

Suggested change
/// If the constructor is shared with the pattern, specialize the pattern removing
/// "one layer" (the constructor) and returning internal patterns.
/// Return None if the pattern can't be specialized using the constructor.
/// We are only interested in the type constructor part of `constructor` and ignore the inner patterns and fields.
/// If the constructor does not match the pattern, returns None.
/// If it matches, returns a vector of the inner fields of the pattern.

And then maybe add some formal statement like on https://doc.rust-lang.org/nightly/nightly-rustc/rustc_pattern_analysis/usefulness/index.html#specialization

Copy link
Member

@chriseth chriseth Apr 30, 2024

Choose a reason for hiding this comment

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

Ok it seems I still have not understood it :)
The returned patterns correspond to the sub-items of the constructor, not of the pattern, right?
But they are taken from the pattern.

pub fn specialize(&self, constructor: &Self) -> Option<PatternTuple> {
match (constructor, self) {
(_, Pattern::CatchAll) => Some(PatternTuple { patterns: vec![] }),
(Pattern::CatchAll, _) => None,
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering, shouldn't this panic? CatchAll is not a constructor, is it?

}),

(Pattern::Enum(path1, patterns1), Pattern::Enum(path2, patterns2)) => {
if path1 != path2 {
Copy link
Member

Choose a reason for hiding this comment

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

Could be pulled into the match pattern

if path1 != path2 {
return None;
}
match (patterns1, patterns2) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
match (patterns1, patterns2) {
match (cons_patterns, patterns) {

Comment on lines +250 to +253
let pats = patterns2.as_ref().unwrap();
Some(PatternTuple {
patterns: pats.clone(),
})
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let pats = patterns2.as_ref().unwrap();
Some(PatternTuple {
patterns: pats.clone(),
})
Some(PatternTuple {
patterns: patterns2.as_ref().unwrap().clone(),
})

(or maybe even .cloned() cold work here?)

Copy link
Member

Choose a reason for hiding this comment

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

Oh wait, why don't you use Some(patterns) instead of Some(_) one line above?

(Pattern::Array(cons_patterns), Pattern::Array(patterns)) => {
let max_len = cons_patterns.len();
let mut constructors = Vec::new();
if patterns.contains(&Pattern::Ellipsis) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if patterns.contains(&Pattern::Ellipsis) {
if let Some(ellipsis_inde) = patterns.iter().position(|p| p ==&Pattern::Ellipsis) {

})
}
(Pattern::Array(cons_patterns), Pattern::Array(patterns)) => {
let max_len = cons_patterns.len();
Copy link
Member

Choose a reason for hiding this comment

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

cons_len? Also please pull it into the block where it is used.

(Pattern::Array(cons_patterns), Pattern::Array(patterns)) => {
let max_len = cons_patterns.len();
let mut constructors = Vec::new();
if patterns.contains(&Pattern::Ellipsis) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we assert that the constructor does not contain an ellipsis?

Comment on lines +215 to +216
let mut constructors = Vec::new();
if patterns.contains(&Pattern::Ellipsis) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let mut constructors = Vec::new();
if patterns.contains(&Pattern::Ellipsis) {
let constructorns = if patterns.contains(&Pattern::Ellipsis) {

@@ -381,6 +423,61 @@ impl PILAnalyzer {
fn driver(&self) -> Driver {
Driver(self)
}

/// Check if a new pattern is useful in a set of patterns.
Copy link
Member

Choose a reason for hiding this comment

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

What is the new pattern here?

@gzanitti gzanitti marked this pull request as draft May 2, 2024 13:14
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

2 participants