Skip to content

Conversation

@randolf-scholz
Copy link
Contributor

@randolf-scholz randolf-scholz commented Aug 7, 2025

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
Contributor Author

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
Copy link
Contributor Author

randolf-scholz commented Aug 7, 2025

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

from typing import Literal, 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
Contributor Author

Repro for the pyinstrument change:

from typing import TypedDict, Literal

type AsyncMode = Literal["enabled", "disabled", "strict"]

class Options(TypedDict, total=False):
    async_mode: AsyncMode

def test(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.

@github-actions

This comment has been minimized.

@github-actions
Copy link
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]

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

Labels

None yet

Projects

None yet

1 participant