Avoid panic when typename is provided as a keyword argument (#6955)

## Summary

The `typename` argument to `NamedTuple` and `TypedDict` is a required
positional argument. We assumed as much, but panicked if it was provided
as a keyword argument or otherwise omitted. This PR handles the case
gracefully.

Closes https://github.com/astral-sh/ruff/issues/6953.
This commit is contained in:
Charlie Marsh 2023-08-28 17:03:18 -04:00 committed by GitHub
parent d1ad20c9ea
commit 87aa5d6b66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 29 deletions

View file

@ -22,3 +22,4 @@ 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)
MyType = typing.NamedTuple(typename="MyType", a=int, b=str)

View file

@ -72,8 +72,7 @@ fn match_named_tuple_assign<'a>(
value: &'a Expr,
semantic: &SemanticModel,
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a Expr)> {
let target = targets.get(0)?;
let Expr::Name(ast::ExprName { id: typename, .. }) = target else {
let [Expr::Name(ast::ExprName { id: typename, .. })] = targets else {
return None;
};
let Expr::Call(ast::ExprCall {
@ -209,13 +208,13 @@ pub(crate) fn convert_named_tuple_functional_to_class(
return;
};
let properties = match (&args[1..], keywords) {
let properties = match (args, keywords) {
// Ex) NamedTuple("MyType")
([], []) => vec![Stmt::Pass(ast::StmtPass {
([_typename], []) => vec![Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
})],
// Ex) NamedTuple("MyType", [("a", int), ("b", str)])
([fields], []) => {
([_typename, fields], []) => {
if let Ok(properties) = create_properties_from_fields_arg(fields) {
properties
} else {
@ -224,7 +223,7 @@ pub(crate) fn convert_named_tuple_functional_to_class(
}
}
// Ex) NamedTuple("MyType", a=int, b=str)
([], keywords) => {
([_typename], keywords) => {
if let Ok(properties) = create_properties_from_keywords(keywords) {
properties
} else {

View file

@ -71,8 +71,7 @@ fn match_typed_dict_assign<'a>(
value: &'a Expr,
semantic: &SemanticModel,
) -> Option<(&'a str, &'a Arguments, &'a Expr)> {
let target = targets.get(0)?;
let Expr::Name(ast::ExprName { id: class_name, .. }) = target else {
let [Expr::Name(ast::ExprName { id: class_name, .. })] = targets else {
return None;
};
let Expr::Call(ast::ExprCall {
@ -210,28 +209,34 @@ fn match_properties_and_total(arguments: &Arguments) -> Result<(Vec<Stmt>, Optio
// ```
// MyType = TypedDict('MyType', {'a': int, 'b': str}, a=int, b=str)
// ```
if let Some(dict) = arguments.args.get(1) {
let total = arguments.find_keyword("total");
match dict {
Expr::Dict(ast::ExprDict {
keys,
values,
range: _,
}) => Ok((properties_from_dict_literal(keys, values)?, total)),
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
..
}) => Ok((properties_from_dict_call(func, keywords)?, total)),
_ => bail!("Expected `arg` to be `Expr::Dict` or `Expr::Call`"),
match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
// Ex) `TypedDict("MyType", {"a": int, "b": str})`
([_typename, fields], [..]) => {
let total = arguments.find_keyword("total");
match fields {
Expr::Dict(ast::ExprDict {
keys,
values,
range: _,
}) => Ok((properties_from_dict_literal(keys, values)?, total)),
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { keywords, .. },
range: _,
}) => Ok((properties_from_dict_call(func, keywords)?, total)),
_ => bail!("Expected `arg` to be `Expr::Dict` or `Expr::Call`"),
}
}
} else if !arguments.keywords.is_empty() {
Ok((properties_from_keywords(&arguments.keywords)?, None))
} else {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
});
Ok((vec![node], None))
// Ex) `TypedDict("MyType")`
([_typename], []) => {
let node = Stmt::Pass(ast::StmtPass {
range: TextRange::default(),
});
Ok((vec![node], None))
}
// Ex) `TypedDict("MyType", a=int, b=str)`
([_typename], fields) => Ok((properties_from_keywords(fields)?, None)),
_ => bail!("Expected `args` to have exactly one or two elements"),
}
}