[ty] Implicit type aliases: Support for PEP 604 unions#21195
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.
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.
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.
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.
| 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.
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.
do we also need to add a branch for type aliases here?
There was a problem hiding this comment.
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.
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.