Skip to content

Commit ce5fc90

Browse files
committed
[ty] Support as-patterns in reachability analysis
1 parent c907078 commit ce5fc90

File tree

8 files changed

+76
-6
lines changed

8 files changed

+76
-6
lines changed

crates/ty_python_semantic/resources/mdtest/conditional/match.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,25 @@ def _(target: None | Foo):
350350
reveal_type(y) # revealed: Literal[1, 3]
351351
```
352352

353+
## `as` patterns
354+
355+
```py
356+
def _(target: int | str):
357+
y = 1
358+
359+
match target:
360+
case 1 as x:
361+
y = 2
362+
reveal_type(x) # revealed: @Todo(`match` pattern definition types)
363+
case "foo" as x:
364+
y = 3
365+
reveal_type(x) # revealed: @Todo(`match` pattern definition types)
366+
case _:
367+
y = 4
368+
369+
reveal_type(y) # revealed: Literal[2, 3, 4]
370+
```
371+
353372
## Guard with object that implements `__bool__` incorrectly
354373

355374
```py

crates/ty_python_semantic/resources/mdtest/exhaustiveness_checking.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,32 @@ def no_invalid_return_diagnostic_here_either[T](x: A[T]) -> ASub[T]:
338338
# is null and void (and therefore we don't emit a diagnostic)
339339
return x
340340
```
341+
342+
## More `match` pattern types
343+
344+
### `as` patterns
345+
346+
```py
347+
from typing import assert_never
348+
349+
def as_pattern_exhaustive(subject: int | str):
350+
match subject:
351+
case int() as x:
352+
pass
353+
case str() as y:
354+
pass
355+
case _:
356+
no_diagnostic_here
357+
358+
assert_never(subject)
359+
360+
def as_pattern_non_exhaustive(subject: int | str):
361+
match subject:
362+
case int() as x:
363+
pass
364+
case _:
365+
this_should_be_an_error # error: [unresolved-reference]
366+
367+
# this diagnostic is correct: the inferred type of `subject` is `str`
368+
assert_never(subject) # error: [type-assertion-failure]
369+
```

crates/ty_python_semantic/resources/mdtest/import/star.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,19 @@ class ContextManagerThatMightNotRunToCompletion:
182182
with ContextManagerThatMightNotRunToCompletion() as L:
183183
U = ...
184184

185-
match 42:
185+
def get_object() -> object:
186+
pass
187+
188+
match get_object():
186189
case {"something": M}:
187190
...
188191
case [*N]:
189192
...
190193
case [O]:
191194
...
192-
case P | Q: # error: [invalid-syntax] "name capture `P` makes remaining patterns unreachable"
195+
case I(foo=R):
193196
...
194-
case object(foo=R):
197+
case P | Q:
195198
...
196199

197200
match 56:

crates/ty_python_semantic/src/semantic_index/builder.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,13 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
838838
.collect();
839839
PatternPredicateKind::Or(predicates)
840840
}
841+
ast::Pattern::MatchAs(pattern) => PatternPredicateKind::As(
842+
pattern
843+
.pattern
844+
.as_ref()
845+
.map(|p| Box::new(self.predicate_kind(p))),
846+
pattern.name.clone().map(|name| name.id),
847+
),
841848
_ => PatternPredicateKind::Unsupported,
842849
}
843850
}

crates/ty_python_semantic/src/semantic_index/predicate.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
1010
use ruff_db::files::File;
1111
use ruff_index::{Idx, IndexVec};
12-
use ruff_python_ast::Singleton;
12+
use ruff_python_ast::{Singleton, name::Name};
1313

1414
use crate::db::Db;
1515
use crate::semantic_index::expression::Expression;
@@ -136,6 +136,7 @@ pub(crate) enum PatternPredicateKind<'db> {
136136
Value(Expression<'db>),
137137
Or(Vec<PatternPredicateKind<'db>>),
138138
Class(Expression<'db>, ClassPatternKind),
139+
As(Option<Box<PatternPredicateKind<'db>>>, Option<Name>),
139140
Unsupported,
140141
}
141142

crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ fn pattern_kind_to_type<'db>(db: &'db dyn Db, kind: &PatternPredicateKind<'db>)
340340
PatternPredicateKind::Or(predicates) => {
341341
UnionType::from_elements(db, predicates.iter().map(|p| pattern_kind_to_type(db, p)))
342342
}
343+
PatternPredicateKind::As(pattern, _) => pattern
344+
.as_deref()
345+
.map(|p| pattern_kind_to_type(db, p))
346+
.unwrap_or(Type::object(db)),
343347
PatternPredicateKind::Unsupported => Type::Never,
344348
}
345349
}
@@ -761,6 +765,10 @@ impl ReachabilityConstraints {
761765
}
762766
})
763767
}
768+
PatternPredicateKind::As(pattern, _) => pattern
769+
.as_deref()
770+
.map(|p| Self::analyze_single_pattern_predicate_kind(db, p, subject_ty))
771+
.unwrap_or(Truthiness::AlwaysTrue),
764772
PatternPredicateKind::Unsupported => Truthiness::Ambiguous,
765773
}
766774
}

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3584,7 +3584,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
35843584
fn infer_nested_match_pattern(&mut self, pattern: &ast::Pattern) {
35853585
match pattern {
35863586
ast::Pattern::MatchValue(match_value) => {
3587-
self.infer_expression(&match_value.value);
3587+
self.infer_maybe_standalone_expression(&match_value.value);
35883588
}
35893589
ast::Pattern::MatchSequence(match_sequence) => {
35903590
for pattern in &match_sequence.patterns {
@@ -3619,7 +3619,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
36193619
for keyword in &arguments.keywords {
36203620
self.infer_nested_match_pattern(&keyword.pattern);
36213621
}
3622-
self.infer_expression(cls);
3622+
self.infer_maybe_standalone_expression(cls);
36233623
}
36243624
ast::Pattern::MatchAs(match_as) => {
36253625
if let Some(pattern) = &match_as.pattern {

crates/ty_python_semantic/src/types/narrow.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
410410
PatternPredicateKind::Or(predicates) => {
411411
self.evaluate_match_pattern_or(subject, predicates, is_positive)
412412
}
413+
PatternPredicateKind::As(pattern, _) => pattern
414+
.as_deref()
415+
.and_then(|p| self.evaluate_pattern_predicate_kind(p, subject, is_positive)),
413416
PatternPredicateKind::Unsupported => None,
414417
}
415418
}

0 commit comments

Comments
 (0)