GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Sentinel and enum type merge issues with generic · Issue #22334 · python/mypy

Sentinel and enum type merge issues with generic #22334

New issue
Copy link
New issue
Copy link
Open
#22351
Open
Sentinel and enum type merge issues with generic#22334
#22351
Copy link
Labels
bugmypy got something wrongmypy got something wrong

Description

@cdce8p
cdce8p
opened on Oct 30, 2026
Issue body actions
from typing_extensions import sentinel

Undefined = sentinel("Undefined")

def none_if_undefined[_T](value: _T | Undefined) -> _T | None:
    return None if value is Undefined else value

def func() -> None:
    device_id: str | Undefined | None = Undefined
    device_id = none_if_undefined(device_id)  # <-- error
    reveal_type(device_id)
test.py:10: error: Argument 1 to "none_if_undefined" has incompatible type "str | Undefined | None"; expected "str | Undefined"  [arg-type]
test.py:11: note: Revealed type is "str | Undefined | None"

However, assigning the result from none_if_undefined to a temporary variable first works as expected.

def func() -> None:
    device_id: str | Undefined | None = Undefined
    x = none_if_undefined(device_id)
    reveal_type(x)
    device_id = x
    reveal_type(device_id)
test.py:11: note: Revealed type is "str | None"
test.py:13: note: Revealed type is "str | None"

--
A similar issues exists for enums

from enum import Enum
from typing import Literal

class E(Enum):
    SPECIAL = 1

def none_if_special[_T](value: _T | Literal[E.SPECIAL]) -> _T | None:
    return None if value is E.SPECIAL else value

def func() -> None:
    device_id: str | Literal[E.SPECIAL] | None = E.SPECIAL
    device_id = none_if_special(device_id)  # <-- error
    reveal_type(device_id)
test.py:12: error: Argument 1 to "none_if_special" has incompatible type "str | Literal[E.SPECIAL] | None"; expected "str | Literal[E.SPECIAL]"  [arg-type]
test.py:13: note: Revealed type is "str | Literal[test7.E.SPECIAL] | None"

--
For completeness the related example with just int - that works fine -- no errors.

def none_if_int[_T](value: _T | int) -> _T | None:
    return None if isinstance(value, int) else value

def func() -> None:
    device_id: str | int | None = 42
    device_id = none_if_int(device_id)
    reveal_type(device_id)

Activity

  1. cdce8p
    added
    bugmypy got something wrongmypy got something wrong
    on Oct 30, 2026
  2. cdce8p commented on Oct 30, 2026

    @cdce8p
    cdce8p
    on Oct 30, 2026
    CollaboratorAuthor
    More actions

    /CC @edgarrmondragon Interested in taking a look at that?

    Reacted by Edgar Ramírez Mondragón
  3. edgarrmondragon commented on Oct 31, 2026

    @edgarrmondragon
    edgarrmondragon
    on Oct 31, 2026
    Contributor
    More actions

    /CC @edgarrmondragon Interested in taking a look at that?

    Thanks for the ping! I'll take a look in the coming days.

    Reacted by Marc Mueller
  4. edgarrmondragon commented on Nov 2, 2026

    @edgarrmondragon
    edgarrmondragon
    on Nov 2, 2026
    Contributor
    More actions

    Hey @cdce8p,

    So this doesn't seem to be exclusive to sentinel or enum.Enum, but it seems to be pre-existing. I was able to reproduce the same error with an example using Literal:

    from typing import Literal
    
    def none_if_one[_T](value: _T | Literal[1]) -> _T | None:
        return None if value == 1 else value
    
    def func() -> None:
        device_id: str | Literal[1] | None = 1
        device_id = none_if_one(device_id)  # <-- error
        reveal_type(device_id)

    Output:

    mre.py:8: error: Argument 1 to "none_if_one" has incompatible type "str | Literal[1] | None"; expected "str | Literal[1]"  [arg-type]
    mre.py:9: note: Revealed type is "str | Literal[1] | None"
    

    The same shape with int (or an ordinary class) instead of the literal type type-checks fine, so it seems specific to literal types.

    I'm still investigating.

    Reacted by Marc Mueller
  5. edgarrmondragon
    added a commit that references this issue on Nov 2, 2026

    Fall back to argument-only inference when literal context is too narrow

    This issue will close once commit e637cc5 is merged into the 'master' branch.
    e637cc5
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

    bugmypy got something wrongmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.