[ty] Refactor handling of special forms#23513
Conversation
… use invalid PEP-604 unions for their second argument
…ialFormCategory` enum
… around type qualifiers
Merge the latest main branch into the alex/refactor-special-forms branch and resolve all resulting compile errors. Key changes: - Resolved merge conflicts across 8 files in ty_python_semantic - Updated MiscSpecialForm::in_type_expression to use Type::literal_string() instead of removed Type::LiteralString variant - Updated typing_self call to match new signature (ClassLiteral param, returns Option<BoundTypeVarInstance>) - Changed TypeAlias handling from DynamicType::TodoTypeAlias to proper InvalidTypeExpressionError - Re-added aliased_stdlib_class() method derived from SpecialFormCategory - Added add_qualifier() method and pub(crate) qualifiers field to TypeAndQualifiers for main's annotation_expression.rs changes - Integrated main's TypeGuard support, Callable refactoring, isinstance/issubclass improvements, and ClassVar/Final redundancy detection - Removed PartialSpecialization import (no longer exists) - Added Hash and GetSize derives to TypeQualifier enum https://claude.ai/code/session_01HXMzTUkfPVJCxdxTzZdePf
Refactor match arms on SpecialFormType variants to use exhaustive matches over SpecialFormCategory where possible: - In annotation_expression.rs, replace .as_type_qualifier().expect(...) with inner exhaustive matches on special_form.kind(), eliminating two .expect() calls and merging separate SpecialForm match arms. - Convert is_valid_in_type_expression and is_valid_isinstance_target from matches!/!matches! to exhaustive match expressions, so the compiler enforces updates when new SpecialFormCategory variants are added. - Remove the now-unused as_type_qualifier method. https://claude.ai/code/session_01HXMzTUkfPVJCxdxTzZdePf
Typing conformance resultsNo changes detected ✅ |
|
Memory usage reportMemory usage unchanged ✅ |
| reveal_type(invalid_dict1) # revealed: dict[Unknown, str] | ||
| reveal_type(invalid_dict2) # revealed: dict[str, Unknown] | ||
| reveal_type(dict_too_few_args) # revealed: dict[str, Unknown] | ||
| reveal_type(dict_too_few_args) # revealed: dict[Unknown, Unknown] |
There was a problem hiding this comment.
I think this is the only change in behaviour from this PR, which comes as a result of unifying the parsing of stdlib aliases between infer/builder.rs and infer/builder/type_expression.rs so that they now share a common implementation. Either the answer we give on main or the new answer seem fine to me, since this is an invalid type expression, but I can look into it if anybody strongly prefers the answer we have on main
|
I've been sitting on this branch for a while, uncertain if it was worth it... but seeing that we had to make this distinction yet again in #23444 inspired me to get Claude to rebase it |
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-argument-type |
0 | 1 | 0 |
| Total | 0 | 1 | 0 |
|
Haven't reviewed the code, but the idea seems very appealing to me!
Have you considered using a nested structure for the |
Hmm, yeah, that might be interesting... I'll ask Claude to try that out and see how it looks by comparison! |
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
Instead of having a flat SpecialFormType enum with a separate SpecialFormCategory enum that duplicates part of the structure via a kind() method, embed the subcategories directly into SpecialFormType itself as nested enum variants: - SpecialFormType::LegacyStdlibAlias(LegacyStdlibAlias) - SpecialFormType::TypeQualifier(TypeQualifier) This eliminates SpecialFormCategory, MiscSpecialForm, and the kind() mapping method, along with several From impls that existed only to convert between the parallel structures. Callers now match directly on SpecialFormType variants instead of calling .kind() first. This is an alternative to the SpecialFormCategory approach, as suggested in #23513 (comment) https://claude.ai/code/session_01AgLYCKoBuLEDsmkn1cnmvZ
|
Closing in favour of #23517 |
Summary
Our
SpecialFormTypeenum is an enumeration of symbols (mostly in thetypingmodule) that are special enough that we consider each symbol to inhabit its own singleton type. That makes them "similar enough" that they all deserve to inhabit the sameType::SpecialFormtop-level variant, but it elides the fact that there are several distinct subcategories within this enumeration that need to be recognised at multiple distinct points throughout the codebase. The flat nature of theSpecialFormTypeenum often makes it hard to distinguish between these subcategories in a type-safe way; we often have to resort to.expect()orunreachable!()calls, e.g.ruff/crates/ty_python_semantic/src/types/infer/builder.rs
Lines 16143 to 16145 in fc1081a
ruff/crates/ty_python_semantic/src/types/infer/builder.rs
Lines 16200 to 16202 in 66defe9
ruff/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs
Line 334 in fc1081a
It also leads to pretty repetitive code: we have to differentiate stdlib aliases from other kinds of special forms in
narrow.rs,infer/builder.rsandinfer/builder/type_expression.rs, in very similar ways.This PR adds a
SpecialFormType::kind()method, which returns a more fine-grainedSpecialFormCategoryenumeration of subcategories of special forms. This results in a net increase of total lines of code, but it's more type-safe code (all three.expect()/unreachable!()calls highlighted above are gone), and much less repetitive code.Test Plan
Existing tests