mirror of https://github.com/astral-sh/ruff
Do not fix `NamedTuple` calls containing both a list of fields and keywords (#5799)
## Summary Fixes #5794 ## Test Plan Existing tests
This commit is contained in:
parent
ae431df146
commit
cfec636046
|
|
@ -4,23 +4,9 @@ import typing
|
||||||
# with complex annotations
|
# with complex annotations
|
||||||
MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
||||||
|
|
||||||
# with default values as list
|
|
||||||
MyType = NamedTuple(
|
|
||||||
"MyType",
|
|
||||||
[("a", int), ("b", str), ("c", list[bool])],
|
|
||||||
defaults=["foo", [True]],
|
|
||||||
)
|
|
||||||
|
|
||||||
# with namespace
|
# with namespace
|
||||||
MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
||||||
|
|
||||||
# too many default values (OK)
|
|
||||||
MyType = NamedTuple(
|
|
||||||
"MyType",
|
|
||||||
[("a", int), ("b", str)],
|
|
||||||
defaults=[1, "bar", "baz"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# invalid identifiers (OK)
|
# invalid identifiers (OK)
|
||||||
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
||||||
|
|
||||||
|
|
@ -29,3 +15,10 @@ MyType = typing.NamedTuple("MyType")
|
||||||
|
|
||||||
# empty fields
|
# empty fields
|
||||||
MyType = typing.NamedTuple("MyType", [])
|
MyType = typing.NamedTuple("MyType", [])
|
||||||
|
|
||||||
|
# keywords
|
||||||
|
MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
|
||||||
|
|
||||||
|
# unfixable
|
||||||
|
MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
|
||||||
|
MyType = typing.NamedTuple("MyType", [("a", int)], b=str)
|
||||||
|
|
|
||||||
|
|
@ -93,11 +93,7 @@ fn match_named_tuple_assign<'a>(
|
||||||
|
|
||||||
/// Generate a `Stmt::AnnAssign` representing the provided property
|
/// Generate a `Stmt::AnnAssign` representing the provided property
|
||||||
/// definition.
|
/// definition.
|
||||||
fn create_property_assignment_stmt(
|
fn create_property_assignment_stmt(property: &str, annotation: &Expr) -> Stmt {
|
||||||
property: &str,
|
|
||||||
annotation: &Expr,
|
|
||||||
value: Option<&Expr>,
|
|
||||||
) -> Stmt {
|
|
||||||
ast::StmtAnnAssign {
|
ast::StmtAnnAssign {
|
||||||
target: Box::new(
|
target: Box::new(
|
||||||
ast::ExprName {
|
ast::ExprName {
|
||||||
|
|
@ -108,40 +104,15 @@ fn create_property_assignment_stmt(
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
annotation: Box::new(annotation.clone()),
|
annotation: Box::new(annotation.clone()),
|
||||||
value: value.map(|value| Box::new(value.clone())),
|
value: None,
|
||||||
simple: true,
|
simple: true,
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Match the `defaults` keyword in a `NamedTuple(...)` call.
|
/// Create a list of property assignments from the `NamedTuple` fields argument.
|
||||||
fn match_defaults(keywords: &[Keyword]) -> Result<&[Expr]> {
|
fn create_properties_from_fields_arg(fields: &Expr) -> Result<Vec<Stmt>> {
|
||||||
let defaults = keywords.iter().find(|keyword| {
|
|
||||||
if let Some(arg) = &keyword.arg {
|
|
||||||
arg == "defaults"
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
match defaults {
|
|
||||||
Some(defaults) => match &defaults.value {
|
|
||||||
Expr::List(ast::ExprList { elts, .. }) => Ok(elts),
|
|
||||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => Ok(elts),
|
|
||||||
_ => bail!("Expected defaults to be `Expr::List` | `Expr::Tuple`"),
|
|
||||||
},
|
|
||||||
None => Ok(&[]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
let node = Stmt::Pass(ast::StmtPass {
|
|
||||||
range: TextRange::default(),
|
|
||||||
});
|
|
||||||
return Ok(vec![node]);
|
|
||||||
};
|
|
||||||
let Expr::List(ast::ExprList { elts, .. }) = &fields else {
|
let Expr::List(ast::ExprList { elts, .. }) = &fields else {
|
||||||
bail!("Expected argument to be `Expr::List`");
|
bail!("Expected argument to be `Expr::List`");
|
||||||
};
|
};
|
||||||
|
|
@ -151,16 +122,8 @@ fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<S
|
||||||
});
|
});
|
||||||
return Ok(vec![node]);
|
return Ok(vec![node]);
|
||||||
}
|
}
|
||||||
let padded_defaults = if elts.len() >= defaults.len() {
|
|
||||||
std::iter::repeat(None)
|
|
||||||
.take(elts.len() - defaults.len())
|
|
||||||
.chain(defaults.iter().map(Some))
|
|
||||||
} else {
|
|
||||||
bail!("Defaults must be `None` or an iterable of at least the number of fields")
|
|
||||||
};
|
|
||||||
elts.iter()
|
elts.iter()
|
||||||
.zip(padded_defaults)
|
.map(|field| {
|
||||||
.map(|(field, default)| {
|
|
||||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &field else {
|
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &field else {
|
||||||
bail!("Expected `field` to be `Expr::Tuple`")
|
bail!("Expected `field` to be `Expr::Tuple`")
|
||||||
};
|
};
|
||||||
|
|
@ -180,9 +143,21 @@ fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<S
|
||||||
if is_dunder(property) {
|
if is_dunder(property) {
|
||||||
bail!("Cannot use dunder property name: {}", property)
|
bail!("Cannot use dunder property name: {}", property)
|
||||||
}
|
}
|
||||||
Ok(create_property_assignment_stmt(
|
Ok(create_property_assignment_stmt(property, annotation))
|
||||||
property, annotation, default,
|
})
|
||||||
))
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a list of property assignments from the `NamedTuple` keyword arguments.
|
||||||
|
fn create_properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
|
||||||
|
keywords
|
||||||
|
.iter()
|
||||||
|
.map(|keyword| {
|
||||||
|
let Keyword { arg, value, .. } = keyword;
|
||||||
|
let Some(arg) = arg else {
|
||||||
|
bail!("Expected `keyword` to have an `arg`")
|
||||||
|
};
|
||||||
|
Ok(create_property_assignment_stmt(arg.as_str(), value))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
@ -228,12 +203,32 @@ pub(crate) fn convert_named_tuple_functional_to_class(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let properties = match match_defaults(keywords)
|
let properties = match (&args[1..], keywords) {
|
||||||
.and_then(|defaults| create_properties_from_args(args, defaults))
|
// Ex) NamedTuple("MyType")
|
||||||
{
|
([], []) => vec![Stmt::Pass(ast::StmtPass {
|
||||||
Ok(properties) => properties,
|
range: TextRange::default(),
|
||||||
Err(err) => {
|
})],
|
||||||
debug!("Skipping `NamedTuple` \"{typename}\": {err}");
|
// Ex) NamedTuple("MyType", [("a", int), ("b", str)])
|
||||||
|
([fields], []) => {
|
||||||
|
if let Ok(properties) = create_properties_from_fields_arg(fields) {
|
||||||
|
properties
|
||||||
|
} else {
|
||||||
|
debug!("Skipping `NamedTuple` \"{typename}\": unable to parse fields");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ex) NamedTuple("MyType", a=int, b=str)
|
||||||
|
([], keywords) => {
|
||||||
|
if let Ok(properties) = create_properties_from_keywords(keywords) {
|
||||||
|
properties
|
||||||
|
} else {
|
||||||
|
debug!("Skipping `NamedTuple` \"{typename}\": unable to parse keywords");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Unfixable
|
||||||
|
_ => {
|
||||||
|
debug!("Skipping `NamedTuple` \"{typename}\": mixed fields and keywords");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ UP014.py:5:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class s
|
||||||
5 | MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
5 | MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
||||||
6 |
|
6 |
|
||||||
7 | # with default values as list
|
7 | # with namespace
|
||||||
|
|
|
|
||||||
= help: Convert `MyType` to class syntax
|
= help: Convert `MyType` to class syntax
|
||||||
|
|
||||||
|
|
@ -20,97 +20,93 @@ UP014.py:5:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class s
|
||||||
6 |+ a: int
|
6 |+ a: int
|
||||||
7 |+ b: tuple[str, ...]
|
7 |+ b: tuple[str, ...]
|
||||||
6 8 |
|
6 8 |
|
||||||
7 9 | # with default values as list
|
7 9 | # with namespace
|
||||||
8 10 | MyType = NamedTuple(
|
8 10 | MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
||||||
|
|
||||||
UP014.py:8:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
UP014.py:8:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
||||||
|
|
|
|
||||||
7 | # with default values as list
|
7 | # with namespace
|
||||||
8 | / MyType = NamedTuple(
|
8 | MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
||||||
9 | | "MyType",
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
||||||
10 | | [("a", int), ("b", str), ("c", list[bool])],
|
9 |
|
||||||
11 | | defaults=["foo", [True]],
|
10 | # invalid identifiers (OK)
|
||||||
12 | | )
|
|
||||||
| |_^ UP014
|
|
||||||
13 |
|
|
||||||
14 | # with namespace
|
|
||||||
|
|
|
|
||||||
= help: Convert `MyType` to class syntax
|
= help: Convert `MyType` to class syntax
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
5 5 | MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
5 5 | MyType = NamedTuple("MyType", [("a", int), ("b", tuple[str, ...])])
|
||||||
6 6 |
|
6 6 |
|
||||||
7 7 | # with default values as list
|
7 7 | # with namespace
|
||||||
8 |-MyType = NamedTuple(
|
8 |-MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
||||||
9 |- "MyType",
|
8 |+class MyType(typing.NamedTuple):
|
||||||
10 |- [("a", int), ("b", str), ("c", list[bool])],
|
|
||||||
11 |- defaults=["foo", [True]],
|
|
||||||
12 |-)
|
|
||||||
8 |+class MyType(NamedTuple):
|
|
||||||
9 |+ a: int
|
9 |+ a: int
|
||||||
10 |+ b: str = "foo"
|
10 |+ b: str
|
||||||
11 |+ c: list[bool] = [True]
|
9 11 |
|
||||||
13 12 |
|
10 12 | # invalid identifiers (OK)
|
||||||
14 13 | # with namespace
|
11 13 | MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
||||||
15 14 | MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
|
||||||
|
|
||||||
UP014.py:15:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
UP014.py:14:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
||||||
|
|
|
|
||||||
14 | # with namespace
|
13 | # no fields
|
||||||
15 | MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
14 | MyType = typing.NamedTuple("MyType")
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
|
||||||
16 |
|
|
||||||
17 | # too many default values (OK)
|
|
||||||
|
|
|
||||||
= help: Convert `MyType` to class syntax
|
|
||||||
|
|
||||||
ℹ Suggested fix
|
|
||||||
12 12 | )
|
|
||||||
13 13 |
|
|
||||||
14 14 | # with namespace
|
|
||||||
15 |-MyType = typing.NamedTuple("MyType", [("a", int), ("b", str)])
|
|
||||||
15 |+class MyType(typing.NamedTuple):
|
|
||||||
16 |+ a: int
|
|
||||||
17 |+ b: str
|
|
||||||
16 18 |
|
|
||||||
17 19 | # too many default values (OK)
|
|
||||||
18 20 | MyType = NamedTuple(
|
|
||||||
|
|
||||||
UP014.py:28:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
|
||||||
|
|
|
||||||
27 | # no fields
|
|
||||||
28 | MyType = typing.NamedTuple("MyType")
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
||||||
29 |
|
15 |
|
||||||
30 | # empty fields
|
16 | # empty fields
|
||||||
|
|
|
|
||||||
= help: Convert `MyType` to class syntax
|
= help: Convert `MyType` to class syntax
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
25 25 | MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
11 11 | MyType = NamedTuple("MyType", [("x-y", int), ("b", tuple[str, ...])])
|
||||||
26 26 |
|
12 12 |
|
||||||
27 27 | # no fields
|
13 13 | # no fields
|
||||||
28 |-MyType = typing.NamedTuple("MyType")
|
14 |-MyType = typing.NamedTuple("MyType")
|
||||||
28 |+class MyType(typing.NamedTuple):
|
14 |+class MyType(typing.NamedTuple):
|
||||||
29 |+ pass
|
15 |+ pass
|
||||||
29 30 |
|
15 16 |
|
||||||
30 31 | # empty fields
|
16 17 | # empty fields
|
||||||
31 32 | MyType = typing.NamedTuple("MyType", [])
|
17 18 | MyType = typing.NamedTuple("MyType", [])
|
||||||
|
|
||||||
UP014.py:31:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
UP014.py:17:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
||||||
|
|
|
|
||||||
30 | # empty fields
|
16 | # empty fields
|
||||||
31 | MyType = typing.NamedTuple("MyType", [])
|
17 | MyType = typing.NamedTuple("MyType", [])
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
||||||
|
18 |
|
||||||
|
19 | # keywords
|
||||||
|
|
|
|
||||||
= help: Convert `MyType` to class syntax
|
= help: Convert `MyType` to class syntax
|
||||||
|
|
||||||
ℹ Suggested fix
|
ℹ Suggested fix
|
||||||
28 28 | MyType = typing.NamedTuple("MyType")
|
14 14 | MyType = typing.NamedTuple("MyType")
|
||||||
29 29 |
|
15 15 |
|
||||||
30 30 | # empty fields
|
16 16 | # empty fields
|
||||||
31 |-MyType = typing.NamedTuple("MyType", [])
|
17 |-MyType = typing.NamedTuple("MyType", [])
|
||||||
31 |+class MyType(typing.NamedTuple):
|
17 |+class MyType(typing.NamedTuple):
|
||||||
32 |+ pass
|
18 |+ pass
|
||||||
|
18 19 |
|
||||||
|
19 20 | # keywords
|
||||||
|
20 21 | MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
|
||||||
|
|
||||||
|
UP014.py:20:1: UP014 [*] Convert `MyType` from `NamedTuple` functional to class syntax
|
||||||
|
|
|
||||||
|
19 | # keywords
|
||||||
|
20 | MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP014
|
||||||
|
21 |
|
||||||
|
22 | # unfixable
|
||||||
|
|
|
||||||
|
= help: Convert `MyType` to class syntax
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
17 17 | MyType = typing.NamedTuple("MyType", [])
|
||||||
|
18 18 |
|
||||||
|
19 19 | # keywords
|
||||||
|
20 |-MyType = typing.NamedTuple("MyType", a=int, b=tuple[str, ...])
|
||||||
|
20 |+class MyType(typing.NamedTuple):
|
||||||
|
21 |+ a: int
|
||||||
|
22 |+ b: tuple[str, ...]
|
||||||
|
21 23 |
|
||||||
|
22 24 | # unfixable
|
||||||
|
23 25 | MyType = typing.NamedTuple("MyType", [("a", int)], [("b", str)])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue