Skip to content

Commit 51588a4

Browse files
asukaminato0721meta-codesync[bot]
authored andcommitted
fix Match case on type don't exaust on types #598 (#1994)
Summary: Fixes #598 Updated Pattern::MatchAs so that, when a case introduces an alias, the resultant narrows are mirrored back onto the original match subject (without rebinding names twice), ensuring later branches see the refined type. Introduced NarrowOp::rebase_subject plus facet-merging helpers and reworked NarrowOps::and_for_subject in to re-target alias narrows, compose facet chains/origins, and insert the rebased operations without placeholder pollution so negated flows correctly eliminate matched variants. Pull Request resolved: #1994 Test Plan: Added test_match_alias_narrows_subject Reviewed By: rchen152 Differential Revision: D90406316 Pulled By: stroxler fbshipit-source-id: 9f01f2c5955e188562285cdf054e3df09ce403dc
1 parent 6bef521 commit 51588a4

4 files changed

Lines changed: 86 additions & 4 deletions

File tree

pyrefly/lib/binding/narrow.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,43 @@ impl NarrowOp {
320320
_ => *self = Self::Or(vec![self.clone(), other]),
321321
}
322322
}
323+
324+
pub fn rebase_subject(&self, subject: &NarrowingSubject) -> Self {
325+
match self {
326+
Self::Atomic(prop, op) => {
327+
Self::Atomic(rebase_facet_subject(subject, prop.as_ref()), op.clone())
328+
}
329+
Self::And(ops) => Self::And(ops.map(|op| op.rebase_subject(subject))),
330+
Self::Or(ops) => Self::Or(ops.map(|op| op.rebase_subject(subject))),
331+
}
332+
}
333+
}
334+
335+
fn rebase_facet_subject(
336+
subject: &NarrowingSubject,
337+
prop: Option<&FacetSubject>,
338+
) -> Option<FacetSubject> {
339+
match (subject, prop) {
340+
(NarrowingSubject::Name(_), None) => None,
341+
(NarrowingSubject::Name(_), Some(facet)) => Some(facet.clone()),
342+
(NarrowingSubject::Facets(_, base_facet), None) => Some(base_facet.clone()),
343+
(NarrowingSubject::Facets(_, base_facet), Some(facet)) => {
344+
Some(merge_facet_subjects(base_facet, facet))
345+
}
346+
}
347+
}
348+
349+
fn merge_facet_subjects(base: &FacetSubject, extra: &FacetSubject) -> FacetSubject {
350+
let mut chain = base.chain.facets().clone();
351+
chain.extend(extra.chain.facets().clone());
352+
let origin = match (base.origin, extra.origin) {
353+
(FacetOrigin::GetMethod, _) | (_, FacetOrigin::GetMethod) => FacetOrigin::GetMethod,
354+
_ => FacetOrigin::Direct,
355+
};
356+
FacetSubject {
357+
chain: UnresolvedFacetChain::new(chain),
358+
origin,
359+
}
323360
}
324361

325362
#[derive(Clone, Debug, Default)]
@@ -330,6 +367,19 @@ impl NarrowOps {
330367
Self(SmallMap::new())
331368
}
332369

370+
pub fn and_for_subject(&mut self, subject: &NarrowingSubject, op: NarrowOp, range: TextRange) {
371+
let name = subject.name().clone();
372+
match self.0.entry(name) {
373+
Entry::Occupied(mut entry) => {
374+
entry.get_mut().0.and(op);
375+
entry.get_mut().1 = range;
376+
}
377+
Entry::Vacant(entry) => {
378+
entry.insert((op, range));
379+
}
380+
}
381+
}
382+
333383
pub fn negate(&self) -> Self {
334384
Self(
335385
self.0

pyrefly/lib/binding/pattern.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,27 @@ impl<'a> BindingsBuilder<'a> {
7979
Pattern::MatchAs(p) => {
8080
// If there's no name for this pattern, refine the variable being matched
8181
// If there is a new name, refine that instead
82+
let original_subject = match_subject.clone();
83+
let alias_name = p.name.as_ref().map(|name| name.id.clone());
8284
let mut subject = match_subject;
8385
if let Some(name) = &p.name {
8486
self.bind_definition(name, Binding::Forward(subject_idx), FlowStyle::Other);
8587
subject = Some(NarrowingSubject::Name(name.id.clone()));
8688
};
8789
if let Some(pattern) = p.pattern {
88-
self.bind_pattern(subject, *pattern, subject_idx)
90+
let mut narrow_ops = self.bind_pattern(subject, *pattern, subject_idx);
91+
if let (Some(alias_name), Some(original_subject)) =
92+
(&alias_name, &original_subject)
93+
&& alias_name != original_subject.name()
94+
&& let Some((alias_op, range)) = narrow_ops.0.get(alias_name).cloned()
95+
{
96+
narrow_ops.and_for_subject(
97+
original_subject,
98+
alias_op.rebase_subject(original_subject),
99+
range,
100+
);
101+
}
102+
narrow_ops
89103
} else {
90104
NarrowOps::new()
91105
}

pyrefly/lib/test/flow_branching.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ def test(x: int):
401401
case 1:
402402
assert_type(x, Literal[1])
403403
case 2 as q:
404-
assert_type(x, int)
404+
assert_type(x, Literal[2])
405405
assert_type(q, Literal[2])
406406
case q:
407407
assert_type(x, int)
@@ -518,7 +518,7 @@ def fun(x: A | B | C) -> None:
518518
assert_type(x, B)
519519
match x:
520520
case B(3, "B") as y:
521-
assert_type(x, A | B | C)
521+
assert_type(x, B)
522522
assert_type(y, B)
523523
match x:
524524
case A(1, "a") | B(2, "b"):
@@ -635,7 +635,7 @@ def test(x: Foo | Bar) -> None:
635635
assert_type(x, Bar)
636636
assert_type(x.x, str) # we want to narrow this to Literal["bar"]
637637
case Bar(a) as b:
638-
assert_type(x, Foo | Bar)
638+
assert_type(x, Bar)
639639
assert_type(b, Bar)
640640
assert_type(a, str)
641641
assert_type(b, Bar)

pyrefly/lib/test/pattern_match.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ def f0(x: int | str):
9999
"#,
100100
);
101101

102+
testcase!(
103+
test_match_alias_narrows_subject,
104+
r#"
105+
from typing import assert_never, assert_type
106+
107+
def my_method(str_or_int: str | int) -> str:
108+
match str_or_int:
109+
case str() as str_data:
110+
assert_type(str_or_int, str)
111+
return str_data
112+
case int() as int_data:
113+
assert_type(str_or_int, int)
114+
return str(int_data)
115+
case _:
116+
assert_never(str_or_int)
117+
"#,
118+
);
119+
102120
testcase!(
103121
test_class_match_with_args_not_exhaustive,
104122
r#"

0 commit comments

Comments
 (0)