[ty] Track dictionary literal keys as individual places#22882
[ty] Track dictionary literal keys as individual places#22882ibraheemdev merged 4 commits intomainfrom
Conversation
Typing conformance resultsNo changes detected ✅ |
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-assignment |
4 | 135 | 2 |
invalid-argument-type |
8 | 51 | 3 |
possibly-missing-attribute |
0 | 45 | 5 |
not-subscriptable |
0 | 27 | 0 |
unsupported-operator |
0 | 18 | 2 |
unused-ignore-comment |
14 | 0 | 0 |
invalid-return-type |
0 | 4 | 4 |
unresolved-attribute |
0 | 6 | 0 |
no-matching-overload |
0 | 2 | 0 |
possibly-unresolved-reference |
0 | 2 | 0 |
type-assertion-failure |
2 | 0 | 0 |
not-iterable |
0 | 1 | 0 |
redundant-cast |
1 | 0 | 0 |
| Total | 29 | 291 | 16 |
| Some(CurrentAssignment::AnnAssign(ann_assign)) => { | ||
| self.add_standalone_type_expression(&ann_assign.annotation); | ||
| self.add_definition( | ||
| let assignment = self.add_definition( |
There was a problem hiding this comment.
Note that during semantic indexing, we don't have a way of distinguishing between an annotated or unannotated assignment, e.g., x: dict[str, Any]; x = {}, so we perform the narrowing in both cases. We also perform the narrowing on TypedDict assignments for the same reason.
carljm
left a comment
There was a problem hiding this comment.
This is fantastic! Code makes sense, looks great; ecosystem report is all good news. Kinda surprised there's no perf impact, but I'll take it!
|
Hmm.. it looks like this fails to consider mutations through the dictionary object, e.g., def f(a: int, b: str): ...
x = { "a": 1, "b": "2" }
x.pop("b")
f(x["a"], x["b"]) # okBut we have similar behavior for other attribute narrowing, is this a known issue? class X:
y: str | None
def evil(x: X):
x.y = None
def _(x: X):
if not x.y is None:
evil(x)
assert_type(x.y, str) # ok |
|
Yes, that's a known issue with attribute/subscript narrowing in general, and shared by all type checkers (for those cases where they do attribute/subscript narrowing). I don't think there is any way around it in general (without giving up very-useful-in-practice narrowing). The problem with any fully sound approach is that there are so many different ways in Python that arbitrary code can implicitly be executed, that ultimately you'd almost always be forced to discard the narrowing almost immediately. We could attempt to handle some common "obvious" cases. |
Track every key-value assignment in a dictionary literal as a place, such that later accesses to individual keys can be narrowed to a more specific type. For example:
See astral-sh/ty#1248 for details.