From a04375173cf5e6378aeeeb69e3032b05cefb7a5c Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Mon, 18 Aug 2025 17:54:05 -0700 Subject: [PATCH] [ty] fix unpacking a type alias with detailed tuple spec (#19981) ## Summary Fixes https://github.com/astral-sh/ty/issues/1046 We special-case iteration of certain types because they may have a more detailed tuple-spec. Now that type aliases are a distinct type variant, we need to handle them as well. I don't love that `Type::TypeAlias` means we have to remember to add a case for it basically anywhere we are special-casing a certain kind of type, but at the moment I don't have a better plan. It's another argument for avoiding fallback cases in `Type` matches, which we usually prefer; I've updated this match statement to be comprehensive. ## Test Plan Added mdtest. --- .../resources/mdtest/pep695_type_aliases.md | 11 ++++++ crates/ty_python_semantic/src/types.rs | 34 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md index ee5bffcb19..904f08099a 100644 --- a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md @@ -64,6 +64,17 @@ x: MyIntOrStr = 1 y: MyIntOrStr = None ``` +## Unpacking from a type alias + +```py +type T = tuple[int, str] + +def f(x: T): + a, b = x + reveal_type(a) # revealed: int + reveal_type(b) # revealed: str +``` + ## Generic type aliases ```py diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ac5124a4bc..30e01fdc97 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -4866,7 +4866,39 @@ impl<'db> Type<'db> { // diagnostic in unreachable code. return Ok(Cow::Owned(TupleSpec::homogeneous(Type::unknown()))); } - _ => {} + Type::TypeAlias(alias) => { + return alias.value_type(db).try_iterate_with_mode(db, mode); + } + Type::Dynamic(_) + | Type::FunctionLiteral(_) + | Type::GenericAlias(_) + | Type::BoundMethod(_) + | Type::MethodWrapper(_) + | Type::WrapperDescriptor(_) + | Type::DataclassDecorator(_) + | Type::DataclassTransformer(_) + | Type::Callable(_) + | Type::ModuleLiteral(_) + | Type::ClassLiteral(_) + | Type::SubclassOf(_) + | Type::ProtocolInstance(_) + | Type::SpecialForm(_) + | Type::KnownInstance(_) + | Type::PropertyInstance(_) + | Type::Union(_) + | Type::Intersection(_) + | Type::AlwaysTruthy + | Type::AlwaysFalsy + | Type::IntLiteral(_) + | Type::BooleanLiteral(_) + | Type::EnumLiteral(_) + | Type::LiteralString + | Type::BytesLiteral(_) + | Type::TypeVar(_) + | Type::NonInferableTypeVar(_) + | Type::BoundSuper(_) + | Type::TypeIs(_) + | Type::TypedDict(_) => {} } let try_call_dunder_getitem = || {