Skip to content

Fix meet_types for literal and instance - #19605

Closed
randolf-scholz wants to merge 4 commits into
python:masterfrom
randolf-scholz:meet_literal
Closed

Fix meet_types for literal and instance#19605
randolf-scholz wants to merge 4 commits into
python:masterfrom
randolf-scholz:meet_literal

Conversation

@randolf-scholz

@randolf-scholzrandolf-scholz commented Aug 7, 2025

Copy link
Copy Markdown
Contributor

New Tests

  • JoinSuite.test_mixed_literal_types tests join between Literal? and other types.
  • MeetSuite.test_mixed_literal_types tests meet between Literal? and other types
  • TypeOpsSuite.test_simplified_union_with_mixed_str_literals2 tests simplified unions containing Literal? types
  • SubtypingSuite.test_literal tests subtype checks with Literal? types
  • RestrictionSuite: new test suite for testing the restrict_subtype_away method.
  • testJoinLiteralInstanceAndEnum tests join between Literal? and StrEnum

@github-actions

This comment has been minimized.

@randolf-scholz

Copy link
Copy Markdown
ContributorAuthor

The first operator failure seems like a bug in typeshed to me: python/typeshed#9004

@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@randolf-scholz

randolf-scholz commented Aug 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Hm, the xarray redundant cast looks like this: mypy-playground

fromtypingimportLiteral, cast
type PDDatetimeUnitOptions=Literal["s", "ms", "ns"]
time_unit=cast(PDDatetimeUnitOptions, "s") # [redundant-cast]

Is the current behavior of emitting redundant cast here intentional? It seems formally incorrect, since "s" should be Literal["s"]? and not Literal["s", "ms", "ns"], but maybe this was introduces as a convenience thing?

In comparison, pyright does not issue RedundantCast here, which aligns with my priors. pyright-playground

@randolf-scholz
randolf-scholz marked this pull request as ready for review August 8, 2025 06:29
@randolf-scholz
randolf-scholz marked this pull request as draft August 8, 2025 06:29
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@randolf-scholz

Copy link
Copy Markdown
ContributorAuthor

Repro for the pyinstrument change:

fromtypingimportTypedDict, Literal
type AsyncMode=Literal["enabled", "disabled", "strict"]
classOptions(TypedDict, total=False):
async_mode: AsyncModedeftest(kwargs: Options, d: dict[Literal["async_mode"], AsyncMode]) ->None:
reveal_type(kwargs)
reveal_type(kwargs.get)
reveal_type(d.get)
reveal_type(kwargs.get("async_mode", "disabled"))
# master: AsyncMode# feature: "Literal['enabled'] | Literal['strict'] | Literal['disabled']?"

I believe the problem here is not the new union schema ("x"? | "x" --> "x"? rather than "x"; see #19625), but that mypy is too simplistic about kwargs.get.

reveal_type(kwargs.get)
# mypy: Overload# def (str) -> object,# def (str, default: object) -> object,# def [_T] (str, default: _T`1) -> object)"# pyright: Overload# def (k: Literal['async_mode']) -> (AsyncMode | None)# def [T] (k: Literal['async_mode'], default: T | AsyncMode) -> (T | AsyncMode)# def [T] (k: str) -> (Any | None), (k: str, default: Any | T) -> (Any | T)

Basically, we want here to pick the second signature shown by pyright, and use the minimal solution T=Never, or equivalently an overload of the form

def [T] (k: Literal['async_mode'], default: AsyncMode) -> (AsyncMode)

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

randolf-scholz added a commit to randolf-scholz/mypy that referenced this pull request Dec 15, 2025
@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

pyinstrument (https://github.com/joerick/pyinstrument)
- pyinstrument/context_manager.py:40: error: Argument 1 to "Profiler" has incompatible type "**dict[str, Literal['enabled', 'disabled', 'strict'] | float | bool | None]"; expected "float" [arg-type]+ pyinstrument/context_manager.py:40: error: Argument 1 to "Profiler" has incompatible type "**dict[str, Literal['enabled', 'strict'] | float | str | bool | None]"; expected "float" [arg-type]- pyinstrument/context_manager.py:40: error: Argument 1 to "Profiler" has incompatible type "**dict[str, Literal['enabled', 'disabled', 'strict'] | float | bool | None]"; expected "Literal['enabled', 'disabled', 'strict']" [arg-type]+ pyinstrument/context_manager.py:40: error: Argument 1 to "Profiler" has incompatible type "**dict[str, Literal['enabled', 'strict'] | float | str | bool | None]"; expected "Literal['enabled', 'disabled', 'strict']" [arg-type]- pyinstrument/context_manager.py:40: error: Argument 1 to "Profiler" has incompatible type "**dict[str, Literal['enabled', 'disabled', 'strict'] | float | bool | None]"; expected "bool | None" [arg-type]+ pyinstrument/context_manager.py:40: error: Argument 1 to "Profiler" has incompatible type "**dict[str, Literal['enabled', 'strict'] | float | str | bool | None]"; expected "bool | None" [arg-type]
prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/server/services/task_run_recorder.py:181: error: "Collection[Any]" has no attribute "state" [attr-defined]+ src/prefect/server/services/task_run_recorder.py:181: error: "object" has no attribute "state" [attr-defined]- src/prefect/server/services/task_run_recorder.py:184: error: "Collection[Any]" has no attribute "id" [attr-defined]+ src/prefect/server/services/task_run_recorder.py:184: error: "object" has no attribute "id" [attr-defined]- src/prefect/server/services/task_run_recorder.py:190: error: "Collection[Any]" has no attribute "keys" [attr-defined]+ src/prefect/server/services/task_run_recorder.py:190: error: "object" has no attribute "keys" [attr-defined]- src/prefect/concurrency/_asyncio.py:112: error: R? has no attribute "json" [attr-defined]+ src/prefect/task_worker.py:370: error: Argument 1 to "submit" of "Executor" has incompatible type "Callable[[Callable[_P, _T], **_P], _T]"; expected "Callable[[Callable[[Task[P, R], UUID | None, TaskRun | None, dict[str, Any] | None, PrefectFuture[Any] | Any | Iterable[PrefectFuture[Any] | Any] | None, Literal['state', 'result'], dict[str, set[RunInput]] | None, dict[str, Any] | None], R | State[Any] | None], Task[[VarArg(Any), KwArg(Any)], Any], UUID, TaskRun, dict[Any, Any], list[Any], str, Any | None], Any | State[Any] | None]" [arg-type]
operator (https://github.com/canonical/operator)
+ ops/model.py:809: error: Unsupported operand types for - ("set[tuple[Literal['tcp', 'udp', 'icmp'], int | None]]" and "set[tuple[str, int] | tuple[Literal['tcp', 'udp', 'icmp'], int | None]]") [operator]+ ops/model.py:811: error: Incompatible types in assignment (expression has type "str", variable has type "Literal['tcp', 'udp', 'icmp']") [assignment]
discord.py (https://github.com/Rapptz/discord.py)
- discord/app_commands/transformers.py:139: error: Incompatible types in assignment (expression has type "list[dict[str, Any]]", target has type "str | int") [assignment]+ discord/app_commands/transformers.py:139: error: Incompatible types in assignment (expression has type "list[dict[str, Any]]", target has type "bool | str | int") [assignment]- discord/app_commands/transformers.py:141: error: Incompatible types in assignment (expression has type "list[int]", target has type "str | int") [assignment]+ discord/app_commands/transformers.py:141: error: Incompatible types in assignment (expression has type "list[int]", target has type "bool | str | int") [assignment]- discord/app_commands/transformers.py:149: error: Incompatible types in assignment (expression has type "int | float", target has type "str | int") [assignment]+ discord/app_commands/transformers.py:149: error: Incompatible types in assignment (expression has type "int | float", target has type "bool | str | int") [assignment]- discord/app_commands/transformers.py:151: error: Incompatible types in assignment (expression has type "int | float", target has type "str | int") [assignment]+ discord/app_commands/transformers.py:151: error: Incompatible types in assignment (expression has type "int | float", target has type "bool | str | int") [assignment]
dedupe (https://github.com/dedupeio/dedupe)
- dedupe/api.py:1547: error: Redundant cast to "Literal['match', 'distinct']" [redundant-cast]

@randolf-scholz

Copy link
Copy Markdown
ContributorAuthor

Resubmitted with clean history as #20444

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@randolf-scholz