Skip to content

Comments

[ty] Fix TypedDict construction from existing TypedDict values#22904

Merged
carljm merged 1 commit intomainfrom
cjm/td-from-td
Jan 28, 2026
Merged

[ty] Fix TypedDict construction from existing TypedDict values#22904
carljm merged 1 commit intomainfrom
cjm/td-from-td

Conversation

@carljm
Copy link
Contributor

@carljm carljm commented Jan 28, 2026

Fix false positive errors when constructing a TypedDict from an existing TypedDict value using **unpacking or positional argument passing.

Previously, Data(**data) and Data(data) where data: Data would incorrectly report missing required keys because the validation logic didn't recognize that the unpacked/passed TypedDict already contains all the required fields.

This change:

  • Updates validate_from_keywords to handle **unpacking of TypedDict values by extracting and validating all fields from the unpacked type
  • Handles positional TypedDict arguments with a simple assignability check
  • Adds a bunch of tests for TypedDict construction patterns

This PR handles intersections; union handling will be a separate change.

Fixes astral-sh/ty#2647

@carljm carljm added the ty Multi-file analysis & type inference label Jan 28, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 28, 2026

Typing conformance results

No changes detected ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 28, 2026

mypy_primer results

Changes were detected when running on open source projects
prefect (https://github.com/PrefectHQ/prefect)
- 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/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/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/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/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/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/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: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: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/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/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`
+ 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`

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

AutoSplit (https://github.com/Toufool/AutoSplit)
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'capture_device_id' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'capture_device_name' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'capture_method' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'capture_region' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'captured_window_title' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'default_comparison_method' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'default_delay_time' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'default_pause_time' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'default_similarity_threshold' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'enable_auto_reset' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'fps_limit' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'live_capture_region' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'loop_splits' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'open_screenshot' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'pause_hotkey' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'reset_hotkey' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'screenshot_directory' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'screenshot_hotkey' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'screenshot_on' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'skip_split_hotkey' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'split_hotkey' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'split_image_directory' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'start_also_resets' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'toggle_auto_reset_image_hotkey' in TypedDict `UserProfileDict` constructor
- src/user_profile.py:146:35: error[missing-typed-dict-key] Missing required key 'undo_split_hotkey' in TypedDict `UserProfileDict` constructor
- Found 62 diagnostics
+ Found 37 diagnostics

bokeh (https://github.com/bokeh/bokeh)
+ src/bokeh/plotting/gmap.py:72:34: error[invalid-argument-type] Argument of type `dict[str, Unknown]` is not assignable to `GMapFigureOptions`
- Found 838 diagnostics
+ Found 839 diagnostics

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`
- Found 2050 diagnostics
+ Found 2049 diagnostics

No memory usage changes detected ✅

Fix false positive errors when constructing a TypedDict from an existing
TypedDict value using `**unpacking` or positional argument passing.

Previously, `Data(**data)` and `Data(data)` where `data: Data` would
incorrectly report missing required keys because the validation logic
didn't recognize that the unpacked/passed TypedDict already contains
all the required fields.

This change:
- Updates `validate_from_keywords` to handle `**unpacking` of TypedDict
  values by extracting and validating all fields from the unpacked type
- Adds `validate_from_typed_dict` to handle positional TypedDict arguments
- Adds comprehensive tests for TypedDict construction patterns

This PR handles simple intersections where just one element is a
`TypedDict`. Union handling and full intersection handling will be a
separate change.

Fixes astral-sh/ty#2647
@carljm carljm marked this pull request as ready for review January 28, 2026 02:54
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 28, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
missing-typed-dict-key 0 25 0
invalid-argument-type 4 2 1
invalid-assignment 4 0 1
invalid-return-type 1 0 3
unused-type-ignore-comment 2 0 0
Total 11 27 5

Full report with detailed diff (timing results)

let required_keys: OrderSet<Name> = items
.iter()
.filter_map(|(key_name, field)| field.is_required().then_some(key_name.as_str()))
.filter_map(|(key_name, field)| field.is_required().then_some(key_name.clone()))
Copy link
Member

Choose a reason for hiding this comment

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

Sad since we don't need owned values here (but not important and hard to fix).

@carljm carljm merged commit 7c826af into main Jan 28, 2026
50 checks passed
@carljm carljm deleted the cjm/td-from-td branch January 28, 2026 16:11
carljm added a commit that referenced this pull request Jan 30, 2026
* main: (76 commits)
  [ty] Improve the check for `NewType`s with generic bases (#22961)
  [ty] Ban legacy `TypeVar` bounds or constraints from containing type variables (#22949)
  Bump the typing conformance suite pin (#22960)
  [ty] Emit an error if a TypeVarTuple is used to subscript `Generic` or `Protocol` without being unpacked (#22952)
  [ty] Reduce false positives when subscripting classes generic over `TypeVarTuple`s (#22950)
  [ty] Detect invalid attempts to subclass `Protocol[]` and `Generic[]` simultaneously (#22948)
  Fix suppression indentation matching (#22903)
  Remove hidden `--output-format` warning (#22944)
  [ty] Validate signatures of dataclass `__post_init__` methods (#22730)
  [ty] extend special-cased `numbers` diagnostic to `invalid-argument-type` errors (#22938)
  [ty] Avoid false positive for `not-iterable` with no-positive intersection types (#22089)
  [ty] Preserve pure negation types in descriptor protocol (#22907)
  [ty] add special-case diagnostic for `numbers` module (#22931)
  [ty] Move the location of more `invalid-overload` diagnostics (#22933)
  [ty] Fix unary and comparison operators for TypeVars with union bounds (#22925)
  [ty] Rule Selection: ignore/warn/select all rules (unless subsequently overriden) (#22832)
  [ty] Fix TypedDict construction from existing TypedDict values (#22904)
  [ty] fix bug in string annotations and clean up diagnostics (#22913)
  [ty] Improve support for goto-type, goto-declaration, hover, and highlighting of string annotations (#22878)
  [ty] Rename old typing imports to new on `unresolved-reference`. (#22827)
  ...
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.

Cannot construct TypedDict from dict of the same type

2 participants