Skip to content

[conformance] Handle type[T] calls where T is bounded by a class#2381

Closed
fangyi-zhou wants to merge 4 commits intofacebook:mainfrom
fangyi-zhou:type_t_constructor_call
Closed

[conformance] Handle type[T] calls where T is bounded by a class#2381
fangyi-zhou wants to merge 4 commits intofacebook:mainfrom
fangyi-zhou:type_t_constructor_call

Conversation

@fangyi-zhou
Copy link
Contributor

@fangyi-zhou fangyi-zhou commented Feb 10, 2026

Summary

When types of type[T] are called, we currently infer a callable that takes any arguments and returns the type variable T. This is a conformance gap, where we should treat such calls as on T. However, the spec made clear that this is not free of problems:

From https://typing.python.org/en/latest/spec/constructors.html#constructor-calls-for-type-t

It should be noted that such code could be unsafe because the type type[T] may represent subclasses of T, and those subclasses could redefine the __new__ and __init__ methods in a way that is incompatible with the base class. Likewise, the metaclass of T could redefine the __call__ method in a way that is incompatible with the base metaclass.

If T is unrestricted, we assume an implicit bound of object, and use the callable type () -> T.
If T is bounded, we use the bound class type to infer the callable signature, but use the type T as the return type.

We cannot handle constrained type variables, since we don't have any intersection type support at the call target level, so I'm leaving the current behaviour as is.

Let's assess the impact on open source codebases, since I expect this change will bring some false positives.

#2369

Test Plan

Ran conformance test suite and fixed 4 failing tests.

When `T` is an unbounded type var, using `type[T]` as a callable should
resolve to `object.__init__` (i.e. doesn't accept any parameters).
For those cases we use the class type as a call target, but we need to
override the return type as the type variable.
To do so, we add a new optional field on CallTarget::Class for the type
variable override. If one is present, the inferred type should be the
type variable, instead of the concrete class.
@meta-cla meta-cla bot added the cla signed label Feb 10, 2026
@github-actions

This comment has been minimized.

@fangyi-zhou fangyi-zhou marked this pull request as ready for review February 10, 2026 23:53
@fangyi-zhou
Copy link
Contributor Author

fangyi-zhou commented Feb 10, 2026

Checking the mypy_primer results, I think we're getting some false positives due spec warning and dynamic behaviours.

The error in static_frame is a bit strange, not sure where we infer any callable in the constructor

@fangyi-zhou fangyi-zhou requested a review from migeed-z February 10, 2026 23:55
@github-actions
Copy link

Diff from mypy_primer, showing the effect of this PR on open source code:

meson (https://github.com/mesonbuild/meson)
+ ERROR mesonbuild/interpreter/interpreter.py:2304:22-25: Argument `CustomTarget | CustomTargetIndex | Executable | ExternalProgram | Jar | Program` is not assignable to parameter `exe` with type `CustomTarget | CustomTargetIndex | Executable | ExternalProgram` in function `mesonbuild.interpreter.interpreterobjects.Test.__init__` [bad-argument-type]
+ ERROR mesonbuild/interpreter/interpreter.py:2306:22-54: Argument `bool | object` is not assignable to parameter `is_parallel` with type `bool` in function `mesonbuild.interpreter.interpreterobjects.Test.__init__` [bad-argument-type]
+ ::error file=mesonbuild/interpreter/interpreter.py,line=2304,col=22,endLine=2304,endColumn=25,title=Pyrefly bad-argument-type::Argument `CustomTarget | CustomTargetIndex | Executable | ExternalProgram | Jar | Program` is not assignable to parameter `exe` with type `CustomTarget | CustomTargetIndex | Executable | ExternalProgram` in function `mesonbuild.interpreter.interpreterobjects.Test.__init__`
+ ::error file=mesonbuild/interpreter/interpreter.py,line=2306,col=22,endLine=2306,endColumn=54,title=Pyrefly bad-argument-type::Argument `bool | object` is not assignable to parameter `is_parallel` with type `bool` in function `mesonbuild.interpreter.interpreterobjects.Test.__init__`

psycopg (https://github.com/psycopg/psycopg)
+ ERROR psycopg/psycopg/rows.py:162:38-53: No matching overload found for function `zip.__new__` called with arguments: (type[zip[_T_co]], list[str] | None, Sequence[Any]) [no-matching-overload]
+ ::error file=psycopg/psycopg/rows.py,line=162,col=38,endLine=162,endColumn=53,title=Pyrefly no-matching-overload::No matching overload found for function `zip.__new__` called with arguments: (type[zip[_T_co]], list[str] | None, Sequence[Any])%0A  Possible overloads:%0A  (cls: type[zip[_T_co]], *, strict: bool = False) -> zip[Any]%0A  (cls: type[zip[_T_co]], iter1: Iterable[_T1], /, *, strict: bool = False) -> zip[tuple[_T1]]%0A  (cls: type[zip[_T_co]], iter1: Iterable[_T1], iter2: Iterable[_T2], /, *, strict: bool = False) -> zip[tuple[_T1, _T2]] [closest match]%0A  (cls: type[zip[_T_co]], iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /, *, strict: bool = False) -> zip[tuple[_T1, _T2, _T3]]%0A  (cls: type[zip[_T_co]], iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], /, *, strict: bool = False) -> zip[tuple[_T1, _T2, _T3, _T4]]%0A  (cls: type[zip[_T_co]], iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], iter5: Iterable[_T5], /, *, strict: bool = False) -> zip[tuple[_T1, _T2, _T3, _T4, _T5]]%0A  (cls: type[zip[_T_co]], iter1: Iterable[Any], iter2: Iterable[Any], iter3: Iterable[Any], iter4: Iterable[Any], iter5: Iterable[Any], iter6: Iterable[Any], /, *iterables: Iterable[Any], *, strict: bool = False) -> zip[tuple[Any, ...]]

artigraph (https://github.com/artigraph/artigraph)
+ ERROR src/arti/storage/__init__.py:221:13-27: Unpacked keyword argument `str | Any` is not assignable to parameter `_arti_fingerprint_fields_` with type `tuple[str, ...]` in function `StoragePartition.__init__` [bad-argument-type]
+ ::error file=src/arti/storage/__init__.py,line=221,col=13,endLine=221,endColumn=27,title=Pyrefly bad-argument-type::Unpacked keyword argument `str | Any` is not assignable to parameter `_arti_fingerprint_fields_` with type `tuple[str, ...]` in function `StoragePartition.__init__`

pydantic (https://github.com/pydantic/pydantic)
+ ERROR pydantic/v1/validators.py:615:30-55: No matching overload found for function `typing.NamedTuple.__init__` called with arguments: (**dict[str, Any]) [no-matching-overload]
+ ::error file=pydantic/v1/validators.py,line=615,col=30,endLine=615,endColumn=55,title=Pyrefly no-matching-overload::No matching overload found for function `typing.NamedTuple.__init__` called with arguments: (**dict[str, Any])%0A  Possible overloads:%0A  (typename: str, fields: Iterable[tuple[str, Any]], /) -> None%0A  (typename: str, fields: None = None, /, **kwargs: Any) -> None [closest match]

static-frame (https://github.com/static-frame/static-frame)
+ ERROR static_frame/core/index_datetime.py:284:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:310:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:334:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:418:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:444:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:469:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:518:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:543:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ERROR static_frame/core/index_datetime.py:568:25-51: Expected a callable, got `Literal[SortStatus.from_range_step]` [not-callable]
+ ::error file=static_frame/core/index_datetime.py,line=284,col=25,endLine=284,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=310,col=25,endLine=310,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=334,col=25,endLine=334,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=418,col=25,endLine=418,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=444,col=25,endLine=444,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=469,col=25,endLine=469,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=518,col=25,endLine=518,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=543,col=25,endLine=543,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`
+ ::error file=static_frame/core/index_datetime.py,line=568,col=25,endLine=568,endColumn=51,title=Pyrefly not-callable::Expected a callable, got `Literal[SortStatus.from_range_step]`

@meta-codesync
Copy link

meta-codesync bot commented Feb 11, 2026

@migeed-z has imported this pull request. If you are a Meta employee, you can view this in D92900019.

Copy link
Contributor

@stroxler stroxler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

@meta-codesync
Copy link

meta-codesync bot commented Feb 11, 2026

@migeed-z merged this pull request in 0aebd10.

@migeed-z migeed-z self-assigned this Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants