Skip to content

Commit abcbf72

Browse files
committed
Fallback
1 parent 42d1dc6 commit abcbf72

3 files changed

Lines changed: 39 additions & 44 deletions

File tree

crates/ty_python_semantic/resources/mdtest/typed_dict.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3180,21 +3180,51 @@ stringified(a=1)
31803180
Only string-keyed mappings can be unpacked into named keyword parameters.
31813181

31823182
```py
3183-
def takes_name(*, name: str) -> None: ...
3183+
from typing_extensions import TypedDict, Unpack
3184+
3185+
class HasNameKwargs(TypedDict):
3186+
name: str
3187+
3188+
def takes_name_kwargs(**kwargs: Unpack[HasNameKwargs]) -> None: ...
31843189
def _(int_key_dict: dict[int, str]) -> None:
31853190
# snapshot: invalid-argument-type
3186-
takes_name(**int_key_dict)
3191+
takes_name_kwargs(**int_key_dict)
31873192
```
31883193

31893194
```snapshot
31903195
error[invalid-argument-type]: Argument expression after ** must be a mapping with `str` key type
3191-
--> src/mdtest_snippet.py:4:16
3196+
--> src/mdtest_snippet.py:9:23
31923197
|
3193-
4 | takes_name(**int_key_dict)
3194-
| ^^^^^^^^^^^^^^ Found `int`
3198+
9 | takes_name_kwargs(**int_key_dict)
3199+
| ^^^^^^^^^^^^^^ Found `int`
31953200
|
31963201
```
31973202

3203+
### Non-mapping values are rejected without missing-argument cascades
3204+
3205+
```py
3206+
from typing_extensions import TypedDict, Unpack
3207+
3208+
class HasNameKwargs(TypedDict):
3209+
name: str
3210+
3211+
class NotAMapping: ...
3212+
3213+
def takes_name_kwargs(**kwargs: Unpack[HasNameKwargs]) -> None: ...
3214+
def _(bad: NotAMapping) -> None:
3215+
# snapshot: invalid-argument-type
3216+
takes_name_kwargs(**bad)
3217+
```
3218+
3219+
```snapshot
3220+
error[invalid-argument-type]: Argument expression after ** must be a mapping type
3221+
--> src/mdtest_snippet.py:11:25
3222+
|
3223+
11 | takes_name_kwargs(**bad)
3224+
| ^^^ Found `NotAMapping`
3225+
|
3226+
```
3227+
31983228
### Explicit keywords still conflict with maybe-present unpacked keys
31993229

32003230
If a partial `TypedDict` may provide a key, passing that key explicitly still counts as a duplicate.

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4159,30 +4159,10 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> {
41594159
return;
41604160
}
41614161

4162-
if let Some((parameter_index, parameter)) = self.parameters.unpacked() {
4163-
let permissive_string_mapping = argument_type.is_some_and(|argument_type| {
4164-
let Some((key_ty, _value_ty)) = argument_type.unpack_keys_and_items(db) else {
4165-
return false;
4166-
};
4167-
4168-
key_ty.is_assignable_to(db, KnownClass::Str.to_instance(db))
4169-
});
4170-
4171-
if !permissive_string_mapping {
4172-
self.errors.push(BindingError::InvalidArgumentType {
4173-
parameter: ParameterContext {
4174-
name: Some(parameter.display_name()),
4175-
index: parameter_index,
4176-
positional: false,
4177-
},
4178-
argument_index: self.get_argument_index(argument_index),
4179-
expected_ty: parameter.annotated_type(),
4180-
provided_ty: argument_type.unwrap_or_else(Type::unknown),
4181-
});
4182-
return;
4183-
}
4184-
}
4185-
4162+
// Fall back to ordinary `**kwargs` binding when the unpacked value is not TypedDict-shaped.
4163+
// This mirrors the non-`Unpack[TypedDict]` path: later validation still reports invalid
4164+
// `**` arguments, while binding here suppresses redundant missing-argument cascades and
4165+
// preserves per-parameter value checking for string-keyed mappings.
41864166
for (parameter_index, parameter) in self.parameters.iter().enumerate() {
41874167
if self.parameter_info[parameter_index]
41884168
.match_state

crates/ty_python_semantic/src/types/signatures.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2515,14 +2515,6 @@ impl<'db> Unpacked<'db> {
25152515
}
25162516
}
25172517

2518-
pub(crate) fn display_name(&self) -> Name {
2519-
Name::new(format!("**{}", self.name))
2520-
}
2521-
2522-
pub(crate) fn annotated_type(&self) -> Type<'db> {
2523-
self.annotated_type
2524-
}
2525-
25262518
fn apply_type_mapping_impl<'a>(
25272519
&self,
25282520
db: &'db dyn Db,
@@ -3102,13 +3094,6 @@ impl<'db> Parameters<'db> {
31023094
.enumerate()
31033095
.rfind(|(_, parameter)| parameter.is_keyword_variadic())
31043096
}
3105-
3106-
/// Return the retained `**kwargs: Unpack[TypedDict]` metadata and its index, if any.
3107-
pub(crate) fn unpacked(&self) -> Option<(usize, &Unpacked<'db>)> {
3108-
self.unpacked
3109-
.as_ref()
3110-
.map(|parameter| (self.value.len(), parameter))
3111-
}
31123097
}
31133098

31143099
impl<'db, 'a> IntoIterator for &'a Parameters<'db> {

0 commit comments

Comments
 (0)