ruff/crates/ruff_python_parser/src/parser/recovery.rs
2024-07-01 10:06:02 +02:00

168 lines
6.6 KiB
Rust

use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, Expr, ExprContext, Pattern};
use ruff_text_size::{Ranged, TextLen, TextRange};
/// Convert the given [`Pattern`] to an [`Expr`].
///
/// This is used to convert an invalid use of pattern to their equivalent expression
/// to preserve the structure of the pattern.
///
/// The conversion is done as follows:
/// - `PatternMatchSingleton`: Boolean and None literals
/// - `PatternMatchValue`: The value itself
/// - `PatternMatchSequence`: List literal
/// - `PatternMatchMapping`: Dictionary literal
/// - `PatternMatchClass`: Call expression
/// - `PatternMatchStar`: Starred expression
/// - `PatternMatchAs`: The pattern itself or the name
/// - `PatternMatchOr`: Binary expression with `|` operator
///
/// Note that the sequence pattern is always converted to a list literal even
/// if it was surrounded by parentheses.
///
/// # Note
///
/// This function returns an invalid [`ast::ExprName`] if the given pattern is a [`Pattern::MatchAs`]
/// with both the pattern and name present. This is because it cannot be converted to an expression
/// without dropping one of them as there's no way to represent `x as y` as a valid expression.
pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr {
match pattern {
Pattern::MatchSingleton(ast::PatternMatchSingleton { range, value }) => match value {
ast::Singleton::True => {
Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, range })
}
ast::Singleton::False => Expr::BooleanLiteral(ast::ExprBooleanLiteral {
value: false,
range,
}),
ast::Singleton::None => Expr::NoneLiteral(ast::ExprNoneLiteral { range }),
},
Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => *value,
// We don't know which kind of sequence this is: `case [1, 2]:` or `case (1, 2):`.
Pattern::MatchSequence(ast::PatternMatchSequence { range, patterns }) => {
Expr::List(ast::ExprList {
elts: patterns.into_iter().map(pattern_to_expr).collect(),
ctx: ExprContext::Store,
range,
})
}
Pattern::MatchMapping(ast::PatternMatchMapping {
range,
keys,
patterns,
rest,
}) => {
let mut items: Vec<ast::DictItem> = keys
.into_iter()
.zip(patterns)
.map(|(key, pattern)| ast::DictItem {
key: Some(key),
value: pattern_to_expr(pattern),
})
.collect();
if let Some(rest) = rest {
let value = Expr::Name(ast::ExprName {
range: rest.range,
id: rest.id,
ctx: ExprContext::Store,
});
items.push(ast::DictItem { key: None, value });
}
Expr::Dict(ast::ExprDict { range, items })
}
Pattern::MatchClass(ast::PatternMatchClass {
range,
cls,
arguments,
}) => Expr::Call(ast::ExprCall {
range,
func: cls,
arguments: ast::Arguments {
range: arguments.range,
args: arguments
.patterns
.into_iter()
.map(pattern_to_expr)
.collect(),
keywords: arguments
.keywords
.into_iter()
.map(|keyword_pattern| ast::Keyword {
range: keyword_pattern.range,
arg: Some(keyword_pattern.attr),
value: pattern_to_expr(keyword_pattern.pattern),
})
.collect(),
},
}),
Pattern::MatchStar(ast::PatternMatchStar { range, name }) => {
if let Some(name) = name {
Expr::Starred(ast::ExprStarred {
range,
value: Box::new(Expr::Name(ast::ExprName {
range: name.range,
id: name.id,
ctx: ExprContext::Store,
})),
ctx: ExprContext::Store,
})
} else {
Expr::Starred(ast::ExprStarred {
range,
value: Box::new(Expr::Name(ast::ExprName {
range: TextRange::new(range.end() - "_".text_len(), range.end()),
id: Name::new_static("_"),
ctx: ExprContext::Store,
})),
ctx: ExprContext::Store,
})
}
}
Pattern::MatchAs(ast::PatternMatchAs {
range,
pattern,
name,
}) => match (pattern, name) {
(Some(_), Some(_)) => Expr::Name(ast::ExprName {
range,
id: Name::empty(),
ctx: ExprContext::Invalid,
}),
(Some(pattern), None) => pattern_to_expr(*pattern),
(None, Some(name)) => Expr::Name(ast::ExprName {
range: name.range,
id: name.id,
ctx: ExprContext::Store,
}),
(None, None) => Expr::Name(ast::ExprName {
range,
id: Name::new_static("_"),
ctx: ExprContext::Store,
}),
},
Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => {
let to_bin_expr = |left: Pattern, right: Pattern| ast::ExprBinOp {
range: TextRange::new(left.start(), right.end()),
left: Box::new(pattern_to_expr(left)),
op: ast::Operator::BitOr,
right: Box::new(pattern_to_expr(right)),
};
let mut iter = patterns.into_iter();
match (iter.next(), iter.next()) {
(Some(left), Some(right)) => {
Expr::BinOp(iter.fold(to_bin_expr(left, right), |expr_bin_op, pattern| {
ast::ExprBinOp {
range: TextRange::new(expr_bin_op.start(), pattern.end()),
left: Box::new(Expr::BinOp(expr_bin_op)),
op: ast::Operator::BitOr,
right: Box::new(pattern_to_expr(pattern)),
}
}))
}
_ => unreachable!("Or patterns can only be formed with at least two patterns."),
}
}
}
}