[ty] improve reachability analysis for with statement#23136
[ty] improve reachability analysis for with statement#23136mtshiba wants to merge 4 commits intoastral-sh:mainfrom
Conversation
Typing conformance results regressed ❌The percentage of diagnostics emitted that were expected errors decreased from 82.27% to 82.10%. The percentage of expected errors that received a diagnostic held steady at 72.99%. Summary
False positives removedDetails
False positives addedDetails
|
|
c37085e to
31378c6
Compare
|
| Project | Old Status | New Status | Old Return Code | New Return Code |
|---|---|---|---|---|
setuptools |
abnormal exit | abnormal exit | 2 |
2 |
Diagnostic changes:
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
not-subscriptable |
106 | 1 | 0 |
unsupported-operator |
92 | 1 | 14 |
possibly-unresolved-reference |
101 | 0 | 0 |
possibly-missing-attribute |
33 | 5 | 36 |
invalid-argument-type |
29 | 1 | 7 |
no-matching-overload |
3 | 20 | 0 |
invalid-return-type |
9 | 1 | 2 |
unused-type-ignore-comment |
0 | 11 | 0 |
unresolved-attribute |
5 | 0 | 4 |
invalid-assignment |
1 | 2 | 0 |
invalid-await |
2 | 0 | 0 |
invalid-context-manager |
2 | 0 | 0 |
not-iterable |
0 | 0 | 2 |
| Total | 383 | 42 | 65 |
|
Rather than testing if the value returned by |
| context_manager_ty.class_member(db, "__exit__".into()) | ||
| }; | ||
| if let Place::Defined(exit) = exit_method.place | ||
| && let Some(__exit__) = exit.ty.as_function_literal() |
There was a problem hiding this comment.
I think we should use try_upcast_to_callable() here rather than special-casing functions
There was a problem hiding this comment.
In the async with case, __aexit__ is an asynchronous function, so the return type of the signature obtained with try_upcast_to_callable() is CoroutineType[..., ..., T].
Getting T from this is a bit tedious, and it's more straightforward to simply expect exit.ty to be a function literal type and take the raw signature.
Given that the specification regarding return types that suppress exceptions is very specific and conservative, it seems reasonable to treat only the case where exit.ty is a function literal as special.
Hmm, the spec says
This seems at odds with the logic you described (perhaps the spec is more ad hoc? But that's the spec...). |
|
Hmmmm I might be misremembering what mypy/pyright do here; sorry if so. I can check myself later. But what i described makes sense to me intuitively; a context manager that returns |
…ent to that of the try statement
7316511 to
c3c29a7
Compare
|
I realized that the control flow of a with statement can be expressed using the same mechanism as a try statement (ref: https://peps.python.org/pep-0343/#specification-the-with-statement). class ExceptionPropagator1:
def __enter__(self) -> None: ...
def __exit__(self, exc_type, exc_value, traceback) -> Literal[False]:
return False
def propagate5(x: int | str) -> None:
if isinstance(x, int):
with ExceptionPropagator1():
raise ValueError
# TODO: should be `str`
reveal_type(x) # revealed: int | strimport sys
def propagate5_try(x: int | str) -> None:
if isinstance(x, int):
mgr = ExceptionPropagator1()
exit = type(mgr).__exit__
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
raise ValueError
except:
exc = False
if not exit(mgr, *sys.exc_info()):
raise
finally:
if exc:
exit(mgr, None, None, None)
# TODO: should be `str`
reveal_type(x) # revealed: int | str |
Summary
This PR closes astral-sh/ty#152.
Test Plan
mdtest updated