Skip to content

Regression: specialised self overloads erase container types and suppress attribute errors #11731

Open

Description

@cmp0xff

Describe the bug

Starting with Pyright 1.1.412, Container[Any].copy() becomes Any instead of Container[Any], losing the outer container type and suppressing the nonexistent-member error. Expected: preserve Container[Any] and report the invalid attribute, since every overload returns a container.

Code or Screenshots

from typing import Any, Generic, Never, TypeVar, overload, reveal_type

T = TypeVar("T")

class Container(Generic[T]):
    @overload
    def copy(self: "Container[Never]") -> "Container[Any]": ...
    @overload
    def copy(self: "Container[int]") -> "Container[int]": ...
    @overload
    def copy(self: "Container[float]") -> "Container[float]": ...
    def copy(self) -> "Container[Any]":
        return self

def example(value: Container[Any], concrete: Container[int]) -> None:
    reveal_type(value.copy())
    reveal_type(concrete.copy())
    value.copy().nonexistent_member()

VS Code extension or command-line

Version 1.1.411 exits with status 1; the later versions exit with status 0.

Pyright Generic result Missing-member error
1.1.411 Container[Any] Reported
1.1.412–1.1.414 Any Missed

All four versions infer Container[int] for the concrete control.

#11601 intentionally handles ambiguity from nested Any/Unknown in invariant nominal type arguments, with specialized-self and pandas-like tests covering precision loss. This report asks whether the shared outer container can be preserved to retain attribute checking; it does not claim a typing-specification violation.

On unchanged pandas-stubs main at pandas-dev/pandas-stubs@507243e, Pyright 1.1.414 reports 240 reportAssertTypeFailure errors versus zero in 1.1.411; related work: pandas-dev/pandas-stubs#1941 and temporary pin pandas-dev/pandas-stubs#1942.

Activity

  1. cmp0xff commented on Sep 10, 2026

    @cmp0xff
    Author

    Additional context: python/typing#2350 explicitly permits more precise gradual results for ambiguous overloads, while keeping Any compliant. Carl Meyer’s discussion specifically identifies pandas-stubs’ leading Never overloads and describes preserving Series[Any] as a way forward.

    The practical concern here is lost error detection: the nonexistent-member access in the reproducer now passes. Could Pyright preserve Container[Any] in this case, and shared generic container structure more generally where the specification’s assignability and supported-operation requirements are satisfied?

    The copy() method is a minimal illustration. The affected pandas arithmetic overloads include type promotion and different result families, so replacing them all with a single type-preserving generic signature would not preserve their behaviour.

  2. cmp0xff commented on Oct 12, 2026

    @cmp0xff
    Author

    Could this be prioritised now that mypy nightly (python/mypy#22011) aligns with ty's existing gradual receiver ambiguity handling (astral-sh/ty#4055)? This blocks a consistent approach in pandas-stubs without losing useful container checking.

    This reduced case still returns Any and misses the invalid attribute in Pyright 1.1.414. Nightly mypy preserves Container[Any] and reports the error.

    from typing import Any, Generic, TypeVar, overload, reveal_type
    
    T = TypeVar("T")
    
    
    class Container(Generic[T]):
        @overload
        def copy(self: "Container[int]") -> "Container[int]": ...
        @overload
        def copy(self: "Container[str]") -> "Container[str]": ...
        def copy(self) -> "Container[Any]":
            raise NotImplementedError
    
    
    def probe(gradual: Container[Any], concrete: Container[int]) -> None:
        reveal_type(gradual.copy())
        reveal_type(concrete.copy())
        gradual.copy().nonexistent_member()
  3. rchiodo commented on Oct 12, 2026

    @rchiodo
    Collaborator

    The container erasure and missing attribute error are gone in the main build I checked, but the broader specialized-self ambiguity isn't fully handled yet.

    The latest public release is still 1.1.414, published September 9, before the changes below. So today's result with released 1.1.414 is expected. The source build also displays 1.1.414, which makes the version string a bit misleading here. The checks below used a fresh build of main at 543798c, not the released package, with a Python 3.12 target and basic mode.

    On that build, the original example's value.copy() reveals Container[Any], and .nonexistent_member() correctly reports an unknown attribute on Container[Any].

    Today's reduced example behaves differently: gradual.copy() reveals Container[int], and the nonexistent-member call also reports an error. That's first-match behavior, though. It drops the str alternative rather than preserving the requested gradual container result. The concrete receiver returns Container[int] in both examples.

    The history explains why this isn't simply "the container fix landed":

    #11732 initially added shared-container preservation. #11739 then rolled back the nested materialization work and the checker portions of that fix, restoring the earlier first-match behavior. That's what accounts for the current results, rather than the original container-preservation implementation remaining intact.

    #11780 later enabled bounded overload alternatives for supported direct function calls, but it intentionally doesn't cover bound-method producers like value.copy().

    So the original erasure/missed-error symptom is resolved in that build of main, but hasn't shipped yet. Correctly retaining the gradual specialized-self alternatives remains open. These checks cover the two examples here, not the broader pandas-stubs cases or an independent mypy comparison.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions