Skip to content

Conversation

@ibraheemdev
Copy link
Member

@ibraheemdev ibraheemdev commented Oct 10, 2025

Summary

Use the declared type of variables as type context for the RHS of assignment expressions, e.g.,

x: list[int | str]
x = [1]
reveal_type(x)  # revealed: list[int | str]

@ibraheemdev ibraheemdev requested a review from carljm as a code owner October 10, 2025 04:29
@ibraheemdev ibraheemdev added the ty Multi-file analysis & type inference label Oct 10, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 10, 2025

Diagnostic diff on typing conformance tests

Changes were detected when running ty on typing conformance tests
--- old-output.txt	2025-10-16 19:20:45.139893750 +0000
+++ new-output.txt	2025-10-16 19:20:48.626930815 +0000
@@ -883,6 +883,13 @@
 tuples_type_form.py:15:1: error[invalid-assignment] Object of type `tuple[Literal[1], Literal[""]]` is not assignable to `tuple[int, int]`
 tuples_type_form.py:25:1: error[invalid-assignment] Object of type `tuple[Literal[1]]` is not assignable to `tuple[()]`
 tuples_type_form.py:36:1: error[invalid-assignment] Object of type `tuple[Literal[1], Literal[2], Literal[3], Literal[""]]` is not assignable to `tuple[int, ...]`
+typeddicts_operations.py:22:17: error[invalid-assignment] Invalid assignment to key "name" with declared type `str` on TypedDict `Movie`: value of type `Literal[1982]`
+typeddicts_operations.py:23:17: error[invalid-assignment] Invalid assignment to key "year" with declared type `int` on TypedDict `Movie`: value of type `Literal[""]`
+typeddicts_operations.py:24:7: error[invalid-key] Invalid key access on TypedDict `Movie`: Unknown key "other"
+typeddicts_operations.py:26:13: error[invalid-key] Invalid key access on TypedDict `Movie`: Unknown key "other"
+typeddicts_operations.py:28:9: error[missing-typed-dict-key] Missing required key 'year' in TypedDict `Movie` constructor
+typeddicts_operations.py:29:42: error[invalid-argument-type] Invalid argument to key "year" with declared type `int` on TypedDict `Movie`: value of type `float`
+typeddicts_operations.py:32:36: error[invalid-key] Invalid key access on TypedDict `Movie`: Unknown key "other"
 typeddicts_operations.py:37:20: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
 typeddicts_operations.py:62:1: error[unresolved-attribute] Type `MovieOptional` has no attribute `clear`
 typeddicts_readonly.py:24:4: error[invalid-assignment] Cannot assign to key "members" on TypedDict `Band`: key is marked read-only
@@ -892,6 +899,9 @@
 typeddicts_readonly.py:61:4: error[invalid-assignment] Cannot assign to key "year" on TypedDict `Movie2`: key is marked read-only
 typeddicts_readonly_inheritance.py:36:4: error[invalid-assignment] Cannot assign to key "name" on TypedDict `Album2`: key is marked read-only
 typeddicts_readonly_inheritance.py:65:19: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `RequiredName` constructor
+typeddicts_readonly_inheritance.py:82:14: error[invalid-assignment] Invalid assignment to key "ident" with declared type `str` on TypedDict `User`: value of type `Literal[3]`
+typeddicts_readonly_inheritance.py:83:15: error[invalid-argument-type] Invalid argument to key "ident" with declared type `str` on TypedDict `User`: value of type `Literal[3]`
+typeddicts_readonly_inheritance.py:84:5: error[missing-typed-dict-key] Missing required key 'ident' in TypedDict `User` constructor
 typeddicts_type_consistency.py:69:21: error[invalid-key] Invalid key access on TypedDict `A3`: Unknown key "y"
 typeddicts_type_consistency.py:101:1: error[invalid-assignment] Object of type `Unknown | None` is not assignable to `str`
 typeddicts_type_consistency.py:126:56: error[invalid-argument-type] Invalid argument to key "inner_key" with declared type `str` on TypedDict `Inner1`: value of type `Literal[1]`
@@ -900,5 +910,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 access on 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 902 diagnostics
+Found 912 diagnostics
 WARN A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 10, 2025

mypy_primer results

Changes were detected when running on open source projects
mypy_primer (https://github.com/hauntsaninja/mypy_primer)
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `str | None`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `str | None`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `int | None`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `int | None`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `bool`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `bool`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `bool`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_mypy` is incorrect: Expected `bool`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyright` is incorrect: Expected `str | None`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyright` is incorrect: Expected `str | None`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_ty` is incorrect: Expected `str`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_ty` is incorrect: Expected `str`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_ty` is incorrect: Expected `str | None`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_ty` is incorrect: Expected `str | None`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyrefly` is incorrect: Expected `str`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyrefly` is incorrect: Expected `str`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyrefly` is incorrect: Expected `str | None`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyrefly` is incorrect: Expected `str | None`, found `Any | str | None | int | Path`
- mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyrefly` is incorrect: Expected `Path | None`, found `Unknown | str | None | int | Path`
+ mypy_primer/main.py:70:87: error[invalid-argument-type] Argument to function `setup_pyrefly` is incorrect: Expected `Path | None`, found `Any | str | None | int | Path`

aioredis (https://github.com/aio-libs/aioredis)
- aioredis/client.py:916:46: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Connection]`, found `Unknown | str | int | None | float`
+ aioredis/client.py:916:46: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Connection]`, found `Any | str | int | None | float`
- aioredis/client.py:916:46: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | None`, found `Unknown | str | int | None | float`
+ aioredis/client.py:916:46: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | None`, found `Any | str | int | None | float`

pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/python.py:1442:13: error[invalid-assignment] Object of type `dict[str, Literal["direct"]]` is not assignable to `dict[str, Literal["indirect", "direct"]]`
- Found 488 diagnostics
+ Found 487 diagnostics

pyjwt (https://github.com/jpadilla/pyjwt)
+ jwt/api_jws.py:215:30: error[missing-typed-dict-key] Missing required key 'verify_signature' in TypedDict `SigOptions` constructor
- Found 33 diagnostics
+ Found 34 diagnostics

starlette (https://github.com/encode/starlette)
- starlette/testclient.py:365:49: warning[possibly-missing-attribute] Attribute `read` on type `Unknown | int | list[Unknown] | BytesIO` may be missing
+ starlette/testclient.py:365:49: warning[possibly-missing-attribute] Attribute `read` on type `Any | int | list[Unknown] | BytesIO` may be missing

poetry (https://github.com/python-poetry/poetry)
- tests/puzzle/test_solver.py:614:9: error[invalid-assignment] Object of type `list[Unknown | str]` is not assignable to `list[Literal["a", "b", "download-package", "install-package"]]`
- Found 991 diagnostics
+ Found 990 diagnostics

freqtrade (https://github.com/freqtrade/freqtrade)
- freqtrade/freqai/data_kitchen.py:827:17: error[invalid-assignment] Method `__setitem__` of type `(bound method dict[str, dict[str, DataFrame]].__setitem__(key: str, value: dict[str, DataFrame], /) -> None) | (bound method dict[Unknown, Unknown].__setitem__(key: Unknown, value: Unknown, /) -> None)` cannot be called with a key of type `Unknown` and a value of type `DataFrame` on object of type `(dict[str, dict[str, DataFrame]] & ~AlwaysFalsy) | dict[Unknown, Unknown]`
+ freqtrade/freqai/data_kitchen.py:827:17: error[invalid-assignment] Method `__setitem__` of type `bound method dict[str, dict[str, DataFrame]].__setitem__(key: str, value: dict[str, DataFrame], /) -> None` cannot be called with a key of type `Unknown` and a value of type `DataFrame` on object of type `dict[str, dict[str, DataFrame]]`
- freqtrade/freqai/data_kitchen.py:830:21: error[invalid-assignment] Method `__setitem__` of type `(bound method dict[str, DataFrame].__setitem__(key: str, value: DataFrame, /) -> None) | (bound method dict[Unknown, Unknown].__setitem__(key: Unknown, value: Unknown, /) -> None)` cannot be called with a key of type `Unknown` and a value of type `dict[Unknown, Unknown]` on object of type `(dict[str, DataFrame] & ~AlwaysFalsy) | dict[Unknown, Unknown]`
+ freqtrade/freqai/data_kitchen.py:830:21: error[invalid-assignment] Method `__setitem__` of type `bound method dict[str, DataFrame].__setitem__(key: str, value: DataFrame, /) -> None` cannot be called with a key of type `Unknown` and a value of type `dict[Unknown, Unknown]` on object of type `dict[str, DataFrame]`
- freqtrade/freqai/data_kitchen.py:836:13: error[invalid-assignment] Method `__setitem__` of type `(bound method dict[str, dict[str, DataFrame]].__setitem__(key: str, value: dict[str, DataFrame], /) -> None) | (bound method dict[Unknown, Unknown].__setitem__(key: Unknown, value: Unknown, /) -> None)` cannot be called with a key of type `Unknown` and a value of type `DataFrame` on object of type `(dict[str, dict[str, DataFrame]] & ~AlwaysFalsy) | dict[Unknown, Unknown]`
+ freqtrade/freqai/data_kitchen.py:836:13: error[invalid-assignment] Method `__setitem__` of type `bound method dict[str, dict[str, DataFrame]].__setitem__(key: str, value: dict[str, DataFrame], /) -> None` cannot be called with a key of type `Unknown` and a value of type `DataFrame` on object of type `dict[str, dict[str, DataFrame]]`
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'id' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'pair' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'lock_time' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'lock_timestamp' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'lock_end_time' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'lock_end_timestamp' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'reason' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'side' in TypedDict `RPCProtectionMsg` constructor
+ freqtrade/freqtradebot.py:2417:19: error[missing-typed-dict-key] Missing required key 'active' in TypedDict `RPCProtectionMsg` constructor
- Found 401 diagnostics
+ Found 410 diagnostics

vision (https://github.com/pytorch/vision)
- references/classification/utils.py:420:5: error[invalid-assignment] Object of type `tuple[type, ...] | tuple[Unknown, ...]` is not assignable to `list[type] | None`
+ references/classification/utils.py:420:5: error[invalid-assignment] Object of type `tuple[type | Unknown, ...]` is not assignable to `list[type] | None`
- torchvision/prototype/transforms/_misc.py:56:13: error[invalid-assignment] Object of type `dict[Any, tuple[int, int]]` is not assignable to `tuple[int, int] | dict[type, tuple[int, int] | None]`
- torchvision/transforms/functional.py:1205:9: error[invalid-assignment] Object of type `list[Unknown | (list[int | float] & Number) | float]` is not assignable to `list[int | float]`
+ torchvision/transforms/functional.py:1205:9: error[invalid-assignment] Object of type `list[int | float | (list[int | float] & Number)]` is not assignable to `list[int | float]`
- torchvision/transforms/functional.py:1225:13: error[invalid-assignment] Object of type `list[Unknown | int | float]` is not assignable to `list[int] | None`
+ torchvision/transforms/functional.py:1225:13: error[invalid-assignment] Object of type `list[int | float]` is not assignable to `list[int] | None`
- torchvision/transforms/v2/functional/_geometry.py:649:9: error[invalid-assignment] Object of type `list[Unknown | (list[int | float] & Number) | float]` is not assignable to `list[int | float]`
+ torchvision/transforms/v2/functional/_geometry.py:649:9: error[invalid-assignment] Object of type `list[int | float | (list[int | float] & Number)]` is not assignable to `list[int | float]`
- Found 1481 diagnostics
+ Found 1480 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
- src/integrations/prefect-docker/prefect_docker/images.py:64:5: error[invalid-assignment] Object of type `dict[Unknown | str, Unknown | str | None | bool | dict[str, Any]]` is not assignable to `dict[str, dict[str, Any]]`
+ src/integrations/prefect-docker/prefect_docker/images.py:64:5: error[invalid-assignment] Object of type `dict[str, dict[str, Any] | str | None | bool]` is not assignable to `dict[str, dict[str, Any]]`
+ src/prefect/flows.py:3093:28: warning[redundant-cast] Value is already of type `str`
+ src/prefect/flows.py:3106:28: warning[redundant-cast] Value is already of type `str`
- Found 3147 diagnostics
+ Found 3149 diagnostics

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- ddtrace/llmobs/_integrations/bedrock.py:304:21: warning[possibly-missing-attribute] Attribute `append` on type `Unknown | list[Unknown] | str` may be missing
+ ddtrace/llmobs/_integrations/bedrock.py:304:21: warning[possibly-missing-attribute] Attribute `append` on type `Any | list[Unknown] | str` may be missing
- ddtrace/llmobs/_integrations/bedrock.py:314:25: warning[possibly-missing-attribute] Attribute `append` on type `Unknown | list[Unknown] | str` may be missing
+ ddtrace/llmobs/_integrations/bedrock.py:314:25: warning[possibly-missing-attribute] Attribute `append` on type `Any | list[Unknown] | str` may be missing

zulip (https://github.com/zulip/zulip)
- zerver/openapi/python_examples.py:1338:12: error[invalid-return-type] Return type does not match returned value: expected `tuple[int, str]`, found `tuple[Unknown, Unknown | str | list[Unknown | int]]`
+ zerver/openapi/python_examples.py:1338:12: error[invalid-return-type] Return type does not match returned value: expected `tuple[int, str]`, found `tuple[Unknown, Any | str | list[Unknown | int]]`

sympy (https://github.com/sympy/sympy)
- sympy/core/evalf.py:1410:5: error[invalid-assignment] Object of type `dict[Unknown | <class 'Symbol'> | <class 'Dummy'> | ... omitted 31 union elements, Unknown | ((x: Expr, prec: int, options: dict[str, Any]) -> Any) | ((expr: Float, prec: int, options: dict[str, Any]) -> Any) | ... omitted 17 union elements]` is not assignable to `dict[type[Expr], (Expr, int, dict[str, Any], /) -> Any]`
+ sympy/core/evalf.py:1410:5: error[invalid-assignment] Object of type `dict[type[Expr], ((Expr, int, dict[str, Any], /) -> Any) | (def evalf_float(expr: Float, prec: int, options: dict[str, Any]) -> Any) | (def evalf_rational(expr: Rational, prec: int, options: dict[str, Any]) -> Any) | ... omitted 17 union elements]` is not assignable to `dict[type[Expr], (Expr, int, dict[str, Any], /) -> Any]`

rotki (https://github.com/rotki/rotki)
- rotkehlchen/globaldb/handler.py:587:82: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
- Found 1713 diagnostics
+ Found 1712 diagnostics

core (https://github.com/home-assistant/core)
- homeassistant/components/recorder/statistics.py:1706:9: error[invalid-assignment] Object of type `set[Unknown | str]` is not assignable to `set[Literal["max", "mean", "min", "change"]] | None`
- homeassistant/components/recorder/statistics.py:1790:16: warning[possibly-missing-attribute] Attribute `isdisjoint` on type `set[Literal["max", "mean", "min", "change"]] | None` may be missing
- homeassistant/components/recorder/statistics.py:1801:17: error[invalid-argument-type] Argument to function `_get_max_mean_min_statistic` is incorrect: Expected `set[Literal["max", "mean", "min", "change"]]`, found `set[Literal["max", "mean", "min", "change"]] | None`
- homeassistant/components/recorder/statistics.py:1804:12: error[unsupported-operator] Operator `in` is not supported for types `str` and `None`, in comparing `Literal["change"]` with `set[Literal["max", "mean", "min", "change"]] | None`
- Found 13760 diagnostics
+ Found 13756 diagnostics
No memory usage changes detected ✅

@ibraheemdev ibraheemdev force-pushed the ibraheem/declared-type-context branch from 7beb24b to 0fbaae1 Compare October 10, 2025 04:34
@github-actions
Copy link
Contributor

github-actions bot commented Oct 10, 2025

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 9 1 12
invalid-assignment 0 4 9
missing-typed-dict-key 10 0 0
possibly-missing-attribute 0 1 3
redundant-cast 2 0 0
invalid-return-type 0 0 1
unsupported-operator 0 1 0
unused-ignore-comment 0 1 0
Total 21 8 25

Full report with detailed diff (timing results)

@ibraheemdev ibraheemdev force-pushed the ibraheem/declared-type-context branch from 0fbaae1 to 9b81c82 Compare October 10, 2025 21:33
@ibraheemdev
Copy link
Member Author

ibraheemdev commented Oct 10, 2025

This is more complicated for class attributes as may need to infer the RHS multiple times during attribute resolution. This is similar to what we do for function overloads, but the difference is that the RHS of an assignment statement is a standalone expression, so I'm not exactly sure how to approach this. We may be able to collect the possible type contexts beforehand and infer the intersection as the single type for the expression, but this needs more work.

I'm going to cut down this PR to just handle simple assignment statements, and implement class attributes in a followup PR.

@carljm
Copy link
Contributor

carljm commented Oct 10, 2025

Took a look at some of the ecosystem hits here, just documenting what I found:

IIRC our approach to type context is generally just to union it in in the specialization builder? It seems like this approach may need some refinement in cases where the type context is a union, so we avoid unioning in any elements of the context union that are definitely not part of the type of the expression we are inferring?

@carljm
Copy link
Contributor

carljm commented Oct 10, 2025

I guess the issue isn't really new to this PR, it already exists: https://play.ty.dev/6d7b5e71-848a-4091-90ea-b56f548a5eff

I think in that case it's going to be important that we can at least narrow away the None.

@ibraheemdev
Copy link
Member Author

#20528 adds a filter_disjoint_elements operation that we might be able to use here (though I'm not sure if will work as is for T | None).

@dcreager
Copy link
Member

I think in that case it's going to be important that we can at least narrow away the None.

Discussed this a bit with @ibraheemdev just now, and I think a rewording of this that I strongly agree with is that we should reveal the same types for x and y in: [playground]

def id[T](x: T) -> T:
    return x

def _(i: int):
    x: int | None = id(i)
    y: int | None = i
    reveal_type(x)
    reveal_type(y)  # revealed: int

@ibraheemdev ibraheemdev changed the base branch from main to ibraheem/generic-call-inference October 15, 2025 01:03
@ibraheemdev ibraheemdev force-pushed the ibraheem/generic-call-inference branch 4 times, most recently from af6dad0 to 5c817d8 Compare October 15, 2025 23:37
@ibraheemdev ibraheemdev force-pushed the ibraheem/declared-type-context branch from 29da7aa to 3bf25d1 Compare October 15, 2025 23:38
Copy link
Member

@dcreager dcreager left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good. Have you looked through the remaining ecosystem report results? (Also, does the ecosystem report show the diff between this PR and #20875, or main? If the latter we should want until that lands and get a fresh report to look at.)

Comment on lines -1335 to +1357
fn add_binding(&mut self, node: AnyNodeRef, binding: Definition<'db>, ty: Type<'db>) {
/// Add a binding for the given definition.
///
/// Returns the result of the `infer_value_ty` closure, which is called with the declared type
/// as type context.
fn add_binding(
&mut self,
node: AnyNodeRef,
binding: Definition<'db>,
infer_value_ty: impl FnOnce(&mut Self, TypeContext<'db>) -> Type<'db>,
) -> Type<'db> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Most of the callers below don't look to be using the type context, and provide a closure that just immediately returns the previous ty parameter. You can reduce the churn on the diff by keeping add_binding with its existing signature, and calling this new variant e.g. add_binding_with_type_context.

fn add_binding(&mut self, node: AnyNodeRef, binding: Definition<'db>, ty: Type<'db>) {
    self.add_binding_with_context(node, binding, |_, _| ty);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has come up a few times, and I think I would prefer to keep the signature as is for now, because I think it's useful to know when there is a potential type context available that is being ignored, and in fact there are a couple of cases where we probably could be using the type context, but I've held off for now.

@ibraheemdev
Copy link
Member Author

ibraheemdev commented Oct 16, 2025

The diff is between this PR and #20875. The ecosystem report looks good, mostly positive changes and a couple regressions due to other known limitations (i.e. astral-sh/ty#1248).

@AlexWaygood AlexWaygood removed their request for review October 16, 2025 18:58
@ibraheemdev ibraheemdev force-pushed the ibraheem/generic-call-inference branch from e9e278f to d806594 Compare October 16, 2025 19:05
ibraheemdev added a commit that referenced this pull request Oct 16, 2025
## Summary

Ignore the type context when specializing a generic call if it leads to
an unnecessarily wide return type. For example, [the example mentioned
here](#20796 (comment))
works as expected after this change:
```py
def id[T](x: T) -> T:
    return x

def _(i: int):
    x: int | None = id(i)
    y: int | None = i
    reveal_type(x)  # revealed: int
    reveal_type(y)  # revealed: int
```

I also added extended our usage of `filter_disjoint_elements` to tuple
and typed-dict inference, which resolves
astral-sh/ty#1266.
Base automatically changed from ibraheem/generic-call-inference to main October 16, 2025 19:17
@ibraheemdev ibraheemdev force-pushed the ibraheem/declared-type-context branch from 3bf25d1 to 9f3fa9f Compare October 16, 2025 19:19
@ibraheemdev ibraheemdev merged commit 25023cc into main Oct 16, 2025
41 checks passed
@ibraheemdev ibraheemdev deleted the ibraheem/declared-type-context branch October 16, 2025 19:40
dcreager added a commit that referenced this pull request Oct 17, 2025
* main:
  [ty] Prefer declared type for invariant collection literals (#20927)
  [ty] Don't track inferability via different `Type` variants (#20677)
  [ty] Use declared variable types as bidirectional type context (#20796)
  [ty] Avoid unnecessarily widening generic specializations (#20875)
  [ty] Support dataclass-transform `field_specifiers` (#20888)
  Bump 0.14.1 (#20925)
  Standardize syntax error construction (#20903)
  [`pydoclint`] Implement `docstring-extraneous-parameter` (`DOC102`) (#20376)
  [ty] Fix panic 'missing root' when handling completion request (#20917)
  [ty] Run file watching tests serial when using nextest (#20918)
  [ty] Add version hint for failed stdlib attribute accesses (#20909)
  More CI improvements (#20920)
  [ty] Check typeshed VERSIONS for parent modules when reporting failed stdlib imports (#20908)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants