Skip to content

Comments

[ty] Support Unpack[TypedDict] for precise **kwargs typing (PEP 692)#22771

Open
bxff wants to merge 1 commit intoastral-sh:mainfrom
bxff:ty/support-typing-unpack
Open

[ty] Support Unpack[TypedDict] for precise **kwargs typing (PEP 692)#22771
bxff wants to merge 1 commit intoastral-sh:mainfrom
bxff:ty/support-typing-unpack

Conversation

@bxff
Copy link
Contributor

@bxff bxff commented Jan 20, 2026

Summary

Closes astral-sh/ty#1746 Partially addresses astral-sh/ty#154 ("Support for Unpack" checkbox)

This PR implements PEP 692 support for using Unpack[TypedDict] to provide more precise type annotations for **kwargs parameters. Previously, **kwargs: str meant all keyword arguments are strings. With this change, you can specify exactly which keyword arguments are expected:

class Movie(TypedDict):
    name: str
    year: int

def foo(**kwargs: Unpack[Movie]) -> None:
    reveal_type(kwargs)  # revealed: Movie

Implementation details:

  • Add KnownInstanceType::UnpackedTypedDict(TypedDictType) variant
  • Parse Unpack[TypedDict] in type expressions, emit errors for invalid usage
  • Type kwargs as the TypedDict itself inside function bodies
  • Validate keyword arguments against TypedDict fields at call sites
  • Report unknown-argument for kwargs not in the TypedDict
  • Report missing-argument for required TypedDict fields not provided
  • Check argument types against specific TypedDict field types
  • Handle **typed_dict_var passthrough with field-by-field type checking

Not yet implemented (out of scope, separate PEP 646 features):

  • Unpack[TypeVarTuple] - returns todo_type!
  • Unpack[tuple[...]] - returns todo_type!

Test Plan

New mdtest file annotations/unpack_kwargs.md with comprehensive coverage:

  • Basic usage and reveal_type verification
  • Required/NotRequired field handling
  • Invalid Unpack usage errors (int, regular TypeVar)
  • Call binding: valid calls, unknown kwargs, missing required fields
  • Type checking for individual field types
  • Passing compatible/incompatible TypedDict via **
  • Passing TypedDict with extra fields via **

@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 20, 2026

Typing conformance results improved 🎉

The percentage of diagnostics emitted that were expected errors increased from 79.65% to 79.90%. The percentage of expected errors that received a diagnostic increased from 70.95% to 71.80%.

Summary

Metric Old New Diff Outcome
True Positives 767 779 +12 ⏫ (✅)
False Positives 196 196 +0
False Negatives 314 306 -8 ⏬ (✅)
Total Diagnostics 963 975 +12
Precision 79.65% 79.90% +0.25% ⏫ (✅)
Recall 70.95% 71.80% +0.84% ⏫ (✅)

True positives added

Details
Location Name Message
callables_kwargs.py:101:19 invalid-assignment Object of type def func1(**kwargs: Unpack[TD2]) -> None is not assignable to TDProtocol3
callables_kwargs.py:102:19 invalid-assignment Object of type def func1(**kwargs: Unpack[TD2]) -> None is not assignable to TDProtocol4
callables_kwargs.py:122:21 invalid-type-form Unpack must be used with a TypedDict, TypeVarTuple, or tuple type, got T@func6
callables_kwargs.py:46:5 missing-argument Missing required keyword arguments v1, v3 for function func1
callables_kwargs.py:51:32 unknown-argument Argument v4 does not match any known parameter of function func1
callables_kwargs.py:58:5
callables_kwargs.py:58:11
missing-argument
invalid-argument-type
Missing required keyword arguments v1, v3 for function func1
Argument to function func1 is incorrect: Expected Unpack[TD2], found str
typeddicts_extra_items.py:143:48 unknown-argument Argument year does not match any known parameter of function unpack_no_extra
typeddicts_readonly_kwargs.py:33:12 invalid-assignment Cannot assign to key "key1" on TypedDict ReadOnlyArgs: key is marked read-only

False positives removed

Details
Location Name Message
callables_kwargs.py:24:5 type-assertion-failure Type int does not match asserted type @Todo(Unpack[] special form)
callables_kwargs.py:32:9 type-assertion-failure Type str does not match asserted type @Todo(Unpack[] special form)
callables_kwargs.py:35:5 type-assertion-failure Type str does not match asserted type @Todo(Unpack[] special form)
callables_kwargs.py:41:5 type-assertion-failure Type TD1 does not match asserted type dict[str, @Todo(Unpack[] special form)]

False positives added

Details
Location Name Message
callables_kwargs.py:100:19 invalid-assignment Object of type def func1(**kwargs: Unpack[TD2]) -> None is not assignable to TDProtocol2
callables_kwargs.py:99:19 invalid-assignment Object of type def func1(**kwargs: Unpack[TD2]) -> None is not assignable to TDProtocol1
typeddicts_extra_items.py:144:45 unknown-argument Argument year does not match any known parameter of function unpack_extra
typeddicts_readonly_kwargs.py:36:16 invalid-assignment Object of type def impl(**kwargs: Unpack[ReadOnlyArgs]) -> None is not assignable to Function

Optional Diagnostics Added

Details
Location Name Message
callables_kwargs.py:61:5
callables_kwargs.py:61:11
missing-argument
invalid-argument-type
Missing required keyword arguments v1, v3 for function func1
Argument to function func1 is incorrect: Expected Unpack[TD2], found Unknown | int | str

@AlexWaygood
Copy link
Member

Hey @bxff -- really appreciate the PRs you've been sending recently! I love how quickly you're knocking down issues, but we still have a bit of a PR backlog from the beta release and the Christmas break. Would you mind holding off on opening any more until we've had a chance to finish reviewing the ones you've already opened? It'll probably make it a better experience for you, because your PRs are less likely to sit unreviewed for extended periods of time (which can lead to annoying merge conflicts for you) :-)

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Jan 20, 2026
@bxff bxff force-pushed the ty/support-typing-unpack branch from c6efb3e to f4cb61a Compare January 20, 2026 18:16
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 20, 2026

mypy_primer results

Changes were detected when running on open source projects
pyinstrument (https://github.com/joerick/pyinstrument)
- pyinstrument/context_manager.py:40:34: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | float`, found `Unknown | None`
+ pyinstrument/context_manager.py:40:34: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | float`, found `Unknown | int | float | str | None`
- pyinstrument/context_manager.py:40:34: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["enabled", "disabled", "strict"]`, found `Unknown | None`
+ pyinstrument/context_manager.py:40:34: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["enabled", "disabled", "strict"]`, found `Unknown | int | float | str | None`
- pyinstrument/context_manager.py:41:9: error[invalid-assignment] Object of type `dict[str, @Todo]` is not assignable to attribute `options` of type `ProfileContextOptions`
+ pyinstrument/context_manager.py:40:34: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `bool | None`, found `Unknown | int | float | str | None`

aiohttp-devtools (https://github.com/aio-libs/aiohttp-devtools)
+ aiohttp_devtools/runserver/watch.py:120:55: error[invalid-argument-type] Argument to bound method `get` is incorrect: Expected `SSLContext | bool | Fingerprint`, found `None | SSLContext`
+ aiohttp_devtools/runserver/watch.py:155:57: error[invalid-argument-type] Argument to bound method `get` is incorrect: Expected `SSLContext | bool | Fingerprint`, found `None | SSLContext`
- Found 35 diagnostics
+ Found 37 diagnostics

pydantic (https://github.com/pydantic/pydantic)
- pydantic/_internal/_core_metadata.py:87:54: error[invalid-assignment] Invalid assignment to key "pydantic_js_extra" with declared type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | ((dict[str, Divergent], type[Any], /) -> None)` on TypedDict `CoreMetadata`: value of type `dict[object, object]`
+ pydantic/_internal/_core_metadata.py:87:54: error[invalid-assignment] Invalid assignment to key "pydantic_js_extra" with declared type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | ((dict[str, int | float | str | ... omitted 3 union elements], type[Any], /) -> None)` on TypedDict `CoreMetadata`: value of type `dict[object, object]`
- pydantic/fields.py:238:95: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:277:57: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
+ pydantic/fields.py:668:16: error[no-matching-overload] No overload of function `Field` matches arguments
- pydantic/fields.py:949:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:949:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:989:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:989:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1032:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1032:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1072:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1072:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1115:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1115:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1154:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1154:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1194:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`
+ pydantic/fields.py:1194:5: error[invalid-parameter-default] Default value of type `PydanticUndefinedType` is not assignable to annotated parameter type `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`
- pydantic/fields.py:1286:39: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1290:47: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1300:47: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1310:53: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1320:57: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1330:39: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1343:40: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1358:43: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- pydantic/fields.py:1573:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, Divergent] | ((dict[str, Divergent], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, Divergent], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`
+ pydantic/fields.py:1573:13: error[invalid-argument-type] Argument is incorrect: Expected `dict[str, int | float | str | ... omitted 3 union elements] | ((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) | None`, found `Top[dict[Unknown, Unknown]] | (((dict[str, int | float | str | ... omitted 3 union elements], /) -> None) & ~Top[dict[Unknown, Unknown]]) | None`
- Found 3154 diagnostics
+ Found 3145 diagnostics

discord.py (https://github.com/Rapptz/discord.py)
- discord/ext/commands/bot.py:307:48: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/bot.py:308:107: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/bot.py:331:48: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/bot.py:332:105: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
+ discord/ext/commands/core.py:602:38: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Unpack[_CommandKwargs]`, found `list[str] | tuple[str, ...] | str | ... omitted 6 union elements`
- discord/ext/commands/core.py:1551:48: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/core.py:1608:48: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
+ discord/ext/commands/core.py:1859:12: error[no-matching-overload] No overload of function `command` matches arguments
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `arguments_heading` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `commands_heading` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `default_argument_description` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `dm_help` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `dm_help_threshold` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `indent` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `no_category` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `paginator` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `show_parameter_descriptions` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `sort_commands` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1087:26: error[unknown-argument] Argument `width` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1378:26: error[unknown-argument] Argument `aliases_heading` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1378:26: error[unknown-argument] Argument `commands_heading` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1378:26: error[unknown-argument] Argument `dm_help` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1378:26: error[unknown-argument] Argument `dm_help_threshold` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1378:26: error[unknown-argument] Argument `no_category` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1378:26: error[unknown-argument] Argument `paginator` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/help.py:1378:26: error[unknown-argument] Argument `sort_commands` does not match any known parameter of bound method `__init__`
+ discord/ext/commands/hybrid.py:836:9: error[invalid-method-override] Invalid override of method `command`: Definition is incompatible with `GroupMixin.command`
+ discord/ext/commands/hybrid.py:860:9: error[invalid-method-override] Invalid override of method `group`: Definition is incompatible with `GroupMixin.group`
- discord/ext/commands/hybrid.py:853:48: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/hybrid.py:854:107: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/hybrid.py:877:48: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/hybrid.py:878:105: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/hybrid.py:932:92: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/ext/commands/hybrid.py:965:90: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/permissions.py:215:48: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- discord/permissions.py:483:46: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
+ discord/shard.py:379:50: error[unknown-argument] Argument `shard_connect_timeout` does not match any known parameter of bound method `__init__`
+ discord/shard.py:379:50: error[unknown-argument] Argument `shard_ids` does not match any known parameter of bound method `__init__`
- Found 530 diagnostics
+ Found 540 diagnostics

trio (https://github.com/python-trio/trio)
- src/trio/_tests/test_subprocess.py:496:55: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- src/trio/_tests/type_tests/subprocesses.py:15:70: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- src/trio/_tests/type_tests/subprocesses.py:22:60: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- src/trio/_tests/type_tests/subprocesses.py:23:70: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- Found 481 diagnostics
+ Found 477 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
- src/integrations/prefect-dbt/prefect_dbt/core/settings.py:94:28: error[invalid-assignment] Object of type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/integrations/prefect-dbt/prefect_dbt/core/settings.py:94:28: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
- src/integrations/prefect-dbt/tests/cli/configs/test_snowflake.py:100:9: error[unknown-argument] Argument `schema` does not match any known parameter
+ src/integrations/prefect-gitlab/prefect_gitlab/repositories.py:90:32: error[no-matching-overload] No overload of function `Field` matches arguments
- src/prefect/cli/deploy/_core.py:86:21: error[invalid-assignment] Object of type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
+ src/prefect/cli/deploy/_core.py:86:21: error[invalid-assignment] Object of type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` is not assignable to `dict[str, Any]`
- src/prefect/deployments/steps/core.py:137:38: error[invalid-argument-type] Argument is incorrect: Argument type `dict[str, Any] | int | dict[Any, Any] | ... omitted 4 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/deployments/steps/core.py:137:38: error[invalid-argument-type] Argument is incorrect: Argument type `dict[Any, Any] | int | dict[str, Any] | ... omitted 4 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/tasks.py:2214:9: error[invalid-method-override] Invalid override of method `with_options`: Definition is incompatible with `Task.with_options`
- src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `dict[str, Any] | int | Unknown | ... omitted 4 union elements` on object of type `dict[str, Any]`
+ src/prefect/utilities/templating.py:320:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown | int | dict[str, Any] | ... omitted 4 union elements` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | dict[str, Any] | int | ... omitted 4 union elements]`
+ src/prefect/utilities/templating.py:323:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `list[Unknown | int | dict[str, Any] | ... omitted 4 union elements]`
- src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `dict[str, Any] | int | str | ... omitted 3 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
+ src/prefect/workers/base.py:232:13: error[invalid-argument-type] Argument is incorrect: Argument type `str | int | dict[str, Any] | ... omitted 3 union elements` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- Found 5374 diagnostics
+ Found 5375 diagnostics

pycryptodome (https://github.com/Legrandin/pycryptodome)
+ lib/Crypto/Protocol/HPKE.py:181:39: error[invalid-argument-type] Argument to function `key_agreement` is incorrect: Expected `Unpack[RequestParams[Unknown]]`, found `Unknown | (EccKey & ~AlwaysFalsy)`
+ lib/Crypto/Protocol/HPKE.py:221:39: error[invalid-argument-type] Argument to function `key_agreement` is incorrect: Expected `Unpack[RequestParams[Unknown]]`, found `Unknown | (EccKey & ~AlwaysFalsy)`
- Found 1327 diagnostics
+ Found 1329 diagnostics

altair (https://github.com/vega/altair)
- altair/datasets/_constraints.py:57:24: error[invalid-return-type] Return type does not match returned value: expected `Metadata`, found `dict[str, @Todo]`
- altair/datasets/_constraints.py:111:33: error[invalid-argument-type] Argument to bound method `from_metadata` is incorrect: Expected `Metadata`, found `dict[str, @Todo]`
- altair/utils/core.py:676:18: warning[possibly-missing-attribute] Attribute `schema` may be missing on object of type `NativeDataFrame | DataFrameLike | Unknown | None`
- altair/utils/core.py:678:22: error[not-subscriptable] Cannot subscript object of type `NativeDataFrame` with no `__getitem__` method
- altair/utils/core.py:678:22: error[not-subscriptable] Cannot subscript object of type `DataFrameLike` with no `__getitem__` method
- altair/utils/core.py:678:22: error[not-subscriptable] Cannot subscript object of type `None` with no `__getitem__` method
- altair/utils/core.py:682:39: warning[possibly-missing-attribute] Attribute `to_native` may be missing on object of type `NativeDataFrame | DataFrameLike | Unknown | None`
- altair/utils/data.py:208:28: error[invalid-argument-type] Argument to function `len` is incorrect: Expected `Sized`, found `(NativeDataFrame & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (narwhals.stable.v1.typing.DataFrameLike & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (Unknown & ~None & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (SupportsGeoInterface & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (altair.utils.core.DataFrameLike & ~DataFrame & ~Top[dict[Unknown, Unknown]])`
- altair/utils/data.py:209:39: error[invalid-argument-type] Argument to function `len` is incorrect: Expected `Sized`, found `(NativeDataFrame & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (narwhals.stable.v1.typing.DataFrameLike & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (Unknown & ~None & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (SupportsGeoInterface & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (altair.utils.core.DataFrameLike & ~DataFrame & ~Top[dict[Unknown, Unknown]])`
- altair/utils/data.py:210:12: error[not-subscriptable] Cannot subscript object of type `NativeDataFrame` with no `__getitem__` method
- altair/utils/data.py:210:12: error[not-subscriptable] Cannot subscript object of type `DataFrameLike` with no `__getitem__` method
- altair/utils/data.py:210:12: error[not-subscriptable] Cannot subscript object of type `SupportsGeoInterface` with no `__getitem__` method
- altair/utils/data.py:210:12: error[not-subscriptable] Cannot subscript object of type `DataFrameLike` with no `__getitem__` method
- altair/utils/data.py:415:12: warning[possibly-missing-attribute] Attribute `write_csv` may be missing on object of type `(NativeDataFrame & ~SupportsGeoInterface & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (narwhals.stable.v1.typing.DataFrameLike & ~SupportsGeoInterface & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (Unknown & ~SupportsGeoInterface & ~DataFrame & ~Top[dict[Unknown, Unknown]]) | (altair.utils.core.DataFrameLike & ~SupportsGeoInterface & ~DataFrame & ~Top[dict[Unknown, Unknown]])`
- altair/utils/schemapi.py:509:12: error[invalid-return-type] Return type does not match returned value: expected `list[Any]`, found `object`
- tests/test_transformed_data.py:97:16: error[invalid-argument-type] Argument to function `len` is incorrect: Expected `Sized`, found `(Any & ~None) | DataFrameLike`
- tests/test_transformed_data.py:98:35: warning[possibly-missing-attribute] Attribute `columns` may be missing on object of type `(Any & ~None) | DataFrameLike`
- Found 1082 diagnostics
+ Found 1065 diagnostics

pywin32 (https://github.com/mhammond/pywin32)
+ com/win32comext/shell/demos/servers/folder_view.py:863:9: error[invalid-argument-type] Argument to function `UseCommandLine` is incorrect: Expected `bool`, found `Literal[0]`
+ com/win32comext/shell/demos/servers/shell_view.py:968:9: error[invalid-argument-type] Argument to function `UseCommandLine` is incorrect: Expected `bool`, found `Literal[0]`
- Found 2697 diagnostics
+ Found 2699 diagnostics

hydpy (https://github.com/hydpy-dev/hydpy)
+ hydpy/models/rconc/rconc_control.py:250:23: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 660 diagnostics
+ Found 661 diagnostics

bokeh (https://github.com/bokeh/bokeh)
+ src/bokeh/layouts.py:384:9: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["normal", "grey"] | None`, found `Unknown | str | None | UndefinedType`
+ src/bokeh/layouts.py:385:9: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `bool`, found `Unknown | bool | UndefinedType`
+ src/bokeh/layouts.py:386:9: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["auto"] | Drag | ToolProxy | None`, found `Unknown | ToolProxy | Tool | UndefinedType`
+ src/bokeh/layouts.py:387:9: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["auto"] | InspectTool | ToolProxy | Sequence[InspectTool] | None`, found `Unknown | ToolProxy | Tool | UndefinedType`
+ src/bokeh/layouts.py:388:9: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["auto"] | Scroll | ToolProxy | None`, found `Unknown | ToolProxy | Tool | UndefinedType`
+ src/bokeh/layouts.py:389:9: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["auto"] | Tap | ToolProxy | None`, found `Unknown | ToolProxy | Tool | UndefinedType`
+ src/bokeh/layouts.py:390:9: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Literal["auto"] | GestureTool | ToolProxy | None`, found `Unknown | ToolProxy | Tool | UndefinedType`
+ src/bokeh/layouts.py:394:9: error[unknown-argument] Argument `children` does not match any known parameter of bound method `__init__`
+ src/bokeh/models/renderers/contour_renderer.py:112:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `GlyphRenderer[Glyph]`, found `Unknown | Instance[GlyphRenderer[Unknown]]`
+ src/bokeh/models/renderers/contour_renderer.py:113:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `GlyphRenderer[Glyph]`, found `Unknown | Instance[GlyphRenderer[Unknown]]`
+ src/bokeh/models/renderers/contour_renderer.py:114:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float]`, found `Unknown | Seq[Unknown]`
+ src/bokeh/models/renderers/contour_renderer.py:115:32: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float]`, found `Unknown | Seq[Unknown]`
+ src/bokeh/plotting/_figure.py:243:41: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Range`, found `Range | None`
+ src/bokeh/plotting/_figure.py:243:60: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Range`, found `Range | None`
+ src/bokeh/plotting/_plot.py:106:32: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | float | datetime | timedelta`, found `int | float | str | (Unknown & ~None) | IntrinsicType`
+ src/bokeh/plotting/_plot.py:106:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | float | datetime | timedelta`, found `int | float | str | (Unknown & ~None) | IntrinsicType`
+ src/bokeh/transform.py:155:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[str | tuple[int, int, int] | tuple[int, int, int, int | float]]`, found `Sequence[str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]]`
+ src/bokeh/transform.py:158:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float]`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]`
+ src/bokeh/transform.py:159:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float] | None`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float] | None`
+ src/bokeh/transform.py:160:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float] | None`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float] | None`
+ src/bokeh/transform.py:200:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[str | tuple[int, int, int] | tuple[int, int, int, int | float]]`, found `Sequence[str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]]`
+ src/bokeh/transform.py:202:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int`, found `int | float`
+ src/bokeh/transform.py:203:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | None`, found `int | float | None`
+ src/bokeh/transform.py:204:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float]`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]`
+ src/bokeh/transform.py:242:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[Property[Literal["blank", "dot", "ring", "horizontal_line", "vertical_line", ... omitted 29 literals]]]`, found `Sequence[str]`
+ src/bokeh/transform.py:244:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int`, found `int | float`
+ src/bokeh/transform.py:245:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | None`, found `int | float | None`
+ src/bokeh/transform.py:285:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[Literal["asterisk", "circle", "circle_cross", "circle_dot", "circle_x", ... omitted 23 literals]]`, found `Sequence[str]`
+ src/bokeh/transform.py:287:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int`, found `int | float`
+ src/bokeh/transform.py:288:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | None`, found `int | float | None`
+ src/bokeh/transform.py:368:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[str | tuple[int, int, int] | tuple[int, int, int, int | float]]`, found `Sequence[str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]]`
+ src/bokeh/transform.py:371:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float]`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]`
+ src/bokeh/transform.py:372:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float] | None`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float] | None`
+ src/bokeh/transform.py:373:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float] | None`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float] | None`
+ src/bokeh/transform.py:415:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[str | tuple[int, int, int] | tuple[int, int, int, int | float]]`, found `Sequence[str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]]`
+ src/bokeh/transform.py:418:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float]`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float]`
+ src/bokeh/transform.py:419:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float] | None`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float] | None`
+ src/bokeh/transform.py:420:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | tuple[int, int, int] | tuple[int, int, int, int | float] | None`, found `str | Color | tuple[int, int, int] | tuple[int, int, int, int | float] | None`
- Found 839 diagnostics
+ Found 877 diagnostics

pandas (https://github.com/pandas-dev/pandas)
- pandas/io/parsers/readers.py:1574:41: error[invalid-argument-type] Argument to function `len` is incorrect: Expected `Sized`, found `(@Todo & ~Literal[False] & ~_NoDefault) | None`
+ pandas/io/parsers/readers.py:1574:41: error[invalid-argument-type] Argument to function `len` is incorrect: Expected `Sized`, found `Hashable & ~Literal[False] & ~_NoDefault`

rotki (https://github.com/rotki/rotki)
- rotkehlchen/chain/decoding/tools.py:96:44: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
- rotkehlchen/chain/decoding/tools.py:99:13: error[invalid-argument-type] Argument to function `decode_transfer_direction` is incorrect: Expected `Sequence[A@BaseDecoderTools]`, found `Unknown | tuple[BTCAddress, ...] | tuple[ChecksumAddress, ...] | tuple[SubstrateAddress, ...] | tuple[SolanaAddress, ...]`
- rotkehlchen/chain/decoding/tools.py:100:62: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
+ rotkehlchen/chain/decoding/tools.py:97:13: error[invalid-argument-type] Argument to function `decode_transfer_direction` is incorrect: Expected `BTCAddress | ChecksumAddress | SubstrateAddress | SolanaAddress`, found `A@BaseDecoderTools`
+ rotkehlchen/chain/decoding/tools.py:98:13: error[invalid-argument-type] Argument to function `decode_transfer_direction` is incorrect: Expected `BTCAddress | ChecksumAddress | SubstrateAddress | SolanaAddress | None`, found `A@BaseDecoderTools | None`
+ rotkehlchen/db/dbhandler.py:883:65: error[no-matching-overload] No overload of bound method `get_db_key` matches arguments
+ rotkehlchen/db/dbhandler.py:896:59: error[no-matching-overload] No overload of bound method `get_db_key` matches arguments
+ rotkehlchen/db/dbhandler.py:1051:14: error[no-matching-overload] No overload of bound method `get_db_key` matches arguments
+ rotkehlchen/tasks/events.py:586:42: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive
+ rotkehlchen/tasks/events.py:589:13: error[invalid-argument-type] Argument to bound method `set_dynamic_cache` is incorrect: Expected `int`, found `Unknown | int | None`
+ rotkehlchen/tasks/events.py:590:13: error[invalid-argument-type] Argument to bound method `set_dynamic_cache` is incorrect: Expected `int`, found `Unknown | int | None`
+ rotkehlchen/tests/db/test_db.py:397:20: error[no-matching-overload] No overload of bound method `get_dynamic_cache` matches arguments
+ rotkehlchen/tests/db/test_db.py:437:20: error[no-matching-overload] No overload of bound method `get_dynamic_cache` matches arguments
- Found 2050 diagnostics
+ Found 2057 diagnostics

Memory usage changes were detected when running on open source projects
flake8 (https://github.com/pycqa/flake8)
-     struct fields = ~3MB
+     struct fields = ~4MB
-     memo fields = ~49MB
+     memo fields = ~52MB

trio (https://github.com/python-trio/trio)
-     memo fields = ~108MB
+     memo fields = ~113MB

sphinx (https://github.com/sphinx-doc/sphinx)
- TOTAL MEMORY USAGE: ~287MB
+ TOTAL MEMORY USAGE: ~301MB
-     struct fields = ~20MB
+     struct fields = ~23MB
-     memo fields = ~176MB
+     memo fields = ~185MB

prefect (https://github.com/PrefectHQ/prefect)
- TOTAL MEMORY USAGE: ~725MB
+ TOTAL MEMORY USAGE: ~761MB
-     struct fields = ~54MB
+     struct fields = ~60MB
-     memo fields = ~424MB
+     memo fields = ~445MB

@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 20, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 47 6 5
unused-type-ignore-comment 1 28 0
unknown-argument 21 1 0
no-matching-overload 8 0 0
invalid-assignment 0 5 2
invalid-parameter-default 0 0 7
not-subscriptable 0 7 0
invalid-return-type 0 2 3
possibly-missing-attribute 0 4 0
invalid-method-override 3 0 0
Total 80 53 17

Full report with detailed diff (timing results)

@dhruvmanila
Copy link
Member

(I'll review this PR as this was something that I planned on doing anyways :))

@dhruvmanila dhruvmanila self-assigned this Jan 22, 2026
@carljm carljm requested review from dhruvmanila and removed request for carljm January 27, 2026 01:53
@bxff bxff force-pushed the ty/support-typing-unpack branch 2 times, most recently from f6042e0 to db10fa0 Compare January 28, 2026 22:38
## Summary
Closes astral-sh/ty#1746 Partially addresses astral-sh/ty#154

This PR implements PEP 692 support for using Unpack[TypedDict] to provide
more precise type annotations for **kwargs parameters.

Implementation details:
- Add KnownInstanceType::UnpackedTypedDict(TypedDictType) variant
- Parse Unpack[TypedDict] in type expressions, emit errors for invalid usage
- Type kwargs as the TypedDict itself inside function bodies
- Validate keyword arguments against TypedDict fields at call sites
- Report unknown-argument for kwargs not in the TypedDict
- Report missing-argument for required TypedDict fields not provided
- Check argument types against specific TypedDict field types
- Handle **typed_dict_var passthrough with field-by-field type checking

Not yet implemented (out of scope, separate PEP 646 features):
- Unpack[TypeVarTuple] - returns todo_type!
- Unpack[tuple[...]] - returns todo_type!

## Test Plan
New mdtest file annotations/unpack_kwargs.md with comprehensive coverage.
@bxff bxff force-pushed the ty/support-typing-unpack branch from db10fa0 to 858348b Compare January 29, 2026 00:24
@sharkdp sharkdp removed their request for review February 3, 2026 13:41
@AlexWaygood AlexWaygood removed their request for review February 10, 2026 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support typing.Unpack

3 participants