Skip to content

False bad-specialization when a generic Protocol is used as a bound on a 2-param class inherited across modules #5449

Open

Description

@marciomazza

Describe the bug

A type parameter bounded by a generic Protocol (F: SupportsSave[Any]) rejects a valid argument
when the class has two type parameters and inherits from a generic base defined in another module.
The error message shows the bound as SupportsSave (no type argument), as if SupportsSave[Any]
lost its argument:

ERROR views.py:16:17-37: `EventForm` is not assignable to upper bound `SupportsSave` of type variable `F` [bad-specialization]

Repro

lib.py:

from typing import Any, Protocol


class Model: ...


class SupportsSave[T: Model](Protocol):
    def save(self) -> T: ...


class BaseView[M, F: SupportsSave[Any]]: ...

views.py:

from typing import Any

from lib import BaseView, Model, SupportsSave


class Event(Model): ...


class EventForm:
    def save(self) -> Event: ...


class View[M, F: SupportsSave[Any]](BaseView[M, F]): ...


class EventView(View[str, EventForm]): ...  # false positive here

pyproject.toml:

[tool.pyrefly]

(Empty section; without a config, the basic preset hides the error.)

Narrowing

The error goes away if any of these change:

  • everything is in a single module;
  • the classes have a single type parameter;
  • the bound is a non-generic Protocol.

Using old-style TypeVar(bound=...), a string bound, or SupportsSave[Model] instead of
SupportsSave[Any] does not change anything. pyright accepts the code, and so does ty.

Version

pyrefly 1.4.0 (latest release) and main 8003f9b3d (2026-10-29), Python 3.14 (same on 3.15).

Activity

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