Handle empty NamedTuple and TypedDict conversions (#3251)
This commit is contained in:
@@ -35,3 +35,9 @@ MyType = TypedDict("MyType", {"in": int, "x-y": int})
|
||||
# unpacking (OK)
|
||||
c = {"c": float}
|
||||
MyType = TypedDict("MyType", {"a": int, "b": str, **c})
|
||||
|
||||
# Empty dict literal
|
||||
MyType = TypedDict("MyType", {})
|
||||
|
||||
# Empty dict call
|
||||
MyType = TypedDict("MyType", dict())
|
||||
|
||||
@@ -23,3 +23,9 @@ MyType = NamedTuple(
|
||||
|
||||
# invalid identifiers (OK)
|
||||
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
||||
|
||||
# no fields
|
||||
MyType = typing.NamedTuple("MyType")
|
||||
|
||||
# empty fields
|
||||
MyType = typing.NamedTuple("MyType", [])
|
||||
|
||||
@@ -103,11 +103,14 @@ fn match_defaults(keywords: &[Keyword]) -> Result<&[Expr]> {
|
||||
/// Create a list of property assignments from the `NamedTuple` arguments.
|
||||
fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<Stmt>> {
|
||||
let Some(fields) = args.get(1) else {
|
||||
return Ok(vec![]);
|
||||
return Ok(vec![create_stmt(StmtKind::Pass)]);
|
||||
};
|
||||
let ExprKind::List { elts, .. } = &fields.node else {
|
||||
bail!("Expected argument to be `ExprKind::List`");
|
||||
};
|
||||
if elts.is_empty() {
|
||||
return Ok(vec![create_stmt(StmtKind::Pass)]);
|
||||
}
|
||||
let padded_defaults = if elts.len() >= defaults.len() {
|
||||
std::iter::repeat(None)
|
||||
.take(elts.len() - defaults.len())
|
||||
|
||||
@@ -78,11 +78,6 @@ fn create_property_assignment_stmt(property: &str, annotation: &ExprKind) -> Stm
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind::Pass` statement.
|
||||
fn create_pass_stmt() -> Stmt {
|
||||
create_stmt(StmtKind::Pass)
|
||||
}
|
||||
|
||||
/// Generate a `StmtKind:ClassDef` statement based on the provided body,
|
||||
/// keywords and base class.
|
||||
fn create_class_def_stmt(
|
||||
@@ -105,6 +100,10 @@ fn create_class_def_stmt(
|
||||
}
|
||||
|
||||
fn properties_from_dict_literal(keys: &[Option<Expr>], values: &[Expr]) -> Result<Vec<Stmt>> {
|
||||
if keys.is_empty() {
|
||||
return Ok(vec![create_stmt(StmtKind::Pass)]);
|
||||
}
|
||||
|
||||
keys.iter()
|
||||
.zip(values.iter())
|
||||
.map(|(key, value)| match key {
|
||||
@@ -134,11 +133,19 @@ fn properties_from_dict_call(func: &Expr, keywords: &[Keyword]) -> Result<Vec<St
|
||||
if id != "dict" {
|
||||
bail!("Expected `id` to be `\"dict\"`")
|
||||
}
|
||||
if keywords.is_empty() {
|
||||
return Ok(vec![create_stmt(StmtKind::Pass)]);
|
||||
}
|
||||
|
||||
properties_from_keywords(keywords)
|
||||
}
|
||||
|
||||
// Deprecated in Python 3.11, removed in Python 3.13.
|
||||
fn properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||
if keywords.is_empty() {
|
||||
return Ok(vec![create_stmt(StmtKind::Pass)]);
|
||||
}
|
||||
|
||||
keywords
|
||||
.iter()
|
||||
.map(|keyword| {
|
||||
@@ -185,12 +192,12 @@ fn match_properties_and_total<'a>(
|
||||
ExprKind::Call { func, keywords, .. } => {
|
||||
Ok((properties_from_dict_call(func, keywords)?, total))
|
||||
}
|
||||
_ => Ok((vec![create_pass_stmt()], total)),
|
||||
_ => bail!("Expected `arg` to be `ExprKind::Dict` or `ExprKind::Call`"),
|
||||
}
|
||||
} else if !keywords.is_empty() {
|
||||
Ok((properties_from_keywords(keywords)?, None))
|
||||
} else {
|
||||
Ok((vec![create_pass_stmt()], None))
|
||||
Ok((vec![create_stmt(StmtKind::Pass)], None))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,4 +192,42 @@ expression: diagnostics
|
||||
row: 30
|
||||
column: 49
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 40
|
||||
column: 0
|
||||
end_location:
|
||||
row: 40
|
||||
column: 32
|
||||
fix:
|
||||
content: "class MyType(TypedDict):\n pass"
|
||||
location:
|
||||
row: 40
|
||||
column: 0
|
||||
end_location:
|
||||
row: 40
|
||||
column: 32
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertTypedDictFunctionalToClass:
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 43
|
||||
column: 0
|
||||
end_location:
|
||||
row: 43
|
||||
column: 36
|
||||
fix:
|
||||
content: "class MyType(TypedDict):\n pass"
|
||||
location:
|
||||
row: 43
|
||||
column: 0
|
||||
end_location:
|
||||
row: 43
|
||||
column: 36
|
||||
parent: ~
|
||||
|
||||
|
||||
@@ -59,4 +59,42 @@ expression: diagnostics
|
||||
row: 15
|
||||
column: 62
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass:
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 28
|
||||
column: 0
|
||||
end_location:
|
||||
row: 28
|
||||
column: 36
|
||||
fix:
|
||||
content: "class MyType(typing.NamedTuple):\n pass"
|
||||
location:
|
||||
row: 28
|
||||
column: 0
|
||||
end_location:
|
||||
row: 28
|
||||
column: 36
|
||||
parent: ~
|
||||
- kind:
|
||||
ConvertNamedTupleFunctionalToClass:
|
||||
name: MyType
|
||||
fixable: true
|
||||
location:
|
||||
row: 31
|
||||
column: 0
|
||||
end_location:
|
||||
row: 31
|
||||
column: 40
|
||||
fix:
|
||||
content: "class MyType(typing.NamedTuple):\n pass"
|
||||
location:
|
||||
row: 31
|
||||
column: 0
|
||||
end_location:
|
||||
row: 31
|
||||
column: 40
|
||||
parent: ~
|
||||
|
||||
|
||||
Reference in New Issue
Block a user