Refactor unary expression parsing (#11088)

## Summary

This PR refactors unary expression parsing with the following changes:
* Ability to get `OperatorPrecedence` from a unary operator (`UnaryOp`)
* Implement methods on `TokenKind`
	* Add `as_unary_operator` which returns an `Option<UnaryOp>`
* Add `as_unary_arithmetic_operator` which returns an `Option<UnaryOp>`
(used for pattern parsing)
* Rename `is_unary` to `is_unary_arithmetic_operator` (used in the
linter)

resolves: #10752 

## Test Plan

Verify that the existing test cases pass, no ecosystem changes, run the
Python based fuzzer on 3000 random inputs and run it on dozens of
open-source repositories.
This commit is contained in:
Dhruv Manilawala 2024-04-23 10:25:02 +05:30 committed by GitHub
parent 7eba967e16
commit 38d2562f41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 125 additions and 85 deletions

View file

@ -303,10 +303,21 @@ impl<'src> Parser<'src> {
context: ExpressionContext,
) -> ParsedExpr {
let start = self.node_start();
let token = self.current_token_kind();
let lhs = match self.current_token_kind() {
unary_tok @ (TokenKind::Plus | TokenKind::Minus | TokenKind::Tilde) => {
let unary_expr = self.parse_unary_expression(context);
if let Some(unary_op) = token.as_unary_operator() {
let expr = self.parse_unary_expression(unary_op, context);
if matches!(unary_op, UnaryOp::Not) {
if left_precedence > OperatorPrecedence::Not {
self.add_error(
ParseErrorType::OtherError(
"Boolean 'not' expression cannot be used here".to_string(),
),
&expr,
);
}
} else {
if left_precedence > OperatorPrecedence::PosNegBitNot
// > The power operator `**` binds less tightly than an arithmetic
// > or bitwise unary operator on its right, that is, 2**-1 is 0.5.
@ -316,36 +327,31 @@ impl<'src> Parser<'src> {
{
self.add_error(
ParseErrorType::OtherError(format!(
"Unary {unary_tok} expression cannot be used here",
"Unary '{unary_op}' expression cannot be used here",
)),
&unary_expr,
&expr,
);
}
Expr::UnaryOp(unary_expr).into()
}
TokenKind::Not => {
let unary_expr = self.parse_unary_expression(context);
if left_precedence > OperatorPrecedence::Not {
self.add_error(
ParseErrorType::OtherError(
"Boolean 'not' expression cannot be used here".to_string(),
),
&unary_expr,
);
}
Expr::UnaryOp(unary_expr).into()
}
return Expr::UnaryOp(expr).into();
}
match self.current_token_kind() {
TokenKind::Star => {
let starred_expr = self.parse_starred_expression(context);
if left_precedence > OperatorPrecedence::Initial
|| !context.is_starred_expression_allowed()
{
self.add_error(ParseErrorType::InvalidStarredExpressionUsage, &starred_expr);
}
Expr::Starred(starred_expr).into()
return Expr::Starred(starred_expr).into();
}
TokenKind::Await => {
let await_expr = self.parse_await_expression();
// `await` expressions cannot be nested
if left_precedence >= OperatorPrecedence::Await {
self.add_error(
@ -355,26 +361,31 @@ impl<'src> Parser<'src> {
&await_expr,
);
}
Expr::Await(await_expr).into()
return Expr::Await(await_expr).into();
}
TokenKind::Lambda => {
// Lambda expression isn't allowed in this context but we'll still
// parse it and report an error for better recovery.
// Lambda expression isn't allowed in this context but we'll still parse it and
// report an error for better recovery.
let lambda_expr = self.parse_lambda_expr();
self.add_error(ParseErrorType::InvalidLambdaExpressionUsage, &lambda_expr);
Expr::Lambda(lambda_expr).into()
return Expr::Lambda(lambda_expr).into();
}
TokenKind::Yield => {
let expr = self.parse_yield_expression();
if left_precedence > OperatorPrecedence::Initial
|| !context.is_yield_expression_allowed()
{
self.add_error(ParseErrorType::InvalidYieldExpressionUsage, &expr);
}
expr.into()
return expr.into();
}
_ => self.parse_atom(),
};
_ => {}
}
let lhs = self.parse_atom();
ParsedExpr {
expr: self.parse_postfix_expression(lhs.expr, start),
@ -881,20 +892,13 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations>
pub(super) fn parse_unary_expression(
&mut self,
op: UnaryOp,
context: ExpressionContext,
) -> ast::ExprUnaryOp {
let start = self.node_start();
self.bump(TokenKind::from(op));
let op = UnaryOp::try_from(self.current_token_kind())
.expect("current token should be a unary operator");
self.bump(self.current_token_kind());
let operand = if op.is_not() {
self.parse_binary_expression_or_higher(OperatorPrecedence::Not, context)
} else {
// plus, minus and tilde
self.parse_binary_expression_or_higher(OperatorPrecedence::PosNegBitNot, context)
};
let operand = self.parse_binary_expression_or_higher(OperatorPrecedence::from(op), context);
ast::ExprUnaryOp {
op,
@ -2402,6 +2406,16 @@ impl From<BoolOp> for OperatorPrecedence {
}
}
impl From<UnaryOp> for OperatorPrecedence {
#[inline]
fn from(op: UnaryOp) -> Self {
match op {
UnaryOp::Not => OperatorPrecedence::Not,
_ => OperatorPrecedence::PosNegBitNot,
}
}
}
impl From<Operator> for OperatorPrecedence {
#[inline]
fn from(op: Operator) -> Self {