Skip to content

Commit

Permalink
Avoid panic when typename is provided as a keyword argument (#6955)
Browse files Browse the repository at this point in the history
## 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 #6953.
  • Loading branch information
charliermarsh authored Aug 28, 2023
1 parent d1ad20c commit 87aa5d6
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 29 deletions.
1 change: 1 addition & 0 deletions crates/ruff/resources/test/fixtures/pyupgrade/UP014.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
# 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)
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"),
}
}

Expand Down

0 comments on commit 87aa5d6

Please sign in to comment.