Skip to content

[ty] Promote literals when inferring class specializations from constructors #18102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

dcreager
Copy link
Member

This implements the stopgap approach described in astral-sh/ty#336 (comment) for handling literal types in generic class specializations.

With this approach, we will promote any literal to its instance type, but only when inferring a generic class specialization from a constructor call:

class C[T]:
    def __init__(self, x: T) -> None: ...

reveal_type(C("string"))  # revealed: C[str]

If you specialize the class explicitly, we still use whatever type you provide, even if it's a literal:

from typing import Literal

reveal_type(C[Literal[5]](5))  # revealed: C[Literal[5]]

And this doesn't apply at all to generic functions:

def f[T](x: T) -> T:
    return x

reveal_type(f(5))  # revealed: Literal[5]

As part of making this happen, we also generalize the TypeMapping machinery. This provides a way to apply a function to type, returning a new type. Compicating matters is that for function literals, we have to apply the mapping lazily, since the function's signature is not created until (and if) someone calls its signature method. That means we have to stash away the mappings that we want to apply to the signatures parameter/return annotations once we do create it. This requires some minor Cow shenanigans to continue working for partial specializations.

@dcreager dcreager added the ty Multi-file analysis & type inference label May 14, 2025
@dcreager dcreager requested a review from sharkdp as a code owner May 14, 2025 18:08
// typevars that are inferred as a literal to the corresponding instance type.
builder
.build(gc)
.apply_type_mapping(db, &TypeMapping::PromoteLiterals)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything else in the PR is setup for this one line

…rals

* origin/main:
  [ty] Add type-expression syntax link to invalid-type-expression (#18104)
  [`flake8-simplify`] add fix safety section (`SIM103`) (#18086)
  [ty] mypy_primer: fix static-frame setup (#18103)
  [`flake8-simplify`] Correct behavior for `str.split`/`rsplit` with `maxsplit=0` (`SIM905`) (#18075)
  [ty] Fix more generics-related TODOs (#18062)
Copy link
Contributor

github-actions bot commented May 14, 2025

mypy_primer results

Changes were detected when running on open source projects
async-utils (https://github.com/mikeshardmind/async-utils)
- error[invalid-assignment] src/async_utils/priority_sem.py:31:1: Object of type `ContextVar[Literal[0]]` is not assignable to `ContextVar[int]`
- Found 16 diagnostics
+ Found 15 diagnostics

python-chess (https://github.com/niklasf/python-chess)
- error[invalid-return-type] chess/svg.py:162:12: Return type does not match returned value: expected `Element[str]`, found `Element[Literal["svg"]]`
- error[invalid-return-type] chess/svg.py:200:12: Return type does not match returned value: expected `Element[str]`, found `Element[Literal["g"]]`
- Found 39 diagnostics
+ Found 37 diagnostics

trio (https://github.com/python-trio/trio)
- error[invalid-argument-type] src/trio/_core/_tests/test_run.py:2162:17: Argument to bound method `set` is incorrect: Expected `Literal["hmmm"]`, found `Literal["hmmmm"]`
- error[invalid-argument-type] src/trio/_core/_tests/test_run.py:2172:17: Argument to bound method `set` is incorrect: Expected `Literal["hmmm"]`, found `Literal["hmmmm"]`
- Found 1097 diagnostics
+ Found 1095 diagnostics

mkosi (https://github.com/systemd/mkosi)
- error[invalid-argument-type] mkosi/config.py:5087:23: Argument to bound method `set` is incorrect: Expected `Literal[False]`, found `bool`
- error[invalid-argument-type] mkosi/config.py:5089:29: Argument to bound method `set` is incorrect: Expected `Literal[False]`, found `bool`
- error[invalid-argument-type] mkosi/config.py:5091:31: Argument to bound method `set` is incorrect: Expected `Literal[False]`, found `bool`
- Found 318 diagnostics
+ Found 315 diagnostics

tornado (https://github.com/tornadoweb/tornado)
- error[invalid-argument-type] tornado/test/asyncio_test.py:277:26: Argument to bound method `set` is incorrect: Expected `Literal["default"]`, found `Literal["foo"]`
- Found 511 diagnostics
+ Found 510 diagnostics

asynq (https://github.com/quora/asynq)
- error[invalid-argument-type] asynq/asynq_to_async.py:85:41: Argument to bound method `set` is incorrect: Expected `Literal[False]`, found `Literal[True]`
- error[unresolved-attribute] asynq/tests/test_generator.py:28:24: Type `Value[Literal["value"]]` has no attribute `value`
+ error[unresolved-attribute] asynq/tests/test_generator.py:28:24: Type `Value[str]` has no attribute `value`
- error[invalid-argument-type] asynq/tests/test_scoped_value.py:28:25: Argument to bound method `override` is incorrect: Expected `Literal["a"]`, found `Literal["c"]`
- error[invalid-argument-type] asynq/tests/test_scoped_value.py:51:13: Argument to bound method `set` is incorrect: Expected `Literal["capybara"]`, found `Literal["nutria"]`
- error[invalid-argument-type] asynq/tests/test_scoped_value.py:64:29: Argument to bound method `override` is incorrect: Expected `Literal["a"]`, found `Literal["b"]`
- Found 221 diagnostics
+ Found 217 diagnostics

pytest (https://github.com/pytest-dev/pytest)
- error[invalid-return-type] src/_pytest/junitxml.py:113:20: Return type does not match returned value: expected `Element[str] | None`, found `Element[Literal["properties"]]`
- error[invalid-return-type] src/_pytest/junitxml.py:152:16: Return type does not match returned value: expected `Element[str]`, found `Element[Literal["testcase"]]`
- error[no-matching-overload] src/_pytest/junitxml.py:675:27: No overload of function `tostring` matches arguments
- error[invalid-return-type] src/_pytest/junitxml.py:691:20: Return type does not match returned value: expected `Element[str] | None`, found `Element[Literal["properties"]]`
- Found 934 diagnostics
+ Found 930 diagnostics

colour (https://github.com/colour-science/colour)
- error[invalid-argument-type] colour/io/tm2714.py:1766:41: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["IESTM2714"]]`
- error[invalid-argument-type] colour/io/tm2714.py:1778:21: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal[""]] | Element[str]`
- error[no-matching-overload] colour/io/tm2714.py:1786:17: No overload of function `tostring` matches arguments
- Found 632 diagnostics
+ Found 629 diagnostics

meson (https://github.com/mesonbuild/meson)
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:627:35: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:641:37: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:647:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:650:37: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:659:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:681:37: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-return-type] mesonbuild/backend/vs2010backend.py:699:16: Return type does not match returned value: expected `tuple[Element[str], Element[str]]`, found `tuple[Element[Literal["Project"]], Element[str]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:1804:40: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:1805:38: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["Project"]]`
- error[invalid-argument-type] mesonbuild/backend/vs2010backend.py:1864:54: Argument to bound method `__init__` is incorrect: Expected `Element[str] | None`, found `Element[Literal["Project"]]`
- error[invalid-assignment] mesonbuild/interpreter/compiler.py:164:1: Object of type `KwargInfo[Literal[""]]` is not assignable to `KwargInfo[str]`
- error[invalid-assignment] mesonbuild/interpreter/type_checking.py:186:1: Object of type `KwargInfo[Literal[True]]` is not assignable to `KwargInfo[bool | UserFeatureOption]`
+ error[invalid-assignment] mesonbuild/interpreter/type_checking.py:186:1: Object of type `KwargInfo[bool]` is not assignable to `KwargInfo[bool | UserFeatureOption]`
- error[invalid-assignment] mesonbuild/interpreter/type_checking.py:193:1: Object of type `KwargInfo[Literal[False]]` is not assignable to `KwargInfo[bool]`
- error[invalid-assignment] mesonbuild/interpreter/type_checking.py:490:1: Object of type `KwargInfo[Literal[False]]` is not assignable to `KwargInfo[bool]`
- error[invalid-argument-type] mesonbuild/modules/_qt.py:776:35: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["RCC"]]`
- error[invalid-argument-type] mesonbuild/modules/_qt.py:785:31: Argument to bound method `__init__` is incorrect: Expected `Element[str] | None`, found `Element[Literal["RCC"]]`
- error[invalid-assignment] mesonbuild/modules/gnome.py:203:1: Object of type `KwargInfo[Literal[True]]` is not assignable to `KwargInfo[bool]`
- error[invalid-argument-type] mesonbuild/modules/i18n.py:352:27: Argument to bound method `evolve` is incorrect: Expected `Literal[False] | None | _NULL_T`, found `Literal[True]`
- error[invalid-argument-type] mesonbuild/mtest.py:869:42: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["testsuite"]]`
- error[invalid-argument-type] mesonbuild/mtest.py:891:37: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["testsuite"]]`
- error[invalid-argument-type] mesonbuild/mtest.py:894:37: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["testsuite"]]`
- error[invalid-argument-type] mesonbuild/mtest.py:905:38: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["testsuite"]] | Unknown`
- error[invalid-argument-type] packaging/createpkg.py:71:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:73:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:75:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:77:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:78:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:81:41: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:84:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:85:32: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:87:23: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] packaging/createpkg.py:90:24: Argument to bound method `__init__` is incorrect: Expected `Element[str] | None`, found `Element[Literal["installer-gui-script"]]`
- error[invalid-argument-type] run_project_tests.py:1251:39: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["testsuites"]]`
- error[invalid-argument-type] run_project_tests.py:1443:20: Argument to bound method `__init__` is incorrect: Expected `Element[str] | None`, found `Element[Literal["testsuites"]]`
- error[invalid-argument-type] unittests/internaltests.py:1546:22: Argument to bound method `evolve` is incorrect: Expected `Literal["foo"] | None | _NULL_T`, found `Literal["bar"]`
- Found 1470 diagnostics
+ Found 1436 diagnostics

hydra-zen (https://github.com/mit-ll-responsible-ai/hydra-zen)
- error[type-assertion-failure] tests/annotations/declarations.py:951:5: Argument does not have asserted type `FullBuilds[@Todo(Support for `typing.TypeAlias`)] | StdBuilds[@Todo(Support for `typing.TypeAlias`)]`
- Found 650 diagnostics
+ Found 649 diagnostics

vision (https://github.com/pytorch/vision)
- error[no-matching-overload] test/builtin_dataset_mocks.py:839:22: No overload of function `tostring` matches arguments
- error[no-matching-overload] test/test_datasets.py:747:22: No overload of function `tostring` matches arguments
- Found 2049 diagnostics
+ Found 2047 diagnostics

streamlit (https://github.com/streamlit/streamlit)
- error[invalid-assignment] lib/streamlit/runtime/scriptrunner_utils/script_run_context.py:63:1: Object of type `ContextVar[Literal[False]]` is not assignable to `ContextVar[bool]`
- Found 3311 diagnostics
+ Found 3310 diagnostics

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- error[invalid-argument-type] ddtrace/internal/coverage/code.py:224:38: Argument to bound method `set` is incorrect: Expected `Literal[False]`, found `Literal[True]`
- Found 8719 diagnostics
+ Found 8718 diagnostics

zulip (https://github.com/zulip/zulip)
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:641:42: Argument to bound method `insert` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:646:24: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]] | Element[str]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:682:38: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]] | Element[str]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1023:16: Return type does not match returned value: expected `Element[str]`, found `Element[Literal["p"]]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:1038:32: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:1057:31: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:1076:38: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1083:20: Return type does not match returned value: expected `Element[str] | None`, found `Element[Literal["div"]]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:1177:40: Argument to bound method `insert` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:1257:42: Argument to bound method `insert` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]]`
- error[invalid-argument-type] zerver/lib/markdown/__init__.py:1264:24: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]] | Element[str]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1458:20: Return type does not match returned value: expected `Element[str] | None`, found `Element[Literal["span"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1471:24: Return type does not match returned value: expected `Element[str] | None`, found `Element[Literal["span"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1478:16: Return type does not match returned value: expected `Element[str] | None`, found `Element[Literal["time"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1513:12: Return type does not match returned value: expected `Element[str]`, found `Element[Literal["span"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1522:12: Return type does not match returned value: expected `Element[str]`, found `Element[Literal["img"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1612:20: Return type does not match returned value: expected `str | Element[str]`, found `Element[Literal["span"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1678:12: Return type does not match returned value: expected `Element[str] | str`, found `Element[Literal["a"]]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:1978:20: Return type does not match returned value: expected `tuple[Element[str] | str | None, int | None, int | None]`, found `tuple[Element[Literal["span"]], int, int]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:2015:20: Return type does not match returned value: expected `tuple[Element[str] | str | None, int | None, int | None]`, found `tuple[Element[Literal["span"]], int, int]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:2050:16: Return type does not match returned value: expected `tuple[Element[str] | str | None, int | None, int | None]`, found `tuple[Element[Literal["a"]], int, int]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:2094:16: Return type does not match returned value: expected `tuple[Element[str] | str | None, int | None, int | None]`, found `tuple[Element[Literal["a"]], int, int]`
- error[invalid-return-type] zerver/lib/markdown/__init__.py:2126:16: Return type does not match returned value: expected `tuple[Element[str] | str | None, int | None, int | None]`, found `tuple[Element[Literal["a"]], int, int]`
- error[invalid-argument-type] zerver/lib/markdown/nested_code_blocks.py:67:26: Argument to function `SubElement` is incorrect: Expected `Element[str]`, found `Element[Literal["div"]]`
- error[invalid-return-type] zerver/lib/markdown/nested_code_blocks.py:69:16: Return type does not match returned value: expected `Element[str]`, found `Element[Literal["div"]]`
- Found 4896 diagnostics
+ Found 4871 diagnostics

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ty Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants