-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Implicit type aliases: Support for PEP 604 unions #21195
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-03 19:01:57.863028190 +0000
+++ new-output.txt 2025-11-03 19:02:01.085061379 +0000
@@ -1,4 +1,4 @@
-fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/cdd0b85/src/function/execute.rs:419:17 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(182f5)): execute: too many cycle iterations`
+fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/cdd0b85/src/function/execute.rs:419:17 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(18af5)): execute: too many cycle iterations`
_directives_deprecated_library.py:15:31: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
_directives_deprecated_library.py:30:26: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
_directives_deprecated_library.py:36:41: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `Self@__add__`
@@ -7,8 +7,6 @@
aliases_explicit.py:41:24: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[str, str]`?
aliases_explicit.py:45:10: error[invalid-type-form] Variable of type `Literal["int | str"]` is not allowed in a type expression
aliases_explicit.py:49:5: error[type-assertion-failure] Argument does not have asserted type `int | str`
-aliases_explicit.py:50:5: error[type-assertion-failure] Argument does not have asserted type `int | None`
-aliases_explicit.py:51:5: error[type-assertion-failure] Argument does not have asserted type `list[int | None]`
aliases_explicit.py:52:5: error[type-assertion-failure] Argument does not have asserted type `list[int]`
aliases_explicit.py:53:5: error[type-assertion-failure] Argument does not have asserted type `tuple[str, ...] | list[str]`
aliases_explicit.py:54:5: error[type-assertion-failure] Argument does not have asserted type `tuple[int, int, int, str]`
@@ -23,8 +21,6 @@
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`
-aliases_implicit.py:61:5: error[type-assertion-failure] Argument does not have asserted type `int | None`
-aliases_implicit.py:62:5: error[type-assertion-failure] Argument does not have asserted type `list[int | None]`
aliases_implicit.py:63:5: error[type-assertion-failure] Argument does not have asserted type `list[int]`
aliases_implicit.py:64:5: error[type-assertion-failure] Argument does not have asserted type `tuple[str, ...] | list[str]`
aliases_implicit.py:65:5: error[type-assertion-failure] Argument does not have asserted type `tuple[int, int, int, str]`
@@ -47,6 +43,7 @@
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`
+aliases_newtype.py:23:16: error[invalid-argument-type] Argument to function `isinstance` is incorrect: Expected `type | UnionType | tuple[Unknown, ...]`, found `NewType`
aliases_newtype.py:26:21: error[invalid-base] Invalid class base with type `NewType`
aliases_newtype.py:63:43: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 3, got 4
aliases_type_statement.py:10:19: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 3
@@ -54,6 +51,7 @@
aliases_type_statement.py:19:1: error[call-non-callable] Object of type `TypeAliasType` is not callable
aliases_type_statement.py:23:7: error[unresolved-attribute] Object of type `typing.TypeAliasType` has no attribute `other_attrib`
aliases_type_statement.py:26:18: error[invalid-base] Invalid class base with type `typing.TypeAliasType`
+aliases_type_statement.py:31:22: error[invalid-argument-type] Argument to function `isinstance` is incorrect: Expected `type | UnionType | tuple[Unknown, ...]`, found `typing.TypeAliasType`
aliases_type_statement.py:37:22: error[invalid-type-form] Function calls are not allowed in type expressions
aliases_type_statement.py:38:22: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
aliases_type_statement.py:39:22: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression
@@ -912,11 +910,17 @@
tuples_type_compat.py:47:5: error[invalid-assignment] Object of type `tuple[Any, ...]` is not assignable to `tuple[int, *tuple[str, ...]]`
tuples_type_compat.py:62:5: error[invalid-assignment] Object of type `tuple[int, ...]` is not assignable to `tuple[int, int]`
tuples_type_compat.py:75:9: error[type-assertion-failure] Argument does not have asserted type `tuple[int]`
+tuples_type_compat.py:76:9: error[type-assertion-failure] Argument does not have asserted type `@Todo(Support for `typing.TypeAlias`)`
tuples_type_compat.py:80:9: error[type-assertion-failure] Argument does not have asserted type `tuple[str, str] | tuple[int, int]`
+tuples_type_compat.py:81:9: error[type-assertion-failure] Argument does not have asserted type `@Todo(Support for `typing.TypeAlias`)`
tuples_type_compat.py:85:9: error[type-assertion-failure] Argument does not have asserted type `tuple[int, str, int]`
+tuples_type_compat.py:86:9: error[type-assertion-failure] Argument does not have asserted type `@Todo(Support for `typing.TypeAlias`)`
tuples_type_compat.py:101:13: error[type-assertion-failure] Argument does not have asserted type `tuple[int]`
+tuples_type_compat.py:102:13: error[type-assertion-failure] Argument does not have asserted type `@Todo(Support for `typing.TypeAlias`)`
tuples_type_compat.py:106:13: error[type-assertion-failure] Argument does not have asserted type `tuple[str, str] | tuple[int, int]`
+tuples_type_compat.py:107:13: error[type-assertion-failure] Argument does not have asserted type `@Todo(Support for `typing.TypeAlias`)`
tuples_type_compat.py:111:13: error[type-assertion-failure] Argument does not have asserted type `tuple[int, str, int]`
+tuples_type_compat.py:112:13: error[type-assertion-failure] Argument does not have asserted type `@Todo(Support for `typing.TypeAlias`)`
tuples_type_compat.py:126:13: error[type-assertion-failure] Argument does not have asserted type `tuple[int | str, str]`
tuples_type_compat.py:127:13: error[type-assertion-failure] Argument does not have asserted type `@Todo(Support for `typing.TypeAlias`)`
tuples_type_compat.py:129:13: error[type-assertion-failure] Argument does not have asserted type `tuple[int | str, int]`
@@ -988,5 +992,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 990 diagnostics
+Found 994 diagnostics
WARN A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details. |
32e6b0c to
ed24374
Compare
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-argument-type |
493 | 8 | 13 |
invalid-key |
108 | 0 | 0 |
type-assertion-failure |
2 | 54 | 0 |
unused-ignore-comment |
1 | 34 | 0 |
invalid-type-form |
14 | 14 | 0 |
invalid-assignment |
14 | 0 | 2 |
possibly-missing-attribute |
7 | 4 | 4 |
unsupported-operator |
2 | 11 | 1 |
unresolved-attribute |
0 | 13 | 0 |
invalid-return-type |
2 | 5 | 0 |
non-subscriptable |
5 | 2 | 0 |
unknown-argument |
0 | 6 | 0 |
call-non-callable |
2 | 0 | 0 |
index-out-of-bounds |
1 | 1 | 0 |
no-matching-overload |
1 | 1 | 0 |
invalid-parameter-default |
1 | 0 | 0 |
redundant-cast |
1 | 0 | 0 |
| Total | 654 | 153 | 20 |
ed24374 to
d575584
Compare
0493014 to
b6e0bd9
Compare
1f380e3 to
52c00e9
Compare
52c00e9 to
c02b5ec
Compare
| python_version: PythonVersion::PY312, | ||
| }, | ||
| 400, | ||
| 500, |
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.
freqtrade makes use of a pattern where a union of typeddicts is narrowed by using a special tag (msg["type"] == RPCMessageType.ENTRY). We don't support this yet, leading to ~100 new diagnostics in a single file (but nowhere else in the ecosystem).
c02b5ec to
46bcc00
Compare
|
46bcc00 to
2d64871
Compare
2d64871 to
958c51d
Compare
| Some(KnownClass::UnionType) => Ok(todo_type!( | ||
| "Support for `types.UnionType` instances in type expressions" | ||
| )), |
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.
These are now hard errors. Opaque instances of UnionType can not be used in a type expression (see corresponding test)
| constraints.display(self.db) | ||
| ) | ||
| } | ||
| KnownInstanceType::UnionType(_) => f.write_str("UnionType"), |
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.
For consistency with the branches above, I would like to change this to types.UnionType, but I will do that in a separate step, because it causes many downstream changes.
AlexWaygood
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.
awesome!
| However, no other type checker seems to support stringified annotations in implicit type aliases. We | ||
| currently also do not support them: |
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.
correct -- this was one of the primary motivations for PEP 613
| /// Return `true` if `self` is a nominal instance of the given known class. | ||
| pub(crate) fn is_instance_of(self, db: &'db dyn Db, known_class: KnownClass) -> bool { | ||
| match self { | ||
| Type::NominalInstance(instance) => instance.class(db).is_known(db, known_class), | ||
| _ => false, | ||
| } | ||
| } |
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.
do we also need to add a branch for type aliases here?
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.
I was mainly trying to replace the previous ty.as_nominal_instance().is_some_and(|instance| instance.class(db).is_known(…)) pattern, so.. no?
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.
yes... I have been wondering if we need to update all of our as_* methods along these lines, though... e.g.
diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs
index e79282a35d..f094763fc5 100644
--- a/crates/ty_python_semantic/src/types.rs
+++ b/crates/ty_python_semantic/src/types.rs
@@ -1013,9 +1013,10 @@ impl<'db> Type<'db> {
any_over_type(db, self, &|ty| matches!(ty, Type::TypeVar(_)), false)
}
- pub(crate) const fn as_class_literal(self) -> Option<ClassLiteral<'db>> {
+ pub(crate) const fn as_class_literal(self, db: &'db dyn Db) -> Option<ClassLiteral<'db>> {
match self {
Type::ClassLiteral(class_type) => Some(class_type),
+ Type::TypeAlias(alias) => alias.value_type(db).as_class_literal(),
_ => None,
}
}Since in general, you should always treat a type alias to T the same as you treat T itself.
But yes, maybe we should just change all these together.
|
Quite a few new diagnostics on this branch have the same cause as those I analysed as happening on import typing
isinstance({}, typing.Dict)I think we should fix this, but that fixing it might need some deep special casing of >>> import typing
>>> typing.Dict()
Traceback (most recent call last):
File "<python-input-7>", line 1, in <module>
typing.Dict()
~~~~~~~~~~~^^
File "/Users/alexw/.pyenv/versions/3.13.1/lib/python3.13/typing.py", line 1315, in __call__
raise TypeError(f"Type {self._name} cannot be instantiated; "
f"use {self.__origin__.__name__}() instead")
TypeError: Type Dict cannot be instantiated; use dict() insteadThis doesn't need to be fixed in this PR, but we might want to prioritise fixing it as a followup. |
|
The new X = type[int] | type[str]
y: X |
Yes, I saw that. I will update the astral-sh/ty#221 ticket with a list of things that we need to support, and put that near the top of the list. |
Summary
Add support for implicit type aliases that use PEP 604 unions:
Typing conformance
The changes are either removed false positives, or new diagnostics due to known limitations unrelated to this PR.
Ecosystem impact
Spot checked, a mix of true positives and known limitations.
Test Plan
New Markdown tests.