-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Use type context for inference of generic function calls #20476
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-09-19 17:26:52.715647243 +0000
+++ new-output.txt 2025-09-19 17:26:55.828672752 +0000
@@ -240,8 +240,8 @@
dataclasses_transform_class.py:119:18: error[unknown-argument] Argument `id` does not match any known parameter of bound method `__init__`
dataclasses_transform_class.py:119:24: error[unknown-argument] Argument `name` does not match any known parameter of bound method `__init__`
dataclasses_transform_converter.py:25:6: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `T@model_field`
-dataclasses_transform_converter.py:48:31: error[invalid-argument-type] Argument to function `model_field` is incorrect: Expected `(Unknown, /) -> Unknown`, found `def bad_converter1() -> int`
-dataclasses_transform_converter.py:49:31: error[invalid-argument-type] Argument to function `model_field` is incorrect: Expected `(Unknown, /) -> Unknown`, found `def bad_converter2(*, x: int) -> int`
+dataclasses_transform_converter.py:48:31: error[invalid-argument-type] Argument to function `model_field` is incorrect: Expected `(Unknown, /) -> int`, found `def bad_converter1() -> int`
+dataclasses_transform_converter.py:49:31: error[invalid-argument-type] Argument to function `model_field` is incorrect: Expected `(Unknown, /) -> int`, found `def bad_converter2(*, x: int) -> int`
dataclasses_transform_converter.py:107:5: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 1, got 6
dataclasses_transform_converter.py:108:5: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 1, got 6
dataclasses_transform_converter.py:109:5: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 1, got 6
@@ -251,7 +251,7 @@
dataclasses_transform_converter.py:116:1: error[invalid-assignment] Object of type `Literal[b"f6"]` is not assignable to attribute `field3` of type `ConverterClass`
dataclasses_transform_converter.py:119:1: error[invalid-assignment] Object of type `Literal[1]` is not assignable to attribute `field3` of type `ConverterClass`
dataclasses_transform_converter.py:121:11: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 1, got 7
-dataclasses_transform_converter.py:130:31: error[invalid-argument-type] Argument to function `model_field` is incorrect: Expected `(Literal[1], /) -> Unknown`, found `def converter_simple(s: str) -> int`
+dataclasses_transform_converter.py:130:31: error[invalid-argument-type] Argument to function `model_field` is incorrect: Expected `(Literal[1], /) -> int`, found `def converter_simple(s: str) -> int`
dataclasses_transform_field.py:49:43: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `(...) -> @Todo(unsupported type[X] special form)`
dataclasses_transform_field.py:75:16: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 1
dataclasses_transform_func.py:53:8: error[missing-argument] No arguments provided for required parameters `id`, `name`
@@ -270,7 +270,7 @@
dataclasses_usage.py:51:28: error[invalid-argument-type] Argument is incorrect: Expected `int | float`, found `Literal["price"]`
dataclasses_usage.py:52:36: error[too-many-positional-arguments] Too many positional arguments: expected 3, got 4
dataclasses_usage.py:83:13: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
-dataclasses_usage.py:88:5: error[invalid-assignment] Object of type `dataclasses.Field[Literal[""]]` is not assignable to `int`
+dataclasses_usage.py:88:14: error[no-matching-overload] No overload of function `field` matches arguments
dataclasses_usage.py:127:8: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
dataclasses_usage.py:130:1: error[missing-argument] No argument provided for required parameter `y` of bound method `__init__`
dataclasses_usage.py:179:6: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 1, got 2
@@ -619,7 +619,7 @@
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:130:5: error[invalid-assignment] Object of type `Container[str]` is not assignable to `Container[LiteralString]`
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:138:1: error[invalid-assignment] Object of type `list[str]` is not assignable to `list[LiteralString]`
+literals_literalstring.py:138:1: error[invalid-assignment] Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`
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 |
d2e8a0a to
ca91334
Compare
|
ca91334 to
32edea8
Compare
| reveal_type(i) # revealed: int | ||
|
|
||
| j: int | str = f2(True) | ||
| reveal_type(j) # revealed: Literal[True] |
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.
This works because we ignore specialization errors on the return type. pyright also infers bool here, but mypy interestingly still infers int | str.
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.
Nice! Have you done any analysis of the ecosystem results?
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
no-matching-overload |
19 | 0 | 0 |
invalid-assignment |
0 | 4 | 13 |
invalid-argument-type |
4 | 4 | 0 |
index-out-of-bounds |
0 | 1 | 0 |
unresolved-attribute |
1 | 0 | 0 |
| Total | 24 | 9 | 13 |
0a9013d to
21db961
Compare
|
There seems to be a common ecosystem error where we fail to type-check calls to class X: ...
def field[T](default_factory: Callable[[], T]) -> T:
return default_factory()
# error[invalid-argument-type] Argument to function `field` is incorrect: Expected `() -> X`, found `<class 'X'>`
x: X = field(X)However, without the type annotation, we currently infer |
|
The fact that we can't infer this typevar is astral-sh/ty#500, which we closed as duplicate of the new constraint solver. It's a bit strange to me that we display the callable as |
21db961 to
1e50e89
Compare
| /// The type returned by this function may be different than the type of the expression | ||
| /// if it was inferred within its region, as it does not account for surrounding type context. | ||
| /// This can be useful to re-infer the type of an expression for diagnostics. | ||
| pub(crate) fn infer_isolated_expression<'db>( |
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.
Don't love the name here.
1e50e89 to
ace79d5
Compare
It looks like we correctly infer the type variable now due to the type annotation, but the issue remains without any generics, so I think this is unrelated: class X:
...
def f(callable: Callable[[], X]) -> X:
return callable()
# error[invalid-argument-type]: Argument to function `f` is incorrect
x = f(X)It seems to work if I add an explicit Edit: Opened astral-sh/ty#1210. |
ace79d5 to
22e0697
Compare
|
There's another issue here with: def f[T](callable: Callable[[], T]) -> T:
return callable()
# Expected `() -> Mapping[str, str]`, found `<class 'dict'>`
x: Mapping[str, str] = f(dict)After this PR, we infer Given that both of those issues are unrelated to the changes here, I think this PR should be good to merge. |
Summary
Part of astral-sh/ty#168. This PR allows widening the type of a generic function call based on a type annotation, e.g.,