Skip to content

Filter overloaded __new__ by cls when converting a class to a callable - #5195

Open
i098 wants to merge 2 commits into
facebook:mainfrom
i098:fix/pyrefly-5053
Open

i098 wants to merge 2 commits into
facebook:mainfrom
i098:fix/pyrefly-5053

Conversation

@i098

@i098 i098 commented Oct 11, 2026

Copy link
Copy Markdown

Summary

Converting a class with an overloaded __new__ to a callable kept every overload, even when the cls annotation of an overload cannot accept the class being converted. For the class in the issue, C[str] was treated as the constructor of C[int] as well.

bind_dunder_new (used by constructor_to_callable) now drops the overloads whose first parameter does not accept the class object. Type parameters are treated as Any on both sides, so these cases keep all their overloads: no cls annotation, cls: type[S] with the overload's own type variable, and a bare generic class such as C. If no overload would remain, the original overload is kept.

Repro, with preset = "default" in pyproject.toml:

from collections.abc import Callable
from functools import partial
from typing import Any, overload

class C[T]:
    @overload
    def __new__(cls: type[C[int]], x: object) -> C[int]: ...
    @overload
    def __new__(cls: type[C[str]], x: str) -> C[str]: ...
    def __new__(cls: type[Any], x: object) -> C[int] | C[str]:
        raise NotImplementedError

wrong: Callable[[str], C[int]] = C[str]
reveal_type(partial(C[str], "x")())

Before: no error on wrong, and revealed type: C[int].

After:

ERROR `type[C[str]]` is not assignable to `(str) -> C[int]` [bad-assignment]
  --> c.py:15:34
 INFO revealed type: C[str] [reveal-type]
  --> c.py:16:12

Fixes #5053

  • I am a human, and I fully understand the code I am submitting.

Test Plan

  • The first commit adds test_overloaded_new_cls_annotation_as_callable, which fails without the fix (the wrong assignment is accepted and partial(...)() is C[int]). The second commit fixes it.
  • test_overloaded_new_cls_annotation_as_callable_variants covers the cases that must keep working: no cls annotation, a bare generic class, type[C[Any]], a type-variable cls, an __init__-only class, and a subclass that inherits the overloaded __new__.
  • cargo test -p pyrefly --lib: 9069 passed, 0 failed.
  • python3 test.py: passes in full (exit 0), including scrut (204 of 204), tensor shapes, conformance with no generated-file changes, jsonschema and the extension helper tests.

I am new to open source, and this pull request was made with AI assistance.

@meta-codesync

meta-codesync Bot commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D124639375. (Because this pull request was imported automatically, there will not be any future comments.)

i098 added 2 commits October 20, 2026 06:03
When a class with overloaded `__new__` is converted to a callable, as in
`Callable[[str], C[int]] = C[str]` or `partial(C[str], "x")`, the overloads
whose `cls` annotation does not match the specialized class are still kept.
This test captures the expected behavior from issue 5053 and currently fails.
…lable

Converting a class with an overloaded `__new__` to a callable kept every
overload, even those whose `cls` annotation cannot accept the class being
converted. For `class C[T]` with `__new__` overloads on `type[C[int]]` and
`type[C[str]]`, `C[str]` was accepted as `Callable[[str], C[int]]`, and
`partial(C[str], "x")()` was inferred as `C[int]`.

`bind_dunder_new` now drops the overloads whose first parameter does not
accept the class object. Type parameters on either side are treated as `Any`,
so overloads without a `cls` annotation, overloads that use their own type
variables, and bare generic classes keep their overloads. If no overload
would remain, the original overload is kept.

Fixes facebook#5053
@i098
i098 force-pushed the fix/pyrefly-5053 branch from ef60120 to 18e5dff Compare October 20, 2026 06:36
@github-actions github-actions Bot added size/m and removed size/m labels Oct 20, 2026

This branch has not been deployed

No deployments
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.

Overloaded constructor is not converted to a callable correctly

2 participants