Skip to content

Commit

Permalink
Update RustPython to fix Dict.keys type (#2086)
Browse files Browse the repository at this point in the history
This PR upgrades RustPython to fix the type of `Dict.keys` to `Vec<Option<Expr>>` (see RustPython/RustPython#4449 for why this change was needed) and unblock #1884.
  • Loading branch information
harupy committed Jan 22, 2023
1 parent 36fb8f7 commit a7ce862
Show file tree
Hide file tree
Showing 18 changed files with 156 additions and 97 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.229", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
Expand Down
4 changes: 4 additions & 0 deletions resources/test/fixtures/pyupgrade/UP013.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@

# using namespace TypedDict
MyType11 = typing.TypedDict("MyType11", {"key": int})

# unpacking
c = {"c": float}
MyType12 = TypedDict("MyType1", {"a": int, "b": str, **c})
6 changes: 6 additions & 0 deletions resources/test/fixtures/pyupgrade/UP031_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
"bar %(bar)s" % {"foo": x, "bar": y}
)

bar = {"bar": y}
print(
"foo %(foo)s "
"bar %(bar)s" % {"foo": x, **bar}
)

print("%s \N{snowman}" % (a,))

print("%(foo)s \N{snowman}" % {"foo": 1})
Expand Down
6 changes: 3 additions & 3 deletions ruff_dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a87
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
ruff_cli = { path = "../ruff_cli" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
schemars = { version = "0.8.11" }
serde_json = {version="1.0.91"}
strum = { version = "0.24.1", features = ["strum_macros"] }
Expand Down
7 changes: 5 additions & 2 deletions src/ast/comparable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ pub enum ComparableExpr<'a> {
orelse: Box<ComparableExpr<'a>>,
},
Dict {
keys: Vec<ComparableExpr<'a>>,
keys: Vec<Option<ComparableExpr<'a>>>,
values: Vec<ComparableExpr<'a>>,
},
Set {
Expand Down Expand Up @@ -424,7 +424,10 @@ impl<'a> From<&'a Expr> for ComparableExpr<'a> {
orelse: orelse.into(),
},
ExprKind::Dict { keys, values } => Self::Dict {
keys: keys.iter().map(std::convert::Into::into).collect(),
keys: keys
.iter()
.map(|expr| expr.as_ref().map(std::convert::Into::into))
.collect(),
values: values.iter().map(std::convert::Into::into).collect(),
},
ExprKind::Set { elts } => Self::Set {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ where
}
ExprKind::Dict { keys, values } => values
.iter()
.chain(keys.iter())
.chain(keys.iter().flatten())
.any(|expr| any_over_expr(expr, func)),
ExprKind::Set { elts } | ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
elts.iter().any(|expr| any_over_expr(expr, func))
Expand Down
2 changes: 1 addition & 1 deletion src/ast/relocate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub fn relocate_expr(expr: &mut Expr, location: Range) {
relocate_expr(orelse, location);
}
ExprKind::Dict { keys, values } => {
for expr in keys {
for expr in keys.iter_mut().flatten() {
relocate_expr(expr, location);
}
for expr in values {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
visitor.visit_expr(orelse);
}
ExprKind::Dict { keys, values } => {
for expr in keys {
for expr in keys.iter().flatten() {
visitor.visit_expr(expr);
}
for expr in values {
Expand Down
2 changes: 1 addition & 1 deletion src/checkers/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3155,7 +3155,7 @@ where
// Ex) TypedDict("a", {"a": int})
if args.len() > 1 {
if let ExprKind::Dict { keys, values } = &args[1].node {
for key in keys {
for key in keys.iter().flatten() {
self.in_type_definition = false;
self.visit_expr(key);
self.in_type_definition = prev_in_type_definition;
Expand Down
23 changes: 13 additions & 10 deletions src/rules/pyflakes/rules/repeated_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ fn into_dictionary_key(expr: &Expr) -> Option<DictionaryKey> {
}

/// F601, F602
pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
pub fn repeated_keys(checker: &mut Checker, keys: &[Option<Expr>], values: &[Expr]) {
// Generate a map from key to (index, value).
let mut seen: FxHashMap<DictionaryKey, FxHashSet<ComparableExpr>> =
FxHashMap::with_capacity_and_hasher(keys.len(), BuildHasherDefault::default());

// Detect duplicate keys.
for (i, key) in keys.iter().enumerate() {
if let Some(key) = into_dictionary_key(key) {
if let Some(seen_values) = seen.get_mut(&key) {
match key {
let Some(key) = key else {
continue;
};
if let Some(dict_key) = into_dictionary_key(key) {
if let Some(seen_values) = seen.get_mut(&dict_key) {
match dict_key {
DictionaryKey::Constant(..) => {
if checker
.settings
Expand All @@ -46,10 +49,10 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
let is_duplicate_value = seen_values.contains(&comparable_value);
let mut diagnostic = Diagnostic::new(
violations::MultiValueRepeatedKeyLiteral(
unparse_expr(&keys[i], checker.stylist),
unparse_expr(key, checker.stylist),
is_duplicate_value,
),
Range::from_located(&keys[i]),
Range::from_located(key),
);
if is_duplicate_value {
if checker.patch(&Rule::MultiValueRepeatedKeyLiteral) {
Expand All @@ -64,7 +67,7 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
checker.diagnostics.push(diagnostic);
}
}
DictionaryKey::Variable(key) => {
DictionaryKey::Variable(dict_key) => {
if checker
.settings
.rules
Expand All @@ -74,10 +77,10 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
let is_duplicate_value = seen_values.contains(&comparable_value);
let mut diagnostic = Diagnostic::new(
violations::MultiValueRepeatedKeyVariable(
key.to_string(),
dict_key.to_string(),
is_duplicate_value,
),
Range::from_located(&keys[i]),
Range::from_located(key),
);
if is_duplicate_value {
if checker.patch(&Rule::MultiValueRepeatedKeyVariable) {
Expand All @@ -94,7 +97,7 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
}
}
} else {
seen.insert(key, FxHashSet::from_iter([(&values[i]).into()]));
seen.insert(dict_key, FxHashSet::from_iter([(&values[i]).into()]));
}
}
}
Expand Down
23 changes: 13 additions & 10 deletions src/rules/pyflakes/rules/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,24 @@ pub(crate) fn percent_format_extra_named_arguments(
if summary.num_positional > 0 {
return;
}
let ExprKind::Dict { keys, values } = &right.node else {
let ExprKind::Dict { keys, .. } = &right.node else {
return;
};
if values.len() > keys.len() {
if keys.iter().any(std::option::Option::is_none) {
return; // contains **x splat
}

let missing: Vec<&str> = keys
.iter()
.filter_map(|k| match &k.node {
// We can only check that string literals exist
ExprKind::Constant {
value: Constant::Str(value),
.filter_map(|k| match k {
Some(Expr {
node:
ExprKind::Constant {
value: Constant::Str(value),
..
},
..
} => {
}) => {
if summary.keywords.contains(value) {
None
} else {
Expand Down Expand Up @@ -138,13 +141,13 @@ pub(crate) fn percent_format_missing_arguments(
return;
}

if let ExprKind::Dict { keys, values } = &right.node {
if values.len() > keys.len() {
if let ExprKind::Dict { keys, .. } = &right.node {
if keys.iter().any(std::option::Option::is_none) {
return; // contains **x splat
}

let mut keywords = FxHashSet::default();
for key in keys {
for key in keys.iter().flatten() {
match &key.node {
ExprKind::Constant {
value: Constant::Str(value),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,18 @@ fn create_class_def_stmt(
})
}

fn properties_from_dict_literal(keys: &[Expr], values: &[Expr]) -> Result<Vec<Stmt>> {
fn properties_from_dict_literal(keys: &[Option<Expr>], values: &[Expr]) -> Result<Vec<Stmt>> {
keys.iter()
.zip(values.iter())
.map(|(key, value)| match &key.node {
ExprKind::Constant {
value: Constant::Str(property),
.map(|(key, value)| match key {
Some(Expr {
node:
ExprKind::Constant {
value: Constant::Str(property),
..
},
..
} => {
}) => {
if is_identifier(property) && !KWLIST.contains(&property.as_str()) {
Ok(create_property_assignment_stmt(property, &value.node))
} else {
Expand Down
72 changes: 39 additions & 33 deletions src/rules/pyupgrade/rules/printf_string_formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,41 +156,47 @@ fn clean_params_dictionary(checker: &mut Checker, right: &Expr) -> Option<String
let mut seen: Vec<&str> = vec![];
let mut indent = None;
for (key, value) in keys.iter().zip(values.iter()) {
if let ExprKind::Constant {
value: Constant::Str(key_string),
..
} = &key.node
{
// If the dictionary key is not a valid variable name, abort.
if !is_identifier(key_string) {
return None;
}
// If the key is a Python keyword, abort.
if KWLIST.contains(&key_string.as_str()) {
return None;
}
// If there are multiple entries of the same key, abort.
if seen.contains(&key_string.as_str()) {
return None;
}
seen.push(key_string);
let mut contents = String::new();
if is_multi_line {
if indent.is_none() {
indent = indentation(checker.locator, key);
match key {
Some(key) => {
if let ExprKind::Constant {
value: Constant::Str(key_string),
..
} = &key.node
{
// If the dictionary key is not a valid variable name, abort.
if !is_identifier(key_string) {
return None;
}
// If the key is a Python keyword, abort.
if KWLIST.contains(&key_string.as_str()) {
return None;
}
// If there are multiple entries of the same key, abort.
if seen.contains(&key_string.as_str()) {
return None;
}
seen.push(key_string);
if is_multi_line {
if indent.is_none() {
indent = indentation(checker.locator, key);
}
}

let value_string = checker
.locator
.slice_source_code_range(&Range::from_located(value));
arguments.push(format!("{key_string}={value_string}"));
} else {
// If there are any non-string keys, abort.
return None;
}
}

let value_string = checker
.locator
.slice_source_code_range(&Range::from_located(value));
contents.push_str(key_string);
contents.push('=');
contents.push_str(value_string);
arguments.push(contents);
} else {
// If there are any non-string keys, abort.
return None;
None => {
let value_string = checker
.locator
.slice_source_code_range(&Range::from_located(value));
arguments.push(format!("**{value_string}"));
}
}
}
// If we couldn't parse out key values, abort.
Expand Down
6 changes: 5 additions & 1 deletion src/rules/pyupgrade/rules/unpack_list_comprehension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ fn contains_await(expr: &Expr) -> bool {
ExprKind::IfExp { test, body, orelse } => {
contains_await(test) || contains_await(body) || contains_await(orelse)
}
ExprKind::Dict { keys, values } => keys.iter().chain(values.iter()).any(contains_await),
ExprKind::Dict { keys, values } => keys
.iter()
.flatten()
.chain(values.iter())
.any(contains_await),
ExprKind::Set { elts } => elts.iter().any(contains_await),
ExprKind::ListComp { elt, .. } => contains_await(elt),
ExprKind::SetComp { elt, .. } => contains_await(elt),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,14 @@ expression: diagnostics
row: 33
column: 53
parent: ~
- kind:
ConvertTypedDictFunctionalToClass: MyType12
location:
row: 37
column: 0
end_location:
row: 37
column: 58
fix: ~
parent: ~

0 comments on commit a7ce862

Please sign in to comment.