ruff
b99f71a4 - [ty] Return type inference (#29253)

Commit
29 days ago
[ty] Return type inference (#29253) ## Summary <img width="331" height="155" alt="image" src="https://github.com/user-attachments/assets/8e1b5c40-57b7-44ec-a197-3770e9fc3236" align=right /> This PR adds return type inference as a new feature to ty. The feature is currently *disabled by default*. Users can activate the feature by setting `analysis.return-type-inference` to `"always"`. In a standalone function, the inferred return type is simply the union of the types of all reachable `return`ed expressions, as shown in the image on the right. If control flow can reach the end of the function scope, we add `None` to the union. In a (non-final) method on a (non-final) class, we additionally union with `Unknown` to allow child classes to widen the intended return type. For example, imagine ```py class TaskBase: def compute(self): return None class IntTask(TaskBase): def compute(self): return 42 ``` Here, we infer a return type of `None | Unknown` for `TaskBase.compute`. Otherwise, if we inferred `None`, we would prohibit child classes from returning something else, which seems undesirable. Another interesting scenario is when the base class method is annotated, but the child class method is not: ```py class Base: def method(self) -> int: return 0 class Child(Base): def method(self): return 1 ``` Here, we respect the base class definition and infer `Child.method` as returning `int`, not `int | Unknown`. This work is based on https://github.com/astral-sh/ruff/pull/17371 by @mtshiba (thank you!) There are a few notable things that are _not_ implemented here: - The type of the function itself currently stays unchanged, that is: we do not infer a signature with the inferred return type. Concretely: `my_function()` may be inferred as `int`, but `my_function` itself would still be inferred as `def my_function() -> Unknown`. We have existing tests with TODOs for this and I plan to tackle this in a follow up. - We do not yet have return type inference for generator functions or async functions - We do not attempt to solve the problem of "escaping" inner types here (see tests with TODOs) - We do not add LSP/playground features related to this feature, but plan to do so soon (display return type hints). - There is a limitation around `functools.wraps` that's present in the ecosystem report. This will hopefully be solved by https://github.com/python/typeshed/pull/16515 - There is a [limitation around `NotImplemented`](https://github.com/astral-sh/ty/issues/1150#issuecomment-6140701931) which hasn't been resolved yet. The solution is described [here](https://github.com/astral-sh/ty/issues/1150#issuecomment-6147899750). - Fixpoint iteration currently uses `Divergent` as a provisional value. I did think about using the new μ-types, but that currently leaves too many questions unanswered: How would we represent an anonymous μ-type? How would the (provisional) recursive marker `R` interact with other types during inference (especially since those operations require "closed" recursive types, and `R` would still be open)? closes https://github.com/astral-sh/ty/issues/128 ## Test Plan - Changed assertions in the previously reviewed & merged mdtests - Studied the ecosystem impact and addressed many problems --------- Co-authored-by: Shunsuke Shibayama <sbym1346@gmail.com>
Author
Parents
Loading