Skip to content

[ty] Fix narrowing PEP 695 type aliases#22894

Merged
carljm merged 5 commits intomainfrom
cjm/pep695-narrow
Jan 27, 2026
Merged

[ty] Fix narrowing PEP 695 type aliases#22894
carljm merged 5 commits intomainfrom
cjm/pep695-narrow

Conversation

@carljm
Copy link
Contributor

@carljm carljm commented Jan 27, 2026

A number of our narrowing cases didn't account for PEP 695 type aliases. The narrowing logic now correctly resolves the type alias and narrows accordingly.

Fixes astral-sh/ty#2645

When a union of TypedDicts is defined via a PEP 695 type alias the
narrowing logic now correctly resolves the type alias and narrows the
union based on literal field comparisons.

Previously, the `is_typeddict_or_union_with_typeddicts` and
`all_matching_typeddict_fields_have_literal_types` functions
did not handle `Type::TypeAlias`, causing narrowing to fail for
PEP 695 type aliases while working correctly for direct unions
and legacy type aliases.

Fixes astral-sh/ty#2645
@carljm carljm added the ty Multi-file analysis & type inference label Jan 27, 2026
@carljm carljm requested a review from dcreager as a code owner January 27, 2026 17:49
@carljm carljm requested a review from oconnor663 January 27, 2026 17:49
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 27, 2026

Typing conformance results

No changes detected ✅

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

you could consider making these match statements exhaustive so it's impossible for us to forget to update them if/when we add another variant like Type::TypeAlias that just has to delegate

@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 27, 2026

mypy_primer results

Changes were detected when running on open source projects
prefect (https://github.com/PrefectHQ/prefect)
- src/integrations/prefect-dbt/prefect_dbt/core/settings.py:94:28: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/integrations/prefect-dbt/prefect_dbt/core/settings.py:94:28: error[invalid-assignment] Object of type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
- src/prefect/cli/deploy/_core.py:86:21: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/cli/deploy/_core.py:86:21: error[invalid-assignment] Object of type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
- src/prefect/deployments/steps/core.py:137:38: error[invalid-argument-type] Argument is incorrect: Argument type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/deployments/steps/core.py:137:38: error[invalid-argument-type] Argument is incorrect: Argument type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown | int | dict[str, Any] | ... omitted 4 union elements` on object of type `dict[str, Any]`
+ src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `dict[str, Any] | int | Unknown | ... omitted 4 union elements` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | int | dict[str, Any] | ... omitted 4 union elements]`
+ src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | dict[str, Any] | int | ... omitted 4 union elements]`
- src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `str | int | dict[str, Any] | ... omitted 3 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `dict[str, Any] | int | str | ... omitted 3 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
- src/scikit_build_core/build/wheel.py:99:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 47 diagnostics
+ Found 46 diagnostics

No memory usage changes detected ✅

@carljm carljm requested a review from AlexWaygood January 27, 2026 20:44
@carljm
Copy link
Contributor Author

carljm commented Jan 27, 2026

Ok, this PR kind of metastasized a bit; I could try to split it up but since the changes are all in one file, that would be slightly painful -- I don't think it's too big to be reasonably reviewable. All changes are tested and I verified every code change here is necessary to fix some test that would otherwise fail.

@carljm carljm marked this pull request as draft January 27, 2026 20:49
Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

LGTM if the primer report is clean!

Comment on lines +1213 to +1221
/// If this type is a `Type::TypeAlias`, recursively resolves it to its
/// underlying value type. Otherwise, returns `self` unchanged.
pub(crate) fn resolve_type_alias(self, db: &'db dyn Db) -> Type<'db> {
let mut ty = self;
while let Type::TypeAlias(alias) = ty {
ty = alias.value_type(db);
}
ty
}
Copy link
Member

Choose a reason for hiding this comment

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

this relates to @mtshiba's comment in #22786 (comment) -- ideally we'd have all of our Type::as_* methods account for type aliases and use this method under the hood; they're too much of a bug magnet otherwise.

But even that doesn't fully solve the problem, because there are other Type variants for which delegation is also often or always important: NewType, TypeVar, Union, Intersection, etc.

Anyway, what you have seems fine for now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think doing this for type aliases would make sense for sure. The other cases are a bit more situational. I'll do that as a follow-up.

@carljm carljm changed the title [ty] Fix TypedDict tagged-union narrowing from PEP 695 type aliases [ty] Fix narrowing PEP 695 type aliases Jan 27, 2026
@carljm carljm marked this pull request as ready for review January 27, 2026 21:03
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 27, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
type-assertion-failure 0 2 0
invalid-return-type 0 0 1
Total 0 2 1

Full report with detailed diff (timing results)

@carljm carljm merged commit a8d1d86 into main Jan 27, 2026
49 checks passed
@carljm carljm deleted the cjm/pep695-narrow branch January 27, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix TypedDict tagged-union narrowing from a PEP 695 type alias

2 participants