Skip to content

Commit 848cb72

Browse files
[ty] Fix narrowing of nonlocal variables with conditional assignments (#22966)
## Summary Given: ```python def _(maybe_float: float | None, certain_int: int, flag: bool) -> None: if isinstance(maybe_float, int): return x = maybe_float def _() -> None: nonlocal x if flag: x = certain_int assert x is not None +x # error: Unary operator `+` is not supported for operand of type `int | float | None` ``` With the guard (removed in this PR), `assert x is not None` would have no effect, since `int | (float & ~int)` is not assignable to `(float & ~int) | None`. Closes astral-sh/ty#2649.
1 parent da7f33a commit 848cb72

2 files changed

Lines changed: 21 additions & 4 deletions

File tree

crates/ty_python_semantic/resources/mdtest/scopes/nonlocal.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,23 @@ def foo():
446446
if isinstance(x, str):
447447
reveal_type(x) # revealed: Never
448448
```
449+
450+
## Narrowing nonlocal variables with conditional assignments
451+
452+
When a nonlocal variable is conditionally reassigned and then narrowed via an assertion, the
453+
narrowing constraint should be applied correctly, even when the enclosing scope's type was itself
454+
narrowed (e.g., via an `isinstance` check).
455+
456+
```py
457+
def _(maybe_float: float | None, certain_int: int, flag: bool) -> None:
458+
if isinstance(maybe_float, int):
459+
return
460+
x = maybe_float
461+
def _() -> None:
462+
nonlocal x
463+
if flag:
464+
x = certain_int
465+
assert x is not None
466+
reveal_type(x) # revealed: int | float
467+
+x
468+
```

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11908,10 +11908,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1190811908
// Since an unbound binding is recorded even for an undefined place,
1190911909
// this can only happen if the code is unreachable
1191011910
// and therefore it is correct to set the result to `Never`.
11911-
let union = union.build();
11912-
if union.is_assignable_to(db, ty) {
11913-
ty = union;
11914-
}
11911+
ty = union.build();
1191511912
}
1191611913
}
1191711914
}

0 commit comments

Comments
 (0)