[conformance] Model Annotated with a distinct Type case#2572
[conformance] Model Annotated with a distinct Type case#2572fangyi-zhou wants to merge 4 commits intofacebook:mainfrom
Annotated with a distinct Type case#2572Conversation
…8-88) Introduce `Type::Annotated(Box<Type>)` — a thin wrapper produced when `Annotated[T, meta]` is used as a value expression (not in an annotation). This makes `Annotated[int, ""]` and TypeAliases of it non-callable and not assignable to `type[T]`, matching the typing spec. Key changes: - `types.rs`: Add `Type::Annotated` variant with Visit/VisitMut impls - `display.rs`: Display as `Annotated[T]` - `specials.rs`: Produce `Type::Annotated(inner)` instead of `Type::Type(inner)` - `solve.rs`: `untype_opt` unwraps Annotated in annotation context; `check_type_form` recognizes Annotated as a valid type form; `as_type_alias` preserves the Annotated wrapper for TypeAliases; `tvars_to_tparams_for_type_alias` recurses into Annotated - `subset.rs`: Reject `Annotated(_) <: Type(_)` (not assignable to type[T]) - `attr.rs`: Add Annotated to the no-attribute catch-all arm - conformance: Update expected results (6 → 0 failures in qualifiers_annotated.py) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
`Type::Annotated` was not handled in `as_attribute_base1`, causing it to fall into the no-op catch-all and produce an internal error whenever an attribute was accessed on an `Annotated[...]` type form (e.g. `Annotated[int, "meta"].__doc__`). Fix by mapping `Type::Annotated` to `ClassInstance(generic_alias)`, which matches the runtime representation: `Annotated[T, ...]` is an instance of `typing._AnnotatedAlias`, a subclass of `typing._GenericAlias`. This was caught by the mypy_primer test in the PR CI run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Annotated with a new Type caseAnnotated with a distinct Type case
|
Diff from mypy_primer, showing the effect of this PR on open source code: Tanjun (https://github.com/FasterSpeeding/Tanjun)
+ ERROR tanjun/annotations.py:1601:12-43: Returned type `Annotated[_T]` is not assignable to declared return type `type[_T]` [bad-return]
+ ERROR tanjun/annotations.py:1855:16-42: Returned type `Annotated[str]` is not assignable to declared return type `type[str]` [bad-return]
+ ERROR tanjun/annotations.py:2268:16-64: Returned type `Annotated[PartialChannel]` is not assignable to declared return type `type[PartialChannel]` [bad-return]
pydantic (https://github.com/pydantic/pydantic)
+ ERROR pydantic/types.py:230:12-235:6: Returned type `Annotated[int]` is not assignable to declared return type `type[int]` [bad-return]
+ ERROR pydantic/types.py:491:12-497:6: Returned type `Annotated[float]` is not assignable to declared return type `type[float]` [bad-return]
+ ERROR pydantic/types.py:679:12-683:6: Returned type `Annotated[bytes]` is not assignable to declared return type `type[bytes]` [bad-return]
+ ERROR pydantic/types.py:817:12-828:6: Returned type `Annotated[str]` is not assignable to declared return type `type[str]` [bad-return]
+ ERROR pydantic/types.py:852:12-87: Returned type `Annotated[set[HashableItemType]]` is not assignable to declared return type `type[set[HashableItemType]]` [bad-return]
+ ERROR pydantic/types.py:868:12-93: Returned type `Annotated[frozenset[HashableItemType]]` is not assignable to declared return type `type[frozenset[HashableItemType]]` [bad-return]
+ ERROR pydantic/types.py:903:12-88: Returned type `Annotated[list[AnyItemType]]` is not assignable to declared return type `type[list[AnyItemType]]` [bad-return]
+ ERROR pydantic/types.py:1124:12-1131:6: Returned type `Annotated[Decimal]` is not assignable to declared return type `type[Decimal]` [bad-return]
+ ERROR pydantic/types.py:2257:12-2261:6: Returned type `Annotated[date]` is not assignable to declared return type `type[date]` [bad-return]
+ ERROR pydantic/v1/fields.py:586:45-49: Argument `Annotated[T]` is not assignable to parameter `class_or_tuple` with type `tuple[type[Any], ...] | type[Any] | None` in function `pydantic.v1.utils.lenient_issubclass` [bad-argument-type]
+ ERROR pydantic/v1/types.py:832:20-24: Returned type `Annotated[T]` is not assignable to declared return type `type[JsonWrapper]` [bad-return]
prefect (https://github.com/PrefectHQ/prefect)
- ERROR src/prefect/blocks/core.py:1046:16-51: Returned type `tuple[BlockDocument, str | None]` is not assignable to declared return type `tuple[BlockDocument, str]` [bad-return]
+ ERROR src/prefect/blocks/core.py:1046:16-51: Returned type `tuple[BlockDocument, Name | None]` is not assignable to declared return type `tuple[BlockDocument, str]` [bad-return]
|
Annotated with a distinct Type caseAnnotated with a distinct Type case
Primer Diff Classification❌ 2 regression(s) | ➖ 1 neutral | 3 project(s) total 2 regression(s) across Tanjun, pydantic. error kinds:
Detailed analysis❌ Regression (2)Tanjun (+3)
pydantic (+11)
➖ Neutral (1)prefect (+1, -1)
Suggested FixSummary: The PR introduced a new Type::Annotated variant that prevents Annotated[T, ...] from being assignable to type[T], causing regressions in Tanjun and pydantic where this pattern is commonly used. 1. In is_subtype_of() in solver/subset.rs, add a special case for Type::Annotated: when checking if Annotated[T, ...] is assignable to type[U], unwrap the Annotated and check if T is assignable to type[U] instead. This restores the previous behavior where Annotated types were treated as equivalent to their inner type for assignment purposes.
2. In check_callable() in solver/subset.rs or the relevant callable checking function, add a guard: when checking if Type::Annotated(inner) is callable, delegate to checking if the inner type is callable. This prevents not-callable errors when Annotated types wrap callable types.
Classification by primer-classifier (1 heuristic, 2 LLM) · Was this helpful? React with 👍 or 👎 |
|
I'm working on a tool to classify those errors easily. Those look like expected errors to me. I think the LLM classified them as regressions, because it doesn't have the PR title and explanation. It thinks the regressions were not intentional and its feedback is essentially undoing the PR. I will fix that. |
|
Hmm.. so the diff ended up introducing 100+ errors on IG. cc @rchen152 for opinions on this. |
I only looked at a few errors, but the ones I dug into were caused by pyrefly no longer treating things like |
|
The errors are true positives given a strict reading of the spec, in practice people have been using that in the wild quite often. See discussion here https://discuss.python.org/t/is-annotated-compatible-with-type-t/43898 |
grievejia
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
#2634) Summary: Fixes #2633, a regression introduced by #2572, where we model `Annotated` in a separate constructor. There are two issues here: 1. Type aliases for `Annotated` types are not resolved (thus being an `UntypedAlias`): this is because `Annotated` is now its own type, which is not always a `Type` for triggering the type alias resolution. The fix is to handle `Annotated` type in the resolution processing, and removing the `Annotated` wrapper. 2. `Annotated` types are not handled in `Literal` check: this is a straightforward fix to check the inner type when encountering an `Annotated` type. Pull Request resolved: #2634 Test Plan: Added a regression test. Reviewed By: stroxler, yangdanny97 Differential Revision: D95167871 Pulled By: rchen152 fbshipit-source-id: a61e259498d616dfb2ed4bb1ca4176eff8884e25
Summary
Before this change, we are treating an annotated type
Annotated[T, ...]as the base typeT. This is fine in the majority of the places, but does not capture some subtleties according to the spec.We have failing conformance test cases due to an
Annotatedtype being accepted as atypeorTypeAlias, as well as being callable, whereas the spec doesn't allow so.https://typing.python.org/en/latest/spec/qualifiers.html#annotated
This diff adds a new Type case for
Annotated, which contains the base type. The type behaves like the base type, except it cannot be passed totypeor called.Test Plan
Ran conformance testsuite, and observed that
qualifiers_annotated.pyis passing.