Skip to content

[ty] Track argument variance for literal promotion without relying on SpecializationBuilder internals#21905

Closed
dcreager wants to merge 15 commits intomainfrom
dcreager/param-variance
Closed

[ty] Track argument variance for literal promotion without relying on SpecializationBuilder internals#21905
dcreager wants to merge 15 commits intomainfrom
dcreager/param-variance

Conversation

@dcreager
Copy link
Member

When inferring a specialization for a generic function call, we might want to promote Literal types that appear in the inferred specialization. Whether we do so depends (among other things) on the variance of those typevars in the parameter types that each argument is matched to. That means we need to track the variance of each typevar as we process the arguments during inference.

Before, we were piggy-backing on how SpecializationBuilder walks through the formal and actual types. Its infer_map method takes in a callback that is invoked each time we record a new type mapping for a typevar. Before, this method was provided with the variance of that typevar, according to the formal/actual pair that we just checked.

We are in the process of changing the internals of SpecializationBuilder so that this approach is no longer tenable. This PR updates the call inference logic to calculate the typevar variance separately, without making any assumptions about how SpecializationBuilder does its work.

@dcreager dcreager added internal An internal refactor or improvement ty Multi-file analysis & type inference labels Dec 10, 2025
@astral-sh-bot
Copy link

astral-sh-bot bot commented Dec 10, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Dec 10, 2025

mypy_primer results

Changes were detected when running on open source projects
beartype (https://github.com/beartype/beartype)
- beartype/claw/_package/clawpkgtrie.py:66:29: warning[unsupported-base] Unsupported class base with type `<class 'dict[str, PackagesTrieBlacklist]'> | <class 'dict[str, Divergent]'>`
- beartype/claw/_package/clawpkgtrie.py:247:29: warning[unsupported-base] Unsupported class base with type `<class 'dict[str, PackagesTrieWhitelist]'> | <class 'dict[str, Divergent]'>`
- Found 491 diagnostics
+ Found 489 diagnostics

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
- src/scikit_build_core/build/wheel.py:98:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 43 diagnostics
+ Found 42 diagnostics

bokeh (https://github.com/bokeh/bokeh)
- src/bokeh/models/annotations/geometry.py:222:29: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[int]] | Property[int]`, found `<class 'Float'>`
+ src/bokeh/models/annotations/geometry.py:222:29: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[0]]] | Property[Literal[0]]`, found `<class 'Float'>`
- src/bokeh/models/annotations/geometry.py:229:30: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[int]] | Property[int]`, found `<class 'Float'>`
+ src/bokeh/models/annotations/geometry.py:229:30: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[0]]] | Property[Literal[0]]`, found `<class 'Float'>`
+ src/bokeh/models/plots.py:779:27: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[5]]] | Property[Literal[5]]`, found `<class 'Int'>`
+ src/bokeh/models/plots.py:801:30: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[2000]]] | Property[Literal[2000]]`, found `<class 'Int'>`
- src/bokeh/models/ranges.py:403:22: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[int]] | Property[int]`, found `<class 'Float'>`
+ src/bokeh/models/ranges.py:403:22: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[0]]] | Property[Literal[0]]`, found `<class 'Float'>`
- src/bokeh/models/ranges.py:413:20: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[int]] | Property[int]`, found `<class 'Float'>`
+ src/bokeh/models/ranges.py:413:20: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[0]]] | Property[Literal[0]]`, found `<class 'Float'>`
+ src/bokeh/models/tools.py:685:25: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[0]]] | Property[Literal[0]]`, found `<class 'Int'>`
+ src/bokeh/models/tools.py:1182:25: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[0]]] | Property[Literal[0]]`, found `<class 'Int'>`
+ src/bokeh/models/widgets/inputs.py:441:34: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[2]]] | Property[Literal[2]]`, found `<class 'Int'>`
+ src/bokeh/models/widgets/inputs.py:592:32: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[100]]] | Property[Literal[100]]`, found `<class 'Int'>`
+ src/bokeh/models/widgets/inputs.py:601:22: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[1]]] | Property[Literal[1]]`, found `<class 'Int'>`
+ src/bokeh/models/widgets/sliders.py:102:22: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[""]]] | Property[Literal[""]]`, found `<class 'String'>`
+ src/bokeh/models/widgets/tables.py:848:31: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `type[Property[Literal[0]]] | Property[Literal[0]]`, found `<class 'Int'>`
- src/bokeh/plotting/_figure.py:201:97: error[invalid-argument-type] Argument to function `process_axis_and_grid` is incorrect: Expected `str | BaseText | None`, found `Unknown | Nullable[str]`
+ src/bokeh/plotting/_figure.py:201:97: error[invalid-argument-type] Argument to function `process_axis_and_grid` is incorrect: Expected `str | BaseText | None`, found `Unknown | Nullable[Literal[""]]`
- src/bokeh/plotting/_figure.py:202:97: error[invalid-argument-type] Argument to function `process_axis_and_grid` is incorrect: Expected `str | BaseText | None`, found `Unknown | Nullable[str]`
+ src/bokeh/plotting/_figure.py:202:97: error[invalid-argument-type] Argument to function `process_axis_and_grid` is incorrect: Expected `str | BaseText | None`, found `Unknown | Nullable[Literal[""]]`
- src/bokeh/settings.py:747:46: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `((int | None | str, /) -> int | None | str) | None`, found `def convert_logging(value: str | int) -> int | None`
+ src/bokeh/settings.py:747:46: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `((int | None | str, /) -> int | None | Literal["none", "debug"]) | None`, found `def convert_logging(value: str | int) -> int | None`
- Found 867 diagnostics
+ Found 876 diagnostics

zulip (https://github.com/zulip/zulip)
+ zerver/tests/test_users.py:259:25: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["email"], Unknown].__getitem__(key: Literal["email"], /) -> Unknown` cannot be called with key of type `Literal["is_owner"]` on object of type `dict[Literal["email"], Unknown]`
+ zerver/tests/test_users.py:261:26: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["email"], Unknown].__getitem__(key: Literal["email"], /) -> Unknown` cannot be called with key of type `Literal["is_owner"]` on object of type `dict[Literal["email"], Unknown]`
+ zerver/tests/test_users.py:318:25: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["email"], Unknown].__getitem__(key: Literal["email"], /) -> Unknown` cannot be called with key of type `Literal["is_admin"]` on object of type `dict[Literal["email"], Unknown]`
+ zerver/tests/test_users.py:320:26: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["email"], Unknown].__getitem__(key: Literal["email"], /) -> Unknown` cannot be called with key of type `Literal["is_admin"]` on object of type `dict[Literal["email"], Unknown]`
+ zerver/tests/test_users.py:361:26: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["email"]` on object of type `dict[Literal["user_id"], Unknown]`
+ zerver/tests/test_users.py:362:27: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["avatar_url"]` on object of type `dict[Literal["user_id"], Unknown]`
+ zerver/tests/test_users.py:363:26: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["delivery_email"]` on object of type `dict[Literal["user_id"], Unknown]`
+ zerver/tests/test_users.py:387:26: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["email"]` on object of type `dict[Literal["user_id"], Unknown]`
+ zerver/tests/test_users.py:389:13: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["avatar_url"]` on object of type `dict[Literal["user_id"], Unknown]`
+ zerver/tests/test_users.py:402:26: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["email"]` on object of type `dict[Literal["user_id"], Unknown]`
+ zerver/tests/test_users.py:404:13: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["avatar_url"]` on object of type `dict[Literal["user_id"], Unknown]`
+ zerver/tests/test_users.py:406:26: error[invalid-argument-type] Method `__getitem__` of type `bound method dict[Literal["user_id"], Unknown].__getitem__(key: Literal["user_id"], /) -> Unknown` cannot be called with key of type `Literal["delivery_email"]` on object of type `dict[Literal["user_id"], Unknown]`
- Found 3631 diagnostics
+ Found 3643 diagnostics

No memory usage changes detected ✅

@carljm
Copy link
Contributor

carljm commented Dec 10, 2025

Code looks fine. Ecosystem changes on zulip suggest that there's an unintentional behavior change here?

@astral-sh-bot
Copy link

astral-sh-bot bot commented Dec 10, 2025

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 21 0 7
unused-ignore-comment 0 3 0
Total 21 3 7

Full report with detailed diff (timing results)

@carljm
Copy link
Contributor

carljm commented Dec 10, 2025

It seems like in the zulip cases we are failing to do literal promotion in a case where previously we did promote.

@dcreager
Copy link
Member Author

It seems like in the zulip cases we are failing to do literal promotion in a case where previously we did promote.

Fixed the zulip case and added an mdtest regression for it.

@codspeed-hq
Copy link

codspeed-hq bot commented Dec 11, 2025

CodSpeed Performance Report

Merging #21905 will degrade performances by 4.25%

Comparing dcreager/param-variance (fe25ee2) with main (c8851ec)

Summary

❌ 1 regression
✅ 12 untouched
⏩ 39 skipped1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Mode Benchmark BASE HEAD Change
Simulation hydra-zen 1.3 s 1.4 s -4.25%

Footnotes

  1. 39 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@AlexWaygood
Copy link
Member

Looks like both the ecosystem CI jobs timed out, which I think is probably not related to the github outage?

@dcreager
Copy link
Member Author

Looks like both the ecosystem CI jobs timed out, which I think is probably not related to the github outage?

Yep, I've got a mypy_primer run going locally to confirm

* origin/main: (29 commits)
  Document range suppressions, reorganize suppression docs (#21884)
  Ignore ruff:isort like ruff:noqa in new suppressions (#21922)
  [ty] Handle `Definition`s in `SemanticModel::scope` (#21919)
  [ty] Attach salsa db when running ide tests for easier debugging (#21917)
  [ty] Don't show hover for expressions with no inferred type (#21924)
  [ty] avoid unions of generic aliases of the same class in fixpoint (#21909)
  [ty] Squash false positive logs for failing to find `builtins` as a real module
  [ty] Uniformly use "not supported" in diagnostics (#21916)
  [ty] Reduce size of ty-ide snapshots (#21915)
  [ty] Adjust scope completions to use all reachable symbols
  [ty] Rename `all_members_of_scope` to `all_end_of_scope_members`
  [ty] Remove `all_` prefix from some routines on UseDefMap
  Enable `--document-private-items` for `ruff_python_formatter` (#21903)
  Remove `BackwardsTokenizer` based `parenthesized_range` references in `ruff_linter` (#21836)
  [ty] Revert "Do not infer types for invalid binary expressions in annotations" (#21914)
  Skip over trivia tokens after re-lexing (#21895)
  [ty] Avoid inferring types for invalid binary expressions in string annotations (#21911)
  [ty] Improve overload call resolution tracing (#21913)
  [ty] fix missing heap_size on Salsa query (#21912)
  [ty] Support implicit type of `cls` in signatures (#21771)
  ...
@dcreager dcreager force-pushed the dcreager/param-variance branch from df40a2b to 438de79 Compare December 11, 2025 20:20
@dcreager
Copy link
Member Author

Looks like both the ecosystem CI jobs timed out

Cherry-picked several of performance optimizations commits from #21551 and that seems to have fixed the timeouts. Locally, at least! Waiting for CI to confirm

@dcreager
Copy link
Member Author

Locally, at least! Waiting for CI to confirm

Welp, famous last words! Instead of pushing this forward more, I'm going to see if I can update the generic protocol PR to not require this.

@dcreager
Copy link
Member Author

I'm going to see if I can update the generic protocol PR to not require this.

This happened, so I'm closing this for now

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

Labels

ecosystem-analyzer internal An internal refactor or improvement ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants