Skip to content

Metaclass attribute becomes unknown after narrowing type[Protocol] with isinstance #5282

Open

Description

@adamtheturtle

Describe the bug

After isinstance(cls, Metadata), the metaclass declares a known int | None attribute. Pyrefly loses that type when the class also came from type(shape) for a protocol with a property of the same name. The same attribute accessed through a parameter annotated directly as Metadata is known. The metaclass attribute has a default here to avoid the separate initialization issue in #5278. This is related to, but narrower than, the previously closed #4168.

Reproduced with Pyrefly 1.4.0 and CPython 3.14.8 on macOS. Pyrefly 1.3.2 accepts this example with the same configuration. Mypy 2.4.0 (strict, without project plugins) and Pyright 1.1.414 also accept it.

Reproducer

from typing import Protocol

class Metadata(type):
    limit: int | None = 1

class Shape(Protocol):
    @property
    def limit(self) -> int | None: ...

def check_limit(shape: Shape) -> None:
    cls = type(shape)
    if not isinstance(cls, Metadata):
        raise TypeError
    if cls.limit is not None:
        print("bounded")

Save this as pyrefly.toml:

preset = "all"
python-version = "3.14"

Compare:

uvx --from pyrefly==1.3.2 pyrefly check --config pyrefly.toml metaclass_narrowing.py
uvx --from pyrefly==1.4.0 pyrefly check --config pyrefly.toml metaclass_narrowing.py

Pyrefly 1.4.0 output

The type of attribute `limit` is unknown; it is inferred as an implicit `Any` [unknown-attribute-access]

Activity

  1. added
    narrowingIssues with narrowing - root cause is usually narrowing, flow handling, or both
    on Oct 19, 2026
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

    narrowingIssues with narrowing - root cause is usually narrowing, flow handling, or bothtypechecking

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions