-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Use type context for inference of generic constructors #20933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-11-10 21:47:35.253883781 +0000
+++ new-output.txt 2025-11-10 21:47:38.684915587 +0000
@@ -16,7 +16,6 @@
aliases_explicit.py:59:5: error[type-assertion-failure] Argument does not have asserted type `int | str | None | list[list[int]]`
aliases_explicit.py:60:5: error[type-assertion-failure] Argument does not have asserted type `(...) -> None`
aliases_explicit.py:61:5: error[type-assertion-failure] Argument does not have asserted type `int | str`
-aliases_explicit.py:98:1: error[type-assertion-failure] Argument does not have asserted type `list[str]`
aliases_explicit.py:101:6: error[call-non-callable] Object of type `UnionType` is not callable
aliases_implicit.py:54:24: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[str, str]`?
aliases_implicit.py:60:5: error[type-assertion-failure] Argument does not have asserted type `int | str`
@@ -38,7 +37,6 @@
aliases_implicit.py:116:10: error[invalid-type-form] Variable of type `Literal[1]` is not allowed in a type expression
aliases_implicit.py:118:10: error[invalid-type-form] Variable of type `Literal["int"]` is not allowed in a type expression
aliases_implicit.py:119:10: error[invalid-type-form] Variable of type `Literal["int | str"]` is not allowed in a type expression
-aliases_implicit.py:128:1: error[type-assertion-failure] Argument does not have asserted type `list[str]`
aliases_implicit.py:133:6: error[call-non-callable] Object of type `UnionType` is not callable
aliases_newtype.py:15:1: error[type-assertion-failure] Argument does not have asserted type `int`
aliases_newtype.py:18:1: error[invalid-assignment] Object of type `NewType` is not assignable to `type`
@@ -570,11 +568,8 @@
generics_syntax_scoping.py:116:13: error[type-assertion-failure] Argument does not have asserted type `TypeVar`
generics_syntax_scoping.py:121:9: error[type-assertion-failure] Argument does not have asserted type `int | float | complex`
generics_syntax_scoping.py:124:13: error[type-assertion-failure] Argument does not have asserted type `int | float | complex`
-generics_type_erasure.py:28:1: error[type-assertion-failure] Argument does not have asserted type `Node[int]`
-generics_type_erasure.py:30:1: error[type-assertion-failure] Argument does not have asserted type `Node[str]`
generics_type_erasure.py:38:16: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | None`, found `Literal[""]`
generics_type_erasure.py:40:16: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | None`, found `Literal[0]`
-generics_type_erasure.py:47:1: error[type-assertion-failure] Argument does not have asserted type `int`
generics_type_erasure.py:56:1: error[type-assertion-failure] Argument does not have asserted type `bytes`
generics_typevartuple_args.py:16:34: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo(PEP 646), ...]`
generics_typevartuple_args.py:20:1: error[type-assertion-failure] Argument does not have asserted type `tuple[int, str]`
@@ -700,6 +695,7 @@
literals_literalstring.py:79:21: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `bool`
literals_literalstring.py:120:22: error[invalid-argument-type] Argument to function `literal_identity` is incorrect: Argument type `str` does not satisfy upper bound `LiteralString` of type variable `TLiteral`
literals_literalstring.py:134:51: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Argument type `str` does not satisfy upper bound `LiteralString` of type variable `T`
+literals_literalstring.py:134:51: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `LiteralString`, found `str`
literals_literalstring.py:167:1: error[type-assertion-failure] Argument does not have asserted type `A`
literals_literalstring.py:171:5: error[invalid-assignment] Object of type `list[LiteralString]` is not assignable to `list[str]`
literals_parameterizations.py:41:15: error[invalid-type-form] Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member
@@ -1003,5 +999,5 @@
typeddicts_usage.py:28:17: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
typeddicts_usage.py:28:18: error[invalid-key] Invalid key for TypedDict `Movie`: Unknown key "title"
typeddicts_usage.py:40:24: error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions. Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?
-Found 1005 diagnostics
+Found 1001 diagnostics
WARN A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details.
|
|
16c4d8a to
22bbbe3
Compare
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-argument-type |
52 | 269 | 3 |
invalid-assignment |
3 | 68 | 3 |
invalid-return-type |
2 | 7 | 6 |
unused-ignore-comment |
0 | 15 | 0 |
possibly-missing-attribute |
3 | 6 | 4 |
type-assertion-failure |
0 | 4 | 0 |
unresolved-attribute |
3 | 0 | 0 |
unsupported-operator |
2 | 1 | 0 |
no-matching-overload |
1 | 1 | 0 |
not-iterable |
0 | 1 | 0 |
| Total | 66 | 372 | 16 |
sharkdp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I know it's a bit daunting to go through the ecosystem diff, but the return on investment is typically quite good, as it tends to reveal problematic cases more often than not. This PR seems to have a non-trivial impact as well, so it'd be great to do a brief analysis to see if anything stands out.
| let return_ty = self.signature.return_ty?; | ||
| // For generic constructors, we use the type context to infer the specialization of the class | ||
| // instance instead of the method's return type. | ||
| let (inference_context, generic_constructor) = if let Type::BoundMethod(method) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Matching on explicit type variants is tempting, I know, but will break down when the callable is not precisely a bound method. For example, in a case like the following, the callable would be a union type:
class X[T]:
def __init__(self, value: T):
self.value = value
def _(flag: bool):
b: X[int | None] = X(1) if flag else X(2)
reveal_type(b)This might be difficult to fix here, and might be an acceptable limitation for now, but we should maybe add a test case and a TODO?
| let (inference_context, generic_constructor) = if let Type::BoundMethod(method) = | ||
| self.callable_type | ||
| && let Type::NominalInstance(instance) = method.self_instance(self.db) | ||
| && method.function(self.db).name(self.db) == "__init__" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it enough to match on __init__ or do we need a similar treatment of __new__ calls (which we generally don't support everywhere and fully yet)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple cases that this does not handle correctly, I left those as TODOs for now.
02bb567 to
c789045
Compare
|
The ecosystem report looks a lot better after rebasing this on #21210. Most of the added diagnostics seem to be existing issues that were hidden by an inference of |
46b938b to
966b438
Compare
c789045 to
37ac0b5
Compare
37ac0b5 to
25ed082
Compare
* origin/main: (38 commits) [ty] Make implicit submodule imports only occur in global scope (#21370) [ty] introduce local variables for `from` imports of submodules in `__init__.py(i)` (#21173) [`ruff`] Ignore `str()` when not used for simple conversion (`RUF065`) (#21330) [ty] implement `typing.NewType` by adding `Type::NewTypeInstance` [ty] supress inlay hints for `+1` and `-1` (#21368) [ty] Use type context for inference of generic constructors (#20933) [ty] Improve generic call expression inference (#21210) [ty] supress some trivial expr inlay hints (#21367) [`configuration`] Fix unclear error messages for line-length values exceeding `u16::MAX` (#21329) [ty] Fix incorrect inference of `enum.auto()` for enums with non-`int` mixins, and imprecise inference of `enum.auto()` for single-member enums (#20541) [`refurb`] Detect empty f-strings (`FURB105`) (#21348) [ty] provide `import` completion when in `from <name> <name>` statement (#21291) [ty] elide redundant inlay hints for function args (#21365) Fix syntax error false positive on alternative `match` patterns (#21362) Add a new "Opening a PR" section to the contribution guide (#21298) [`flake8-simplify`] Fix SIM222 false positive for `tuple(generator) or None` (`SIM222`) (#21187) Rebuild ruff binary instead of sharing it across jobs (#21361) [ty] Fix `--exclude` and `src.exclude` merging (#21341) [ty] Add support for properties that return `Self` (#21335) Add upstream linter URL to `ruff linter --output-format=json` (#21316) ...
Summary
Resolves astral-sh/ty#1228.