Introduce LiteralExpressionRef for all literals (#8339)

## Summary

This PR adds a new `LiteralExpressionRef` which wraps all of the literal
expression nodes in a single enum. This allows for a narrow type when
working exclusively with a literal node. Additionally, it also
implements a `Expr::as_literal_expr` method to return the new enum if
the expression is indeed a literal one.

A few rules have been updated to account for the new enum:
1. `redundant_literal_union`
2. `if_else_block_instead_of_dict_lookup`
3. `magic_value_comparison`

To account for the change in (2), a new `ComparableLiteral` has been
added which can be constructed from the new enum
(`ComparableLiteral::from(<LiteralExpressionRef>)`).

### Open Questions

1. The new `ComparableLiteral` can be exclusively used via the
`LiteralExpressionRef` enum. Should we remove all of the literal
variants from `ComparableExpr` and instead have a single
`ComparableExpr::Literal(ComparableLiteral)` variant instead?

## Test Plan

`cargo test`
This commit is contained in:
Dhruv Manilawala 2023-10-31 18:26:11 +05:30 committed by GitHub
parent a8d04cbd88
commit 97ae617fac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 127 additions and 48 deletions

View file

@ -644,6 +644,39 @@ pub struct ExprFString<'a> {
values: Vec<ComparableExpr<'a>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableLiteral<'a> {
None,
Ellipsis,
Bool(&'a bool),
Str { value: &'a str, unicode: &'a bool },
Bytes { value: &'a [u8] },
Number(ComparableNumber<'a>),
}
impl<'a> From<ast::LiteralExpressionRef<'a>> for ComparableLiteral<'a> {
fn from(literal: ast::LiteralExpressionRef<'a>) -> Self {
match literal {
ast::LiteralExpressionRef::NoneLiteral(_) => Self::None,
ast::LiteralExpressionRef::EllipsisLiteral(_) => Self::Ellipsis,
ast::LiteralExpressionRef::BooleanLiteral(ast::ExprBooleanLiteral {
value, ..
}) => Self::Bool(value),
ast::LiteralExpressionRef::StringLiteral(ast::ExprStringLiteral {
value,
unicode,
..
}) => Self::Str { value, unicode },
ast::LiteralExpressionRef::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
Self::Bytes { value }
}
ast::LiteralExpressionRef::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
Self::Number(value.into())
}
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ExprStringLiteral<'a> {
value: &'a str,

View file

@ -336,3 +336,28 @@ impl Ranged for ExpressionRef<'_> {
}
}
}
/// Unowned pendant to all the literal variants of [`ast::Expr`] that stores a
/// reference instead of an owned value.
#[derive(Copy, Clone, Debug, PartialEq, is_macro::Is)]
pub enum LiteralExpressionRef<'a> {
StringLiteral(&'a ast::ExprStringLiteral),
BytesLiteral(&'a ast::ExprBytesLiteral),
NumberLiteral(&'a ast::ExprNumberLiteral),
BooleanLiteral(&'a ast::ExprBooleanLiteral),
NoneLiteral(&'a ast::ExprNoneLiteral),
EllipsisLiteral(&'a ast::ExprEllipsisLiteral),
}
impl Ranged for LiteralExpressionRef<'_> {
fn range(&self) -> TextRange {
match self {
LiteralExpressionRef::StringLiteral(expression) => expression.range(),
LiteralExpressionRef::BytesLiteral(expression) => expression.range(),
LiteralExpressionRef::NumberLiteral(expression) => expression.range(),
LiteralExpressionRef::BooleanLiteral(expression) => expression.range(),
LiteralExpressionRef::NoneLiteral(expression) => expression.range(),
LiteralExpressionRef::EllipsisLiteral(expression) => expression.range(),
}
}
}

View file

@ -6,7 +6,7 @@ use std::fmt;
use std::fmt::Debug;
use std::ops::Deref;
use crate::int;
use crate::{int, LiteralExpressionRef};
use ruff_text_size::{Ranged, TextRange, TextSize};
/// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod)
@ -640,6 +640,18 @@ impl Expr {
)
}
pub fn as_literal_expr(&self) -> Option<LiteralExpressionRef<'_>> {
match self {
Expr::StringLiteral(expr) => Some(LiteralExpressionRef::StringLiteral(expr)),
Expr::BytesLiteral(expr) => Some(LiteralExpressionRef::BytesLiteral(expr)),
Expr::NumberLiteral(expr) => Some(LiteralExpressionRef::NumberLiteral(expr)),
Expr::BooleanLiteral(expr) => Some(LiteralExpressionRef::BooleanLiteral(expr)),
Expr::NoneLiteral(expr) => Some(LiteralExpressionRef::NoneLiteral(expr)),
Expr::EllipsisLiteral(expr) => Some(LiteralExpressionRef::EllipsisLiteral(expr)),
_ => None,
}
}
pub fn is_implicit_concatenated_string(&self) -> bool {
match self {
Expr::StringLiteral(ExprStringLiteral {