Skip to content

Commit a494065

Browse files
authored
[ty] allow if statements in TypedDict bodies (#24702)
1 parent 8653efd commit a494065

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

crates/ty_python_semantic/resources/mdtest/typed_dict.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4335,6 +4335,50 @@ class Baz(Bar):
43354335
pass
43364336
```
43374337

4338+
## Conditional fields in class body
4339+
4340+
Conditional branches in a `TypedDict` body can declare fields. Static reachability determines
4341+
whether those fields are part of the schema.
4342+
4343+
### Python 3.12 or later
4344+
4345+
```toml
4346+
[environment]
4347+
python-version = "3.12"
4348+
```
4349+
4350+
```py
4351+
import sys
4352+
from typing import TypedDict
4353+
4354+
class ConditionalField(TypedDict):
4355+
x: int
4356+
if sys.version_info >= (3, 12):
4357+
y: str
4358+
4359+
ConditionalField(x=1, y="hello")
4360+
```
4361+
4362+
### Python before 3.12
4363+
4364+
```toml
4365+
[environment]
4366+
python-version = "3.11"
4367+
```
4368+
4369+
```py
4370+
import sys
4371+
from typing import TypedDict
4372+
4373+
class ConditionalField(TypedDict):
4374+
x: int
4375+
if sys.version_info >= (3, 12):
4376+
y: str
4377+
4378+
# error: [invalid-key] "Unknown key "y" for TypedDict `ConditionalField`"
4379+
ConditionalField(x=1, y="hello")
4380+
```
4381+
43384382
## `TypedDict` with `@dataclass` decorator
43394383

43404384
Applying `@dataclass` to a `TypedDict` class is conceptually incoherent: `TypedDict` defines

crates/ty_python_semantic/src/types/infer/builder/post_inference/typed_dict.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,14 @@ fn validate_typed_dict_class_body(context: &InferContext<'_, '_>, class_node: &a
3636
// may also contain a docstring or pass statements (primarily to allow the creation
3737
// of an empty `TypedDict`). No other statements are allowed, and type checkers
3838
// should report an error if any are present.
39-
for stmt in &class_node.body {
39+
validate_typed_dict_class_body_statements(context, &class_node.body);
40+
}
41+
42+
fn validate_typed_dict_class_body_statements(
43+
context: &InferContext<'_, '_>,
44+
statements: &[ast::Stmt],
45+
) {
46+
for stmt in statements {
4047
match stmt {
4148
// Annotated assignments are allowed (that's the whole point), but they're
4249
// not allowed to have a value.
@@ -52,6 +59,14 @@ fn validate_typed_dict_class_body(context: &InferContext<'_, '_>, class_node: &a
5259
}
5360
// Pass statements are allowed.
5461
ast::Stmt::Pass(_) => continue,
62+
// If statements are allowed; the body statements must validate.
63+
ast::Stmt::If(if_stmt) => {
64+
validate_typed_dict_class_body_statements(context, &if_stmt.body);
65+
for elif_else_clause in &if_stmt.elif_else_clauses {
66+
validate_typed_dict_class_body_statements(context, &elif_else_clause.body);
67+
}
68+
continue;
69+
}
5570
ast::Stmt::Expr(expr) => {
5671
// Docstrings are allowed.
5772
if matches!(*expr.value, ast::Expr::StringLiteral(_)) {

0 commit comments

Comments
 (0)