Skip to content

Commit e73d952

Browse files
authored
[ty] Include inferred type in invalid-key concise diagnostic for union/intersection TypedDicts (#24693)
## Summary Closes astral-sh/ty#3271 When subscripting a union or intersection of TypedDicts with an invalid key, the concise diagnostic message now includes the source expression and its full inferred type. This lets programmatic consumers of ty's output distinguish between: 1. Key invalid on one branch of a union — may be intentional (e.g. probing for a key that only exists on some branches) 2. Key truly invalid on a single-typed variable — likely a bug ### Before ``` file.py:19:14: error[invalid-key] Unknown key "foo" for TypedDict `DictB` file.py:22:14: error[invalid-key] Unknown key "bar" for TypedDict `DictA` ``` Both errors look identical in structure — there is no way to tell from concise output that line 19 involves a union type while line 22 does not. ### After ``` file.py:19:14: error[invalid-key] Unknown key "foo" for TypedDict `DictB` (`resp` has type `DictA | DictB`) file.py:22:14: error[invalid-key] Unknown key "bar" for TypedDict `DictA` ``` The source expression name is used (e.g. `resp`, `obj.data`), making the message immediately understandable. The full (non-concise) diagnostic output is unchanged — it already showed the union/intersection context via a secondary annotation. ## Test Plan - Updated 3 existing mdtest assertions in `typed_dict.md` to verify the new concise message format for union and intersection cases - All 330 tests in `ty_python_semantic` pass - Clippy clean - Manually verified concise output on the reproduction from the linked issue
1 parent 80feb29 commit e73d952

2 files changed

Lines changed: 13 additions & 6 deletions

File tree

crates/ty_python_semantic/resources/mdtest/typed_dict.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,7 +1873,7 @@ def _(
18731873

18741874
reveal_type(being["name"]) # revealed: str
18751875

1876-
# error: [invalid-key] "Unknown key "age" for TypedDict `Animal`"
1876+
# error: [invalid-key] "Unknown key "age" for TypedDict `Animal` (subscripted object has type `Person | Animal`)"
18771877
reveal_type(being["age"]) # revealed: int | None | Unknown
18781878

18791879
# error: [invalid-key]
@@ -1928,15 +1928,15 @@ def _(being: Person | Animal):
19281928
# error: [invalid-assignment] "Invalid assignment to key "name" with declared type `str` on TypedDict `Animal`: value of type `Literal[1]`"
19291929
being["name"] = 1
19301930

1931-
# error: [invalid-key] "Unknown key "leg" for TypedDict `Animal`"
1931+
# error: [invalid-key] "Unknown key "leg" for TypedDict `Animal` (subscripted object has type `Person | Animal`)"
19321932
being["leg"] = "unknown"
19331933

19341934
def _(centaur: Intersection[Person, Animal]):
19351935
centaur["name"] = "Chiron"
19361936
centaur["age"] = 100
19371937
centaur["legs"] = 4
19381938

1939-
# error: [invalid-key] "Unknown key "unknown" for TypedDict `Person`"
1939+
# error: [invalid-key] "Unknown key "unknown" for TypedDict `Person` (subscripted object has type `Person & Animal`)"
19401940
centaur["unknown"] = "value"
19411941

19421942
def _(person: Person, union_of_keys: Literal["name", "age"], unknown_value: Any):

crates/ty_python_semantic/src/types/diagnostic.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5248,9 +5248,16 @@ pub(crate) fn report_invalid_key_on_typed_dict<'db>(
52485248
));
52495249
} else {
52505250
diagnostic.set_primary_message(format_args!("Unknown key \"{key}\""));
5251-
diagnostic.set_concise_message(format_args!(
5252-
"Unknown key \"{key}\" for TypedDict `{typed_dict_name}`",
5253-
));
5251+
if let Some(full_ty) = full_object_ty {
5252+
diagnostic.set_concise_message(format_args!(
5253+
"Unknown key \"{key}\" for TypedDict `{typed_dict_name}` (subscripted object has type `{full_ty}`)",
5254+
full_ty = full_ty.display(db),
5255+
));
5256+
} else {
5257+
diagnostic.set_concise_message(format_args!(
5258+
"Unknown key \"{key}\" for TypedDict `{typed_dict_name}`",
5259+
));
5260+
}
52545261
}
52555262
}
52565263
_ => {

0 commit comments

Comments
 (0)