Skip to content

structurally equivalent namedtuples can confuse mypy in unions in some cases #18520

Closed
@asottile-sentry

Description

@asottile-sentry

Bug Report

some narrowing goes wrong and mypy ends up getting confused with a bunch of self-union types (A | A) and spurious errors

I'm not entirely sure the cause however

To Reproduce

from typing import NamedTuple


class AKey(NamedTuple):
    k: str

class A(NamedTuple):
    key: AKey
    s: str


class BKey(NamedTuple):
    k: str

class B(NamedTuple):
    key: BKey
    s: str


def f(x: A | B | str) -> A | B | str:
    if isinstance(x, str):
        return x
    else:
        return x._replace(s='wat')

Expected Behavior

no errors from the given snippet

Actual Behavior

$ mypy t3.py
t3.py:24: error: Invalid self argument "A | A" to attribute function "_replace" with type "Callable[[_NT, DefaultNamedArg(AKey, 'key'), DefaultNamedArg(str, 's')], _NT]"  [misc]
t3.py:24: error: Invalid self argument "B | B" to attribute function "_replace" with type "Callable[[_NT, DefaultNamedArg(BKey, 'key'), DefaultNamedArg(str, 's')], _NT]"  [misc]
t3.py:24: error: Incompatible return value type (got "A | B | A | B", expected "A | B | str")  [return-value]
Found 3 errors in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.14.0
  • Mypy command-line flags: n/a
  • Mypy configuration options from mypy.ini (and other config files): n/a
  • Python version used: 3.13.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions