Skip to content

bad-return after type(x) is C / isinstance(x, C) narrowing on a TypeVar #3550

@jurasic-pf

Description

@jurasic-pf

Describe the Bug

Pyrefly rejects a dispatch-on-concrete-subclass pattern that pyright (1.1.408) accepts. When a function is parameterised over a TypeVar bound to a union, narrowing the value with type(x) is C or isinstance(x, C) and returning the result of a function that takes the concrete class is reported as bad-return.

I tried findinding pre-existing issues on this, but couldn't. This is technically different from issue #2530 (which was about narrowing of plain unions, now fixed) — here the value is bound by a TypeVar.

from typing import TypeVar

class A:
    pass

class B:
    pass

T = TypeVar("T", bound=A | B)

def func_a(a: A) -> A:
    return a

def func_b(b: B) -> B:
    return b

def dispatch_with_type_is(x: T) -> T:
    if type(x) is A:
        return func_a(x)  # pyrefly: bad-return (`A` is not assignable to `T`)
    elif type(x) is B:
        return func_b(x)  # pyrefly: bad-return (`B` is not assignable to `T`)
    raise NotImplementedError


def dispatch_with_isinstance(x: T) -> T:
    if isinstance(x, A):
        return func_a(x)  # pyrefly: bad-return
    elif isinstance(x, B):
        return func_b(x)  # pyrefly: bad-return
    raise NotImplementedError

Pyrefly output (1.0.0)

ERROR Returned type `A` is not assignable to declared return type `T` [bad-return]
ERROR Returned type `B` is not assignable to declared return type `T` [bad-return]
ERROR Returned type `A` is not assignable to declared return type `T` [bad-return]
ERROR Returned type `B` is not assignable to declared return type `T` [bad-return]

Pyright output (1.1.408)

0 errors, 0 warnings, 0 informations

Environment

  • pyrefly 1.0.0
  • Python 3.12

Sandbox Link

https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIAOumAE64C2ABAC6nEToDmTEDxuWiyYAVdjABqqWtWoBjKKjhwmAQUTUmWpsSVxZ6BXqYAhDem069BkUwC8o8VNoAKSiBHuANEzwBXLDtVJgAfUwBKA0wYMCYwALkAfVQXVEQ1cKYAWgA%2BNXNLWhgWP1oLVCiYuITE7BdsdJNM3NMC7SKSst9K2MwIOF0WOQALRIB3CBZRtmIYRP6XfHSRZryRNq0IWJmYRcz%2B-M1LduLSi3jDZL2tAGIdUiKwKFJ07FRMLI6zphcAA1VfrwVOhcMI9BAuOhUNhYKxcExfiJfpELNoYFAtqxxNcDmYjscmF8uhcknV8JkmHcyI9nq93p9Tl0-iZAQcQWDlBCoTCYHCEUiUYVUP1eQA5UEASX4sAYMHQLBgmAAorR6DJ0D0mH0BqghqMJlN5nBOHAWBg5LslqJVqINrxYv0TWbDJafKpwnbCozzjUUuTbvcaS9fPSiRrUVp0ZjHehTebXRFPSdOj7LmSKVSHjFaSGPmH8YThXAxZLpTBZfLFSq1SAvCBqdnSIQWIwoBQ7gAFLNPUhMNBYPD4JhyXA0CGlXUQUeEah3ADKMF5wxYLGIcEQAHoNw2e4RBFwN3KN5hcHI4BuR2OuBOWFP0Bu4oImKgAG7CxQ84ejyDX2iT0dMLgxC3qOcAzugZBTKOWQvjAtDGgBDjuAAzIQACMABM7jUAA2nBapwAAutQAR8AIQiKlkfRFHIt6wfYTAAORcoIMCMdQ7IMgAjn4EBFB8ADWMCkFkqByBaygMYxYzSOg7HoCAAC%2BdZiXRMAAGLQDAFD9jgBAkOQilAA

(Only applicable for extension issues) IDE Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions