Skip to content

Conversation

@sharkdp
Copy link
Contributor

@sharkdp sharkdp commented Nov 19, 2025

Summary

Eagerly evaluate the elements of a PEP 604 union in value position (e.g. IntOrStr = int | str) as type expressions and store the result (the corresponding Type::Union if all elements are valid type expressions, or the first encountered InvalidTypeExpressionError) on the UnionTypeInstance, such that the Type::Union(…) does not need to be recomputed every time the implicit type alias is used in a type annotation.

This might lead to performance improvements for large unions, but is also necessary for correctness, because the elements of the union might refer to type variables that need to be looked up in the scope of the type alias, not at the usage site.

Test Plan

New Markdown tests

@sharkdp sharkdp added the ty Multi-file analysis & type inference label Nov 19, 2025
builder = builder.add(if inferred_as.type_expression() {
*element
} else {
element.in_type_expression(db, scope_id, typevar_binding_context)?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The previous version was also only able to surface the first error. I'm not sure if this is a problem, or something that we can live with.

UnionTypeInstance::PEP604(instance) => {
// Cloning here is cheap if the result is a `Type` (which is `Copy`). It's more
// expensive if there are errors.
instance.union_type(db).clone()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@carljm In the end, this didn't require a new salsa query. Instead, we're now eagerly building that Type::Union in case there were no errors, and interning the result. And then we only need to copy the result here when we see a use of this implicit type alias in a type expression.

This means that we do some extra work if someone defines IntOrStr = int | str and then never uses it as a type. But that feels okay to me?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, that seems reasonable.

Copy link
Contributor Author

@sharkdp sharkdp Nov 20, 2025

Choose a reason for hiding this comment

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

What's potentially more problematic is the fact that when we have a LargeUnion = C1 | C2 | … | Cn, we would build $n - 1$ union types eagerly, instead of just one. But if that turns out to be a problem, we can turn the union_type field into a salsa query.

@sharkdp sharkdp force-pushed the david/eager-in-type-expression branch from 41258a6 to d058e94 Compare November 19, 2025 21:06
@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 19, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@sharkdp sharkdp force-pushed the david/eager-in-type-expression branch from d058e94 to 41ecb33 Compare November 20, 2025 12:50
@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 20, 2025

mypy_primer results

Changes were detected when running on open source projects
scikit-build-core (https://github.com/scikit-build/scikit-build-core)
+ src/scikit_build_core/build/wheel.py:98:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 43 diagnostics
+ Found 44 diagnostics

No memory usage changes detected ✅

@sharkdp sharkdp force-pushed the david/eager-in-type-expression branch from 41ecb33 to 3dc7c48 Compare November 20, 2025 14:14
@sharkdp sharkdp force-pushed the david/eager-in-type-expression branch from 3dc7c48 to 08a1f4e Compare November 20, 2025 14:19
@sharkdp sharkdp marked this pull request as ready for review November 20, 2025 14:20
@sharkdp sharkdp requested a review from dcreager as a code owner November 20, 2025 14:20
reveal_type(callable_or_int) # revealed: ((str, /) -> bytes) | int
# TODO should be Unknown | int
reveal_type(type_var_or_int) # revealed: T@_ | int
reveal_type(type_var_or_int) # revealed: typing.TypeVar | int
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We still don't support generic implicit type aliases, but this is a step in the right direction. T@_ is just wrong.

Comment on lines -877 to -878
// TODO: add support for PEP 604 union types on the right hand side of `isinstance`
// and `issubclass`, for example `isinstance(x, str | (int | float))`.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was recently added

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.

Nice!

@sharkdp
Copy link
Contributor Author

sharkdp commented Nov 20, 2025

Looks like there are minor performance improvements

image

@sharkdp sharkdp merged commit 0761ea4 into main Nov 20, 2025
41 checks passed
@sharkdp sharkdp deleted the david/eager-in-type-expression branch November 20, 2025 16:28
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