-
Notifications
You must be signed in to change notification settings - Fork 408
Support indirect match subjects (match get_result())
#3865
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,9 @@ use ruff_python_ast::Number; | |
| use ruff_python_ast::Pattern; | ||
| use ruff_python_ast::PatternKeyword; | ||
| use ruff_python_ast::StmtMatch; | ||
| use ruff_python_ast::name::Name; | ||
| use ruff_text_size::Ranged; | ||
| use ruff_text_size::TextRange; | ||
|
|
||
| use crate::binding::binding::Binding; | ||
| use crate::binding::binding::BindingExpect; | ||
|
|
@@ -48,18 +50,39 @@ enum MatchSubject { | |
| None, | ||
| /// A single match subject (e.g., `match x:`). | ||
| Single(NarrowingSubject), | ||
| /// A local-only subject for matching non-name expressions (e.g., `match await f():`). | ||
| /// Python evaluates the subject once before matching, so we need a stable internal | ||
| /// subject for branch narrowing while diagnostics still point at the source expression. | ||
| Synthetic { | ||
| subject: NarrowingSubject, | ||
| display_subject_range: TextRange, | ||
| }, | ||
| /// Per-element subjects from a tuple match (e.g., `match x, y:`). | ||
| Tuple(Vec<Option<NarrowingSubject>>), | ||
| } | ||
|
|
||
| impl MatchSubject { | ||
| /// Extract a single narrowing subject, if this is `Single`. | ||
| /// Extract a single narrowing subject, if available. | ||
| fn as_single(&self) -> Option<&NarrowingSubject> { | ||
| match self { | ||
| MatchSubject::Single(s) => Some(s), | ||
| MatchSubject::Single(s) | MatchSubject::Synthetic { subject: s, .. } => Some(s), | ||
| _ => Option::None, | ||
| } | ||
| } | ||
|
|
||
| fn is_synthetic(&self) -> bool { | ||
| matches!(self, MatchSubject::Synthetic { .. }) | ||
| } | ||
|
|
||
| fn display_subject_range(&self, fallback: TextRange) -> TextRange { | ||
| match self { | ||
| MatchSubject::Synthetic { | ||
| display_subject_range, | ||
| .. | ||
| } => *display_subject_range, | ||
| _ => fallback, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<'a> BindingsBuilder<'a> { | ||
|
|
@@ -554,7 +577,10 @@ impl<'a> BindingsBuilder<'a> { | |
| } else { | ||
| match expr_to_subjects(&x.subject).first() { | ||
| Some(s) => MatchSubject::Single(s.clone()), | ||
| None => MatchSubject::None, | ||
| None => MatchSubject::Synthetic { | ||
| subject: NarrowingSubject::Name(Name::new_static("$match_subject")), | ||
| display_subject_range: x.subject.range(), | ||
| }, | ||
| } | ||
| }; | ||
| let mut exhaustive = false; | ||
|
|
@@ -667,7 +693,15 @@ impl<'a> BindingsBuilder<'a> { | |
| if exhaustive { | ||
| self.finish_exhaustive_fork(); | ||
| } else { | ||
| let narrow_entries = self.build_narrow_entries(&negated_prev_ops); | ||
| let narrow_entries = if match_subject.is_synthetic() { | ||
| negated_prev_ops | ||
| .0 | ||
| .values() | ||
| .map(|(op, range)| (subject_idx, Box::new(op.clone()), *range)) | ||
| .collect() | ||
| } else { | ||
| self.build_narrow_entries(&negated_prev_ops) | ||
| }; | ||
| // Create BindingExpect only if we have a narrowing subject (for exhaustiveness warnings) | ||
| if let Some(narrowing_subject) = match_subject.as_single() | ||
| && let Some((op, range)) = negated_prev_ops.0.get(narrowing_subject.name()) | ||
|
|
@@ -678,7 +712,14 @@ impl<'a> BindingsBuilder<'a> { | |
| subject_idx, | ||
| narrowing_subject: narrowing_subject.clone(), | ||
| narrow_ops_for_fall_through: (Box::new(op.clone()), *range), | ||
| subject_range: x.subject.range(), | ||
| subject_range: match_subject.display_subject_range(x.subject.range()), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're effectively matching on let (subject_range, display_subject_range) = match match_subject {
MatchSubject::Synthetic { display_subject_range, .. } => (display_subject_range, Some(display_subject_range)),
_ => (x.subject.range(), None)
};would make this more explicit and hence easier to follow. (This would also make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, I pressed the wrong button. This could also turn into a subject_range: display_subject_range.unwrap_or_else(|| x.subject.range()),
display_subject_range, |
||
| display_subject_range: match match_subject { | ||
| MatchSubject::Synthetic { | ||
| display_subject_range, | ||
| .. | ||
| } => Some(display_subject_range), | ||
| _ => None, | ||
| }, | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
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.
Returning an
Option<TextRange>and leaving it to the caller to determine what they want to do with aNoneseems more useful. This will also simplify some code below.