Start working on using a bumpalo for the AST

This commit is contained in:
Micha Reiser
2024-07-20 16:47:03 +02:00
parent a3e67abf4c
commit 811dd2bb68
413 changed files with 112821 additions and 3878 deletions

View File

@@ -64,11 +64,11 @@
//! [parsing]: https://en.wikipedia.org/wiki/Parsing
//! [lexer]: crate::lexer
use std::iter::FusedIterator;
use std::ops::Deref;
pub use crate::error::{FStringErrorType, ParseError, ParseErrorType};
pub use crate::token::{Token, TokenKind};
use ruff_allocator::Allocator;
use std::iter::FusedIterator;
use std::ops::Deref;
use crate::parser::Parser;
@@ -107,8 +107,11 @@ pub mod typing;
/// let module = parse_module(source);
/// assert!(module.is_ok());
/// ```
pub fn parse_module(source: &str) -> Result<Parsed<ModModule>, ParseError> {
Parser::new(source, Mode::Module)
pub fn parse_module<'ast>(
source: &str,
allocator: &'ast Allocator,
) -> Result<Parsed<ModModule<'ast>>, ParseError> {
Parser::new(source, Mode::Module, allocator)
.parse()
.try_into_module()
.unwrap()
@@ -130,8 +133,11 @@ pub fn parse_module(source: &str) -> Result<Parsed<ModModule>, ParseError> {
/// let expr = parse_expression("1 + 2");
/// assert!(expr.is_ok());
/// ```
pub fn parse_expression(source: &str) -> Result<Parsed<ModExpression>, ParseError> {
Parser::new(source, Mode::Expression)
pub fn parse_expression<'a>(
source: &str,
allocator: &'a Allocator,
) -> Result<Parsed<ModExpression<'a>>, ParseError> {
Parser::new(source, Mode::Expression, allocator)
.parse()
.try_into_expression()
.unwrap()
@@ -154,12 +160,13 @@ pub fn parse_expression(source: &str) -> Result<Parsed<ModExpression>, ParseErro
/// let parsed = parse_expression_range("11 + 22 + 33", TextRange::new(TextSize::new(5), TextSize::new(7)));
/// assert!(parsed.is_ok());
/// ```
pub fn parse_expression_range(
pub fn parse_expression_range<'ast>(
source: &str,
range: TextRange,
) -> Result<Parsed<ModExpression>, ParseError> {
allocator: &'ast Allocator,
) -> Result<Parsed<ModExpression<'ast>>, ParseError> {
let source = &source[..range.end().to_usize()];
Parser::new_starts_at(source, Mode::Expression, range.start())
Parser::new_starts_at(source, Mode::Expression, range.start(), allocator)
.parse()
.try_into_expression()
.unwrap()
@@ -212,29 +219,41 @@ pub fn parse_expression_range(
/// let parsed = parse(source, Mode::Ipython);
/// assert!(parsed.is_ok());
/// ```
pub fn parse(source: &str, mode: Mode) -> Result<Parsed<Mod>, ParseError> {
parse_unchecked(source, mode).into_result()
pub fn parse<'ast>(
source: &str,
mode: Mode,
allocator: &'ast Allocator,
) -> Result<Parsed<Mod<'ast>>, ParseError> {
parse_unchecked(source, mode, allocator).into_result()
}
/// Parse the given Python source code using the specified [`Mode`].
///
/// This is same as the [`parse`] function except that it doesn't check for any [`ParseError`]
/// and returns the [`Parsed`] as is.
pub fn parse_unchecked(source: &str, mode: Mode) -> Parsed<Mod> {
Parser::new(source, mode).parse()
pub fn parse_unchecked<'ast>(
source: &str,
mode: Mode,
allocator: &'ast Allocator,
) -> Parsed<Mod<'ast>> {
Parser::new(source, mode, allocator).parse()
}
/// Parse the given Python source code using the specified [`PySourceType`].
pub fn parse_unchecked_source(source: &str, source_type: PySourceType) -> Parsed<ModModule> {
pub fn parse_unchecked_source<'ast>(
source: &str,
source_type: PySourceType,
allocator: &'ast Allocator,
) -> Parsed<ModModule<'ast>> {
// SAFETY: Safe because `PySourceType` always parses to a `ModModule`
Parser::new(source, source_type.as_mode())
Parser::new(source, source_type.as_mode(), allocator)
.parse()
.try_into_module()
.unwrap()
}
/// Represents the parsed source code.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug)]
pub struct Parsed<T> {
syntax: T,
tokens: Tokens,
@@ -293,7 +312,16 @@ impl<T> Parsed<T> {
}
}
impl Parsed<Mod> {
impl<T> PartialEq for Parsed<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.syntax == other.syntax && self.tokens == other.tokens && self.errors == other.errors
}
}
impl<'ast> Parsed<Mod<'ast>> {
/// Attempts to convert the [`Parsed<Mod>`] into a [`Parsed<ModModule>`].
///
/// This method checks if the `syntax` field of the output is a [`Mod::Module`]. If it is, the
@@ -301,7 +329,7 @@ impl Parsed<Mod> {
/// returns [`None`].
///
/// [`Some(Parsed<ModModule>)`]: Some
pub fn try_into_module(self) -> Option<Parsed<ModModule>> {
pub fn try_into_module(self) -> Option<Parsed<ModModule<'ast>>> {
match self.syntax {
Mod::Module(module) => Some(Parsed {
syntax: module,
@@ -319,7 +347,7 @@ impl Parsed<Mod> {
/// Otherwise, it returns [`None`].
///
/// [`Some(Parsed<ModExpression>)`]: Some
pub fn try_into_expression(self) -> Option<Parsed<ModExpression>> {
pub fn try_into_expression(self) -> Option<Parsed<ModExpression<'ast>>> {
match self.syntax {
Mod::Module(_) => None,
Mod::Expression(expression) => Some(Parsed {
@@ -331,32 +359,32 @@ impl Parsed<Mod> {
}
}
impl Parsed<ModModule> {
impl<'ast> Parsed<ModModule<'ast>> {
/// Returns the module body contained in this parsed output as a [`Suite`].
pub fn suite(&self) -> &Suite {
pub fn suite(&self) -> &Suite<'ast> {
&self.syntax.body
}
/// Consumes the [`Parsed`] output and returns the module body as a [`Suite`].
pub fn into_suite(self) -> Suite {
pub fn into_suite(self) -> Suite<'ast> {
self.syntax.body
}
}
impl Parsed<ModExpression> {
impl<'ast> Parsed<ModExpression<'ast>> {
/// Returns the expression contained in this parsed output.
pub fn expr(&self) -> &Expr {
pub fn expr(&self) -> &Expr<'ast> {
&self.syntax.body
}
/// Returns a mutable reference to the expression contained in this parsed output.
pub fn expr_mut(&mut self) -> &mut Expr {
pub fn expr_mut(&mut self) -> &mut Expr<'ast> {
&mut self.syntax.body
}
/// Consumes the [`Parsed`] output and returns the contained [`Expr`].
pub fn into_expr(self) -> Expr {
*self.syntax.body
pub fn into_expr(self) -> Expr<'ast> {
self.syntax.body
}
}

View File

@@ -106,7 +106,7 @@ pub(super) const END_EXPR_SET: TokenSet = TokenSet::new([
/// Tokens that can appear at the end of a sequence.
const END_SEQUENCE_SET: TokenSet = END_EXPR_SET.remove(TokenKind::Comma);
impl<'src> Parser<'src> {
impl<'src, 'ast> Parser<'src, 'ast> {
/// Returns `true` if the parser is at a name or keyword (including soft keyword) token.
pub(super) fn at_name_or_keyword(&self) -> bool {
self.at(TokenKind::Name) || self.current_token_kind().is_keyword()
@@ -138,7 +138,7 @@ impl<'src> Parser<'src> {
/// used to match the `star_expressions` rule.
///
/// [Python grammar]: https://docs.python.org/3/reference/grammar.html
pub(super) fn parse_expression_list(&mut self, context: ExpressionContext) -> ParsedExpr {
pub(super) fn parse_expression_list(&mut self, context: ExpressionContext) -> ParsedExpr<'ast> {
let start = self.node_start();
let parsed_expr = self.parse_conditional_expression_or_higher_impl(context);
@@ -167,7 +167,7 @@ impl<'src> Parser<'src> {
pub(super) fn parse_named_expression_or_higher(
&mut self,
context: ExpressionContext,
) -> ParsedExpr {
) -> ParsedExpr<'ast> {
let start = self.node_start();
let parsed_expr = self.parse_conditional_expression_or_higher_impl(context);
@@ -190,14 +190,14 @@ impl<'src> Parser<'src> {
/// instead of as a tuple, as done by [`Parser::parse_expression_list`] use this function.
///
/// [Python grammar]: https://docs.python.org/3/reference/grammar.html
pub(super) fn parse_conditional_expression_or_higher(&mut self) -> ParsedExpr {
pub(super) fn parse_conditional_expression_or_higher(&mut self) -> ParsedExpr<'ast> {
self.parse_conditional_expression_or_higher_impl(ExpressionContext::default())
}
pub(super) fn parse_conditional_expression_or_higher_impl(
&mut self,
context: ExpressionContext,
) -> ParsedExpr {
) -> ParsedExpr<'ast> {
if self.at(TokenKind::Lambda) {
Expr::Lambda(self.parse_lambda_expr()).into()
} else {
@@ -224,7 +224,7 @@ impl<'src> Parser<'src> {
/// specified method to allow parsing lambda expression.
///
/// [Python grammar]: https://docs.python.org/3/reference/grammar.html
fn parse_simple_expression(&mut self, context: ExpressionContext) -> ParsedExpr {
fn parse_simple_expression(&mut self, context: ExpressionContext) -> ParsedExpr<'ast> {
self.parse_binary_expression_or_higher(OperatorPrecedence::Initial, context)
}
@@ -235,7 +235,7 @@ impl<'src> Parser<'src> {
&mut self,
left_precedence: OperatorPrecedence,
context: ExpressionContext,
) -> ParsedExpr {
) -> ParsedExpr<'ast> {
let start = self.node_start();
let lhs = self.parse_lhs_expression(left_precedence, context);
self.parse_binary_expression_or_higher_recursive(lhs, left_precedence, context, start)
@@ -243,11 +243,11 @@ impl<'src> Parser<'src> {
pub(super) fn parse_binary_expression_or_higher_recursive(
&mut self,
mut left: ParsedExpr,
mut left: ParsedExpr<'ast>,
left_precedence: OperatorPrecedence,
context: ExpressionContext,
start: TextSize,
) -> ParsedExpr {
) -> ParsedExpr<'ast> {
let mut progress = ParserProgress::default();
loop {
@@ -292,9 +292,9 @@ impl<'src> Parser<'src> {
let right = self.parse_binary_expression_or_higher(new_precedence, context);
Expr::BinOp(ast::ExprBinOp {
left: Box::new(left.expr),
left: self.alloc_box(left.expr),
op: bin_op,
right: Box::new(right.expr),
right: self.alloc_box(right.expr),
range: self.node_range(start),
})
}
@@ -317,7 +317,7 @@ impl<'src> Parser<'src> {
&mut self,
left_precedence: OperatorPrecedence,
context: ExpressionContext,
) -> ParsedExpr {
) -> ParsedExpr<'ast> {
let start = self.node_start();
let token = self.current_token_kind();
@@ -416,7 +416,7 @@ impl<'src> Parser<'src> {
/// sense, it matches the `bitwise_or` rule of the [Python grammar].
///
/// [Python grammar]: https://docs.python.org/3/reference/grammar.html
fn parse_expression_with_bitwise_or_precedence(&mut self) -> ParsedExpr {
fn parse_expression_with_bitwise_or_precedence(&mut self) -> ParsedExpr<'ast> {
let parsed_expr = self.parse_conditional_expression_or_higher();
if parsed_expr.is_parenthesized {
@@ -450,7 +450,7 @@ impl<'src> Parser<'src> {
/// field will be [`ExprContext::Invalid`].
///
/// See: <https://docs.python.org/3/reference/expressions.html#atom-identifiers>
pub(super) fn parse_name(&mut self) -> ast::ExprName {
pub(super) fn parse_name(&mut self) -> ast::ExprName<'ast> {
let identifier = self.parse_identifier();
let ctx = if identifier.is_valid() {
@@ -471,20 +471,26 @@ impl<'src> Parser<'src> {
/// For an invalid identifier, the `id` field will be an empty string.
///
/// See: <https://docs.python.org/3/reference/expressions.html#atom-identifiers>
pub(super) fn parse_identifier(&mut self) -> ast::Identifier {
pub(super) fn parse_identifier(&mut self) -> ast::Identifier<'ast> {
let range = self.current_token_range();
if self.at(TokenKind::Name) {
let TokenValue::Name(name) = self.bump_value(TokenKind::Name) else {
unreachable!();
};
return ast::Identifier { id: name, range };
return ast::Identifier {
id: self.allocator.alloc_str(&name),
range,
};
}
if self.current_token_kind().is_soft_keyword() {
let id = Name::new(self.src_text(range));
self.bump_soft_keyword_as_name();
return ast::Identifier { id, range };
return ast::Identifier {
id: self.allocator.alloc_str(&id),
range,
};
}
if self.current_token_kind().is_keyword() {
@@ -497,7 +503,7 @@ impl<'src> Parser<'src> {
range,
);
let id = Name::new(self.src_text(range));
let id = self.alloc_str(self.src_text(range));
self.bump_any();
ast::Identifier { id, range }
} else {
@@ -507,7 +513,7 @@ impl<'src> Parser<'src> {
);
ast::Identifier {
id: Name::empty(),
id: "",
range: self.missing_node_range(),
}
}
@@ -516,7 +522,7 @@ impl<'src> Parser<'src> {
/// Parses an atom.
///
/// See: <https://docs.python.org/3/reference/expressions.html#atoms>
fn parse_atom(&mut self) -> ParsedExpr {
fn parse_atom(&mut self) -> ParsedExpr<'ast> {
let start = self.node_start();
let lhs = match self.current_token_kind() {
@@ -595,7 +601,7 @@ impl<'src> Parser<'src> {
);
Expr::Name(ast::ExprName {
range: self.missing_node_range(),
id: Name::empty(),
id: "",
ctx: ExprContext::Invalid,
})
}
@@ -611,7 +617,11 @@ impl<'src> Parser<'src> {
/// expression, `[` for a subscript expression, or `.` for an attribute expression.
///
/// This method does nothing if the current token is not a candidate for a postfix expression.
pub(super) fn parse_postfix_expression(&mut self, mut lhs: Expr, start: TextSize) -> Expr {
pub(super) fn parse_postfix_expression(
&mut self,
mut lhs: Expr<'ast>,
start: TextSize,
) -> Expr<'ast> {
loop {
lhs = match self.current_token_kind() {
TokenKind::Lpar => Expr::Call(self.parse_call_expression(lhs, start)),
@@ -632,11 +642,11 @@ impl<'src> Parser<'src> {
/// If the parser isn't position at a `(` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#calls>
fn parse_call_expression(&mut self, func: Expr, start: TextSize) -> ast::ExprCall {
fn parse_call_expression(&mut self, func: Expr<'ast>, start: TextSize) -> ast::ExprCall<'ast> {
let arguments = self.parse_arguments();
ast::ExprCall {
func: Box::new(func),
func: self.alloc_box(func),
arguments,
range: self.node_range(start),
}
@@ -649,12 +659,12 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `(` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-argument_list>
pub(super) fn parse_arguments(&mut self) -> ast::Arguments {
pub(super) fn parse_arguments(&mut self) -> ast::Arguments<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lpar);
let mut args = vec![];
let mut keywords = vec![];
let mut args = ruff_allocator::Vec::new_in(&self.allocator);
let mut keywords = ruff_allocator::Vec::new_in(&self.allocator);
let mut seen_keyword_argument = false; // foo = 1
let mut seen_keyword_unpacking = false; // **foo
@@ -717,7 +727,7 @@ impl<'src> Parser<'src> {
&parsed_expr,
);
ast::Identifier {
id: Name::empty(),
id: "",
range: parsed_expr.range(),
}
};
@@ -752,8 +762,8 @@ impl<'src> Parser<'src> {
let arguments = ast::Arguments {
range: self.node_range(start),
args: args.into_boxed_slice(),
keywords: keywords.into_boxed_slice(),
args: args.into_bump_slice_mut(),
keywords: keywords.into_bump_slice_mut(),
};
self.validate_arguments(&arguments);
@@ -770,9 +780,9 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#subscriptions>
fn parse_subscript_expression(
&mut self,
mut value: Expr,
mut value: Expr<'ast>,
start: TextSize,
) -> ast::ExprSubscript {
) -> ast::ExprSubscript<'ast> {
self.bump(TokenKind::Lsqb);
// To prevent the `value` context from being `Del` within a `del` statement,
@@ -788,10 +798,10 @@ impl<'src> Parser<'src> {
self.add_error(ParseErrorType::EmptySlice, slice_range);
return ast::ExprSubscript {
value: Box::new(value),
slice: Box::new(Expr::Name(ast::ExprName {
value: self.alloc_box(value),
slice: self.alloc_box(Expr::Name(ast::ExprName {
range: slice_range,
id: Name::empty(),
id: "",
ctx: ExprContext::Invalid,
})),
ctx: ExprContext::Load,
@@ -831,8 +841,8 @@ impl<'src> Parser<'src> {
self.expect(TokenKind::Rsqb);
ast::ExprSubscript {
value: Box::new(value),
slice: Box::new(slice),
value: self.alloc_box(value),
slice: self.alloc_box(slice),
ctx: ExprContext::Load,
range: self.node_range(start),
}
@@ -841,7 +851,7 @@ impl<'src> Parser<'src> {
/// Parses a slice expression.
///
/// See: <https://docs.python.org/3/reference/expressions.html#slicings>
fn parse_slice(&mut self) -> Expr {
fn parse_slice(&mut self) -> Expr<'ast> {
const UPPER_END_SET: TokenSet =
TokenSet::new([TokenKind::Comma, TokenKind::Colon, TokenKind::Rsqb])
.union(NEWLINE_EOF_SET);
@@ -876,18 +886,20 @@ impl<'src> Parser<'src> {
self.expect(TokenKind::Colon);
let lower = lower.map(Box::new);
let lower = lower.map(|lower| self.alloc_box(lower));
let upper = if self.at_ts(UPPER_END_SET) {
None
} else {
Some(Box::new(self.parse_conditional_expression_or_higher().expr))
let expression = self.parse_conditional_expression_or_higher().expr;
Some(self.alloc_box(expression))
};
let step = if self.eat(TokenKind::Colon) {
if self.at_ts(STEP_END_SET) {
None
} else {
Some(Box::new(self.parse_conditional_expression_or_higher().expr))
let expression = self.parse_conditional_expression_or_higher().expr;
Some(self.alloc_box(expression))
}
} else {
None
@@ -915,7 +927,7 @@ impl<'src> Parser<'src> {
&mut self,
op: UnaryOp,
context: ExpressionContext,
) -> ast::ExprUnaryOp {
) -> ast::ExprUnaryOp<'ast> {
let start = self.node_start();
self.bump(TokenKind::from(op));
@@ -923,7 +935,7 @@ impl<'src> Parser<'src> {
ast::ExprUnaryOp {
op,
operand: Box::new(operand.expr),
operand: self.alloc_box(operand.expr),
range: self.node_range(start),
}
}
@@ -937,15 +949,15 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#attribute-references>
pub(super) fn parse_attribute_expression(
&mut self,
value: Expr,
value: Expr<'ast>,
start: TextSize,
) -> ast::ExprAttribute {
) -> ast::ExprAttribute<'ast> {
self.bump(TokenKind::Dot);
let attr = self.parse_identifier();
ast::ExprAttribute {
value: Box::new(value),
value: self.alloc_box(value),
attr,
ctx: ExprContext::Load,
range: self.node_range(start),
@@ -964,11 +976,11 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#boolean-operations>
fn parse_boolean_expression(
&mut self,
lhs: Expr,
lhs: Expr<'ast>,
start: TextSize,
op: BoolOp,
context: ExpressionContext,
) -> ast::ExprBoolOp {
) -> ast::ExprBoolOp<'ast> {
self.bump(TokenKind::from(op));
let mut values = vec![lhs];
@@ -1030,15 +1042,16 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#comparisons>
fn parse_comparison_expression(
&mut self,
lhs: Expr,
lhs: Expr<'ast>,
start: TextSize,
op: CmpOp,
context: ExpressionContext,
) -> ast::ExprCompare {
) -> ast::ExprCompare<'ast> {
self.bump_cmp_op(op);
let mut comparators = vec![];
let mut operators = vec![op];
let mut comparators = ruff_allocator::Vec::new_in(&self.allocator);
let mut operators = ruff_allocator::Vec::new_in(&self.allocator);
operators.push(op);
let mut progress = ParserProgress::default();
@@ -1067,9 +1080,9 @@ impl<'src> Parser<'src> {
}
ast::ExprCompare {
left: Box::new(lhs),
ops: operators.into_boxed_slice(),
comparators: comparators.into_boxed_slice(),
left: self.alloc_box(lhs),
ops: operators.into_bump_slice_mut(),
comparators: comparators.into_bump_slice_mut(),
range: self.node_range(start),
}
}
@@ -1081,7 +1094,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `String` or `FStringStart` token.
///
/// See: <https://docs.python.org/3/reference/grammar.html> (Search "strings:")
pub(super) fn parse_strings(&mut self) -> Expr {
pub(super) fn parse_strings(&mut self) -> Expr<'ast> {
const STRING_START_SET: TokenSet =
TokenSet::new([TokenKind::String, TokenKind::FStringStart]);
@@ -1132,9 +1145,9 @@ impl<'src> Parser<'src> {
/// If the length of `strings` is less than 2.
fn handle_implicitly_concatenated_strings(
&mut self,
strings: Vec<StringType>,
strings: Vec<StringType<'ast>>,
range: TextRange,
) -> Expr {
) -> Expr<'ast> {
assert!(strings.len() > 1);
let mut has_fstring = false;
@@ -1251,7 +1264,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `String` token.
///
/// See: <https://docs.python.org/3.13/reference/lexical_analysis.html#string-and-bytes-literals>
fn parse_string_or_byte_literal(&mut self) -> StringType {
fn parse_string_or_byte_literal(&mut self) -> StringType<'ast> {
let range = self.current_token_range();
let flags = self.tokens.current_flags().as_any_string_flags();
@@ -1259,7 +1272,7 @@ impl<'src> Parser<'src> {
unreachable!()
};
match parse_string_literal(value, flags, range) {
match parse_string_literal(&value, flags, range, self.allocator) {
Ok(string) => string,
Err(error) => {
let location = error.location();
@@ -1271,7 +1284,7 @@ impl<'src> Parser<'src> {
// rb"a𝐁c123"
// b"""123a𝐁c"""
StringType::Bytes(ast::BytesLiteral {
value: Box::new([]),
value: &[],
range,
flags: ast::BytesLiteralFlags::from(flags).with_invalid(),
})
@@ -1299,7 +1312,7 @@ impl<'src> Parser<'src> {
///
/// See: <https://docs.python.org/3/reference/grammar.html> (Search "fstring:")
/// See: <https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals>
fn parse_fstring(&mut self) -> ast::FString {
fn parse_fstring(&mut self) -> ast::FString<'ast> {
let start = self.node_start();
let flags = self.tokens.current_flags().as_any_string_flags();
@@ -1324,7 +1337,7 @@ impl<'src> Parser<'src> {
&mut self,
flags: ast::AnyStringFlags,
kind: FStringElementsKind,
) -> FStringElements {
) -> FStringElements<'ast> {
let mut elements = vec![];
self.parse_list(RecoveryContextKind::FStringElements(kind), |parser| {
@@ -1340,8 +1353,8 @@ impl<'src> Parser<'src> {
unreachable!()
};
FStringElement::Literal(
parse_fstring_literal_element(value, flags, range).unwrap_or_else(
|lex_error| {
parse_fstring_literal_element(&value, flags, range, self.allocator)
.unwrap_or_else(|lex_error| {
// test_err invalid_fstring_literal_element
// f'hello \N{INVALID} world'
// f"""hello \N{INVALID} world"""
@@ -1354,8 +1367,7 @@ impl<'src> Parser<'src> {
value: "".into(),
range,
}
},
),
}),
)
}
// `Invalid` tokens are created when there's a lexical error, so
@@ -1388,7 +1400,7 @@ impl<'src> Parser<'src> {
fn parse_fstring_expression_element(
&mut self,
flags: ast::AnyStringFlags,
) -> ast::FStringExpressionElement {
) -> ast::FStringExpressionElement<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lbrace);
@@ -1465,7 +1477,7 @@ impl<'src> Parser<'src> {
let format_spec = if self.eat(TokenKind::Colon) {
let spec_start = self.node_start();
let elements = self.parse_fstring_elements(flags, FStringElementsKind::FormatSpec);
Some(Box::new(ast::FStringFormatSpec {
Some(self.alloc_box(ast::FStringFormatSpec {
range: self.node_range(spec_start),
elements,
}))
@@ -1498,7 +1510,7 @@ impl<'src> Parser<'src> {
}
ast::FStringExpressionElement {
expression: Box::new(value.expr),
expression: self.alloc_box(value.expr),
debug_text,
conversion,
format_spec,
@@ -1513,7 +1525,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `[` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#list-displays>
fn parse_list_like_expression(&mut self) -> Expr {
fn parse_list_like_expression(&mut self) -> Expr<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lsqb);
@@ -1566,7 +1578,7 @@ impl<'src> Parser<'src> {
/// - <https://docs.python.org/3/reference/expressions.html#set-displays>
/// - <https://docs.python.org/3/reference/expressions.html#dictionary-displays>
/// - <https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries>
fn parse_set_or_dict_like_expression(&mut self) -> Expr {
fn parse_set_or_dict_like_expression(&mut self) -> Expr<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lbrace);
@@ -1654,7 +1666,7 @@ impl<'src> Parser<'src> {
/// Matches the `(tuple | group | genexp)` rule in the [Python grammar].
///
/// [Python grammar]: https://docs.python.org/3/reference/grammar.html
fn parse_parenthesized_expression(&mut self) -> ParsedExpr {
fn parse_parenthesized_expression(&mut self) -> ParsedExpr<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lpar);
@@ -1735,11 +1747,11 @@ impl<'src> Parser<'src> {
/// Uses the `parse_func` to parse each item in the tuple.
pub(super) fn parse_tuple_expression(
&mut self,
first_element: Expr,
first_element: Expr<'ast>,
start: TextSize,
parenthesized: Parenthesized,
mut parse_func: impl FnMut(&mut Parser<'src>) -> ParsedExpr,
) -> ast::ExprTuple {
mut parse_func: impl FnMut(&mut Parser<'src, 'ast>) -> ParsedExpr<'ast>,
) -> ast::ExprTuple<'ast> {
// TODO(dhruvmanila): Can we remove `parse_func` and use `parenthesized` to
// determine the parsing function?
@@ -1768,7 +1780,11 @@ impl<'src> Parser<'src> {
/// Parses a list expression.
///
/// See: <https://docs.python.org/3/reference/expressions.html#list-displays>
fn parse_list_expression(&mut self, first_element: Expr, start: TextSize) -> ast::ExprList {
fn parse_list_expression(
&mut self,
first_element: Expr<'ast>,
start: TextSize,
) -> ast::ExprList<'ast> {
if !self.at_sequence_end() {
self.expect(TokenKind::Comma);
}
@@ -1795,7 +1811,11 @@ impl<'src> Parser<'src> {
/// Parses a set expression.
///
/// See: <https://docs.python.org/3/reference/expressions.html#set-displays>
fn parse_set_expression(&mut self, first_element: Expr, start: TextSize) -> ast::ExprSet {
fn parse_set_expression(
&mut self,
first_element: Expr<'ast>,
start: TextSize,
) -> ast::ExprSet<'ast> {
if !self.at_sequence_end() {
self.expect(TokenKind::Comma);
}
@@ -1823,10 +1843,10 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#dictionary-displays>
fn parse_dictionary_expression(
&mut self,
key: Option<Expr>,
value: Expr,
key: Option<Expr<'ast>>,
value: Expr<'ast>,
start: TextSize,
) -> ast::ExprDict {
) -> ast::ExprDict<'ast> {
if !self.at_sequence_end() {
self.expect(TokenKind::Comma);
}
@@ -1866,7 +1886,7 @@ impl<'src> Parser<'src> {
/// followed by `if` clauses.
///
/// See: <https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-comp_for>
fn parse_generators(&mut self) -> Vec<ast::Comprehension> {
fn parse_generators(&mut self) -> Vec<ast::Comprehension<'ast>> {
const GENERATOR_SET: TokenSet = TokenSet::new([TokenKind::For, TokenKind::Async]);
let mut generators = vec![];
@@ -1887,7 +1907,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at an `async` or `for` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries>
fn parse_comprehension(&mut self) -> ast::Comprehension {
fn parse_comprehension(&mut self) -> ast::Comprehension<'ast> {
let start = self.node_start();
let is_async = self.eat(TokenKind::Async);
@@ -1938,10 +1958,10 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#generator-expressions>
pub(super) fn parse_generator_expression(
&mut self,
element: Expr,
element: Expr<'ast>,
start: TextSize,
parenthesized: Parenthesized,
) -> ast::ExprGenerator {
) -> ast::ExprGenerator<'ast> {
let generators = self.parse_generators();
if parenthesized.is_yes() {
@@ -1949,7 +1969,7 @@ impl<'src> Parser<'src> {
}
ast::ExprGenerator {
elt: Box::new(element),
elt: self.alloc_box(element),
generators,
range: self.node_range(start),
parenthesized: parenthesized.is_yes(),
@@ -1961,15 +1981,15 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries>
fn parse_list_comprehension_expression(
&mut self,
element: Expr,
element: Expr<'ast>,
start: TextSize,
) -> ast::ExprListComp {
) -> ast::ExprListComp<'ast> {
let generators = self.parse_generators();
self.expect(TokenKind::Rsqb);
ast::ExprListComp {
elt: Box::new(element),
elt: self.alloc_box(element),
generators,
range: self.node_range(start),
}
@@ -1980,17 +2000,17 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries>
fn parse_dictionary_comprehension_expression(
&mut self,
key: Expr,
value: Expr,
key: Expr<'ast>,
value: Expr<'ast>,
start: TextSize,
) -> ast::ExprDictComp {
) -> ast::ExprDictComp<'ast> {
let generators = self.parse_generators();
self.expect(TokenKind::Rbrace);
ast::ExprDictComp {
key: Box::new(key),
value: Box::new(value),
key: self.alloc_box(key),
value: self.alloc_box(value),
generators,
range: self.node_range(start),
}
@@ -2001,15 +2021,15 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries>
fn parse_set_comprehension_expression(
&mut self,
element: Expr,
element: Expr<'ast>,
start: TextSize,
) -> ast::ExprSetComp {
) -> ast::ExprSetComp<'ast> {
let generators = self.parse_generators();
self.expect(TokenKind::Rbrace);
ast::ExprSetComp {
elt: Box::new(element),
elt: self.alloc_box(element),
generators,
range: self.node_range(start),
}
@@ -2031,7 +2051,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `*` token.
///
/// [Python grammar]: https://docs.python.org/3/reference/grammar.html
fn parse_starred_expression(&mut self, context: ExpressionContext) -> ast::ExprStarred {
fn parse_starred_expression(&mut self, context: ExpressionContext) -> ast::ExprStarred<'ast> {
let start = self.node_start();
self.bump(TokenKind::Star);
@@ -2045,7 +2065,7 @@ impl<'src> Parser<'src> {
};
ast::ExprStarred {
value: Box::new(parsed_expr.expr),
value: self.alloc_box(parsed_expr.expr),
ctx: ExprContext::Load,
range: self.node_range(start),
}
@@ -2058,7 +2078,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at an `await` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#await-expression>
fn parse_await_expression(&mut self) -> ast::ExprAwait {
fn parse_await_expression(&mut self) -> ast::ExprAwait<'ast> {
let start = self.node_start();
self.bump(TokenKind::Await);
@@ -2068,7 +2088,7 @@ impl<'src> Parser<'src> {
);
ast::ExprAwait {
value: Box::new(parsed_expr.expr),
value: self.alloc_box(parsed_expr.expr),
range: self.node_range(start),
}
}
@@ -2080,7 +2100,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `yield` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#yield-expressions>
fn parse_yield_expression(&mut self) -> Expr {
fn parse_yield_expression(&mut self) -> Expr<'ast> {
let start = self.node_start();
self.bump(TokenKind::Yield);
@@ -2089,10 +2109,10 @@ impl<'src> Parser<'src> {
}
let value = self.at_expr().then(|| {
Box::new(
self.parse_expression_list(ExpressionContext::starred_bitwise_or())
.expr,
)
let expression = self
.parse_expression_list(ExpressionContext::starred_bitwise_or())
.expr;
self.alloc_box(expression)
});
Expr::Yield(ast::ExprYield {
@@ -2107,7 +2127,7 @@ impl<'src> Parser<'src> {
/// even when parsing a `yield from` expression.
///
/// See: <https://docs.python.org/3/reference/expressions.html#yield-expressions>
fn parse_yield_from_expression(&mut self, start: TextSize) -> Expr {
fn parse_yield_from_expression(&mut self, start: TextSize) -> Expr<'ast> {
// Grammar:
// 'yield' 'from' expression
//
@@ -2135,7 +2155,7 @@ impl<'src> Parser<'src> {
}
Expr::YieldFrom(ast::ExprYieldFrom {
value: Box::new(expr),
value: self.alloc_box(expr),
range: self.node_range(start),
})
}
@@ -2149,9 +2169,9 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/expressions.html#assignment-expressions>
pub(super) fn parse_named_expression(
&mut self,
mut target: Expr,
mut target: Expr<'ast>,
start: TextSize,
) -> ast::ExprNamed {
) -> ast::ExprNamed<'ast> {
self.bump(TokenKind::ColonEqual);
if !target.is_name_expr() {
@@ -2162,8 +2182,8 @@ impl<'src> Parser<'src> {
let value = self.parse_conditional_expression_or_higher();
ast::ExprNamed {
target: Box::new(target),
value: Box::new(value.expr),
target: self.alloc_box(target),
value: self.alloc_box(value.expr),
range: self.node_range(start),
}
}
@@ -2175,7 +2195,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `lambda` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#lambda>
fn parse_lambda_expr(&mut self) -> ast::ExprLambda {
fn parse_lambda_expr(&mut self) -> ast::ExprLambda<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lambda);
@@ -2184,7 +2204,8 @@ impl<'src> Parser<'src> {
// lambda: 1
None
} else {
Some(Box::new(self.parse_parameters(FunctionKind::Lambda)))
let parameters = self.parse_parameters(FunctionKind::Lambda);
Some(self.alloc_box(parameters))
};
self.expect(TokenKind::Colon);
@@ -2209,7 +2230,7 @@ impl<'src> Parser<'src> {
let body = self.parse_conditional_expression_or_higher();
ast::ExprLambda {
body: Box::new(body.expr),
body: self.alloc_box(body.expr),
parameters,
range: self.node_range(start),
}
@@ -2222,7 +2243,11 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at an `if` token.
///
/// See: <https://docs.python.org/3/reference/expressions.html#conditional-expressions>
pub(super) fn parse_if_expression(&mut self, body: Expr, start: TextSize) -> ast::ExprIf {
pub(super) fn parse_if_expression(
&mut self,
body: Expr<'ast>,
start: TextSize,
) -> ast::ExprIf<'ast> {
self.bump(TokenKind::If);
let test = self.parse_simple_expression(ExpressionContext::default());
@@ -2232,9 +2257,9 @@ impl<'src> Parser<'src> {
let orelse = self.parse_conditional_expression_or_higher();
ast::ExprIf {
body: Box::new(body),
test: Box::new(test.expr),
orelse: Box::new(orelse.expr),
body: self.alloc_box(body),
test: self.alloc_box(test.expr),
orelse: self.alloc_box(orelse.expr),
range: self.node_range(start),
}
}
@@ -2245,7 +2270,7 @@ impl<'src> Parser<'src> {
///
/// If the parser isn't positioned at a `IpyEscapeCommand` token.
/// If the escape command kind is not `%` or `!`.
fn parse_ipython_escape_command_expression(&mut self) -> ast::ExprIpyEscapeCommand {
fn parse_ipython_escape_command_expression(&mut self) -> ast::ExprIpyEscapeCommand<'ast> {
let start = self.node_start();
let TokenValue::IpyEscapeCommand { value, kind } =
@@ -2262,7 +2287,7 @@ impl<'src> Parser<'src> {
let command = ast::ExprIpyEscapeCommand {
range: self.node_range(start),
kind,
value,
value: self.alloc_str(&value),
};
if self.mode != Mode::Ipython {
@@ -2276,7 +2301,7 @@ impl<'src> Parser<'src> {
/// 1. There aren't any duplicate keyword argument
/// 2. If there are more than one argument (positional or keyword), all generator expressions
/// present should be parenthesized.
fn validate_arguments(&mut self, arguments: &ast::Arguments) {
fn validate_arguments(&mut self, arguments: &ast::Arguments<'ast>) {
let mut all_arg_names =
FxHashSet::with_capacity_and_hasher(arguments.keywords.len(), FxBuildHasher);
@@ -2316,21 +2341,21 @@ impl<'src> Parser<'src> {
}
#[derive(Debug)]
pub(super) struct ParsedExpr {
pub(super) expr: Expr,
pub(super) struct ParsedExpr<'ast> {
pub(super) expr: Expr<'ast>,
pub(super) is_parenthesized: bool,
}
impl ParsedExpr {
impl ParsedExpr<'_> {
#[inline]
pub(super) const fn is_unparenthesized_starred_expr(&self) -> bool {
!self.is_parenthesized && self.expr.is_starred_expr()
}
}
impl From<Expr> for ParsedExpr {
impl<'ast> From<Expr<'ast>> for ParsedExpr<'ast> {
#[inline]
fn from(expr: Expr) -> Self {
fn from(expr: Expr<'ast>) -> Self {
ParsedExpr {
expr,
is_parenthesized: false,
@@ -2338,15 +2363,15 @@ impl From<Expr> for ParsedExpr {
}
}
impl Deref for ParsedExpr {
type Target = Expr;
impl<'ast> Deref for ParsedExpr<'ast> {
type Target = Expr<'ast>;
fn deref(&self) -> &Self::Target {
&self.expr
}
}
impl Ranged for ParsedExpr {
impl Ranged for ParsedExpr<'_> {
#[inline]
fn range(&self) -> TextRange {
self.expr.range()

View File

@@ -1,7 +1,7 @@
use std::cmp::Ordering;
use bitflags::bitflags;
use ruff_allocator::Allocator;
use ruff_python_ast::{Mod, ModExpression, ModModule};
use ruff_text_size::{Ranged, TextRange, TextSize};
@@ -23,7 +23,7 @@ mod statement;
mod tests;
#[derive(Debug)]
pub(crate) struct Parser<'src> {
pub(crate) struct Parser<'src, 'ast> {
source: &'src str,
/// Token source for the parser that skips over any non-trivia token.
@@ -47,16 +47,23 @@ pub(crate) struct Parser<'src> {
/// The start offset in the source code from which to start parsing at.
start_offset: TextSize,
allocator: &'ast Allocator,
}
impl<'src> Parser<'src> {
impl<'src, 'ast> Parser<'src, 'ast> {
/// Create a new parser for the given source code.
pub(crate) fn new(source: &'src str, mode: Mode) -> Self {
Parser::new_starts_at(source, mode, TextSize::new(0))
pub(crate) fn new(source: &'src str, mode: Mode, allocator: &'ast Allocator) -> Self {
Parser::new_starts_at(source, mode, TextSize::new(0), allocator)
}
/// Create a new parser for the given source code which starts parsing at the given offset.
pub(crate) fn new_starts_at(source: &'src str, mode: Mode, start_offset: TextSize) -> Self {
pub(crate) fn new_starts_at(
source: &'src str,
mode: Mode,
start_offset: TextSize,
allocator: &'ast Allocator,
) -> Self {
let tokens = TokenSource::from_source(source, mode, start_offset);
Parser {
@@ -68,11 +75,12 @@ impl<'src> Parser<'src> {
prev_token_end: TextSize::new(0),
start_offset,
current_token_id: TokenId::default(),
allocator,
}
}
/// Consumes the [`Parser`] and returns the parsed [`Parsed`].
pub(crate) fn parse(mut self) -> Parsed<Mod> {
pub(crate) fn parse(mut self) -> Parsed<Mod<'ast>> {
let syntax = match self.mode {
Mode::Expression => Mod::Expression(self.parse_single_expression()),
Mode::Module | Mode::Ipython => Mod::Module(self.parse_module()),
@@ -89,7 +97,7 @@ impl<'src> Parser<'src> {
///
/// After parsing a single expression, an error is reported and all remaining tokens are
/// dropped by the parser.
fn parse_single_expression(&mut self) -> ModExpression {
fn parse_single_expression(&mut self) -> ModExpression<'ast> {
let start = self.node_start();
let parsed_expr = self.parse_expression_list(ExpressionContext::default());
@@ -116,7 +124,7 @@ impl<'src> Parser<'src> {
self.bump(TokenKind::EndOfFile);
ModExpression {
body: Box::new(parsed_expr.expr),
body: parsed_expr.expr,
range: self.node_range(start),
}
}
@@ -124,7 +132,7 @@ impl<'src> Parser<'src> {
/// Parses a Python module.
///
/// This is to be used for [`Mode::Module`] and [`Mode::Ipython`].
fn parse_module(&mut self) -> ModModule {
fn parse_module(&mut self) -> ModModule<'ast> {
let body = self.parse_list_into_vec(
RecoveryContextKind::ModuleStatements,
Parser::parse_statement,
@@ -138,6 +146,21 @@ impl<'src> Parser<'src> {
}
}
fn alloc_box<T>(&self, value: T) -> ruff_allocator::Box<'ast, T> {
ruff_allocator::Box::new_in(value, &self.allocator)
}
fn alloc<T>(&self, value: T) -> &'ast mut T {
self.allocator.alloc(value)
}
fn alloc_str<T>(&self, value: T) -> &'ast mut str
where
T: AsRef<str>,
{
self.allocator.alloc_str(value.as_ref())
}
fn finish(self, syntax: Mod) -> Parsed<Mod> {
assert_eq!(
self.current_token_kind(),
@@ -444,7 +467,7 @@ impl<'src> Parser<'src> {
fn parse_list_into_vec<T>(
&mut self,
recovery_context_kind: RecoveryContextKind,
parse_element: impl Fn(&mut Parser<'src>) -> T,
parse_element: impl Fn(&mut Parser<'src, 'ast>) -> T,
) -> Vec<T> {
let mut elements = Vec::new();
self.parse_list(recovery_context_kind, |p| elements.push(parse_element(p)));
@@ -461,7 +484,7 @@ impl<'src> Parser<'src> {
fn parse_list(
&mut self,
recovery_context_kind: RecoveryContextKind,
mut parse_element: impl FnMut(&mut Parser<'src>),
mut parse_element: impl FnMut(&mut Parser<'src, 'ast>),
) {
let mut progress = ParserProgress::default();
@@ -503,7 +526,7 @@ impl<'src> Parser<'src> {
fn parse_comma_separated_list_into_vec<T>(
&mut self,
recovery_context_kind: RecoveryContextKind,
parse_element: impl Fn(&mut Parser<'src>) -> T,
parse_element: impl Fn(&mut Parser<'src, 'ast>) -> T,
) -> Vec<T> {
let mut elements = Vec::new();
self.parse_comma_separated_list(recovery_context_kind, |p| elements.push(parse_element(p)));
@@ -520,7 +543,7 @@ impl<'src> Parser<'src> {
fn parse_comma_separated_list(
&mut self,
recovery_context_kind: RecoveryContextKind,
mut parse_element: impl FnMut(&mut Parser<'src>),
mut parse_element: impl FnMut(&mut Parser<'src, 'ast>),
) {
let mut progress = ParserProgress::default();

View File

@@ -1,4 +1,3 @@
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, Expr, ExprContext, Number, Operator, Pattern, Singleton};
use ruff_text_size::{Ranged, TextSize};
@@ -49,7 +48,7 @@ const MAPPING_PATTERN_START_SET: TokenSet = TokenSet::new([
])
.union(LITERAL_PATTERN_START_SET);
impl<'src> Parser<'src> {
impl<'src, 'ast> Parser<'src, 'ast> {
/// Returns `true` if the current token is a valid start of a pattern.
pub(super) fn at_pattern_start(&self) -> bool {
self.at_ts(PATTERN_START_SET) || self.at_soft_keyword()
@@ -63,7 +62,7 @@ impl<'src> Parser<'src> {
/// Entry point to start parsing a pattern.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-patterns>
pub(super) fn parse_match_patterns(&mut self) -> Pattern {
pub(super) fn parse_match_patterns(&mut self) -> Pattern<'ast> {
let start = self.node_start();
// We don't yet know if it's a sequence pattern or a single pattern, so
@@ -84,7 +83,7 @@ impl<'src> Parser<'src> {
/// Parses an `or_pattern` or an `as_pattern`.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-pattern>
fn parse_match_pattern(&mut self, allow_star_pattern: AllowStarPattern) -> Pattern {
fn parse_match_pattern(&mut self, allow_star_pattern: AllowStarPattern) -> Pattern<'ast> {
let start = self.node_start();
// We don't yet know if it's an or pattern or an as pattern, so use whatever
@@ -124,7 +123,7 @@ impl<'src> Parser<'src> {
lhs = Pattern::MatchAs(ast::PatternMatchAs {
range: self.node_range(start),
name: Some(ident),
pattern: Some(Box::new(lhs)),
pattern: Some(self.alloc_box(lhs)),
});
}
@@ -134,7 +133,7 @@ impl<'src> Parser<'src> {
/// Parses a pattern.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-closed_pattern>
fn parse_match_pattern_lhs(&mut self, allow_star_pattern: AllowStarPattern) -> Pattern {
fn parse_match_pattern_lhs(&mut self, allow_star_pattern: AllowStarPattern) -> Pattern<'ast> {
let start = self.node_start();
let mut lhs = match self.current_token_kind() {
@@ -171,7 +170,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `{` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#mapping-patterns>
fn parse_match_pattern_mapping(&mut self) -> ast::PatternMatchMapping {
fn parse_match_pattern_mapping(&mut self) -> ast::PatternMatchMapping<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lbrace);
@@ -199,7 +198,7 @@ impl<'src> Parser<'src> {
rest = Some(identifier);
} else {
let key = match parser.parse_match_pattern_lhs(AllowStarPattern::No) {
Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => *value,
Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => value.into_inner(),
Pattern::MatchSingleton(ast::PatternMatchSingleton { value, range }) => {
match value {
Singleton::None => Expr::NoneLiteral(ast::ExprNoneLiteral { range }),
@@ -217,7 +216,7 @@ impl<'src> Parser<'src> {
ParseErrorType::OtherError("Invalid mapping pattern key".to_string()),
&pattern,
);
recovery::pattern_to_expr(pattern)
recovery::pattern_to_expr(pattern, &parser.allocator)
}
};
keys.push(key);
@@ -254,7 +253,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `*` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-star_pattern>
fn parse_match_pattern_star(&mut self) -> ast::PatternMatchStar {
fn parse_match_pattern_star(&mut self) -> ast::PatternMatchStar<'ast> {
let start = self.node_start();
self.bump(TokenKind::Star);
@@ -277,7 +276,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `(` or `[` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#sequence-patterns>
fn parse_parenthesized_or_sequence_pattern(&mut self) -> Pattern {
fn parse_parenthesized_or_sequence_pattern(&mut self) -> Pattern<'ast> {
let start = self.node_start();
let parentheses = if self.eat(TokenKind::Lpar) {
SequenceMatchPatternParentheses::Tuple
@@ -333,10 +332,10 @@ impl<'src> Parser<'src> {
/// [open sequence pattern]: https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-open_sequence_pattern
fn parse_sequence_match_pattern(
&mut self,
first_element: Pattern,
first_element: Pattern<'ast>,
start: TextSize,
parentheses: Option<SequenceMatchPatternParentheses>,
) -> ast::PatternMatchSequence {
) -> ast::PatternMatchSequence<'ast> {
if parentheses.is_some_and(|parentheses| {
self.at(parentheses.closing_kind()) || self.peek() == parentheses.closing_kind()
}) {
@@ -366,7 +365,7 @@ impl<'src> Parser<'src> {
/// Parses a literal pattern.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-literal_pattern>
fn parse_match_pattern_literal(&mut self) -> Pattern {
fn parse_match_pattern_literal(&mut self) -> Pattern<'ast> {
let start = self.node_start();
match self.current_token_kind() {
TokenKind::None => {
@@ -394,7 +393,7 @@ impl<'src> Parser<'src> {
let str = self.parse_strings();
Pattern::MatchValue(ast::PatternMatchValue {
value: Box::new(str),
value: self.alloc_box(str),
range: self.node_range(start),
})
}
@@ -405,7 +404,7 @@ impl<'src> Parser<'src> {
let range = self.node_range(start);
Pattern::MatchValue(ast::PatternMatchValue {
value: Box::new(Expr::NumberLiteral(ast::ExprNumberLiteral {
value: self.alloc_box(Expr::NumberLiteral(ast::ExprNumberLiteral {
value: Number::Complex { real, imag },
range,
})),
@@ -419,7 +418,7 @@ impl<'src> Parser<'src> {
let range = self.node_range(start);
Pattern::MatchValue(ast::PatternMatchValue {
value: Box::new(Expr::NumberLiteral(ast::ExprNumberLiteral {
value: self.alloc_box(Expr::NumberLiteral(ast::ExprNumberLiteral {
value: Number::Int(value),
range,
})),
@@ -433,7 +432,7 @@ impl<'src> Parser<'src> {
let range = self.node_range(start);
Pattern::MatchValue(ast::PatternMatchValue {
value: Box::new(Expr::NumberLiteral(ast::ExprNumberLiteral {
value: self.alloc_box(Expr::NumberLiteral(ast::ExprNumberLiteral {
value: Number::Float(value),
range,
})),
@@ -462,7 +461,7 @@ impl<'src> Parser<'src> {
}
return Pattern::MatchValue(ast::PatternMatchValue {
value: Box::new(Expr::UnaryOp(unary_expr)),
value: self.alloc_box(Expr::UnaryOp(unary_expr)),
range: self.node_range(start),
});
}
@@ -481,7 +480,7 @@ impl<'src> Parser<'src> {
let attribute = self.parse_attr_expr_for_match_pattern(id, start);
Pattern::MatchValue(ast::PatternMatchValue {
value: Box::new(attribute),
value: self.alloc_box(attribute),
range: self.node_range(start),
})
} else {
@@ -511,12 +510,12 @@ impl<'src> Parser<'src> {
);
let invalid_node = Expr::Name(ast::ExprName {
range: self.missing_node_range(),
id: Name::empty(),
id: "",
ctx: ExprContext::Invalid,
});
Pattern::MatchValue(ast::PatternMatchValue {
range: invalid_node.range(),
value: Box::new(invalid_node),
value: self.alloc_box(invalid_node),
})
}
}
@@ -533,9 +532,9 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/compound_stmts.html#literal-patterns>
fn parse_complex_literal_pattern(
&mut self,
lhs: Pattern,
lhs: Pattern<'ast>,
start: TextSize,
) -> ast::PatternMatchValue {
) -> ast::PatternMatchValue<'ast> {
let operator = if self.eat(TokenKind::Plus) {
Operator::Add
} else {
@@ -550,7 +549,7 @@ impl<'src> Parser<'src> {
lhs.value
} else {
self.add_error(ParseErrorType::ExpectedRealNumber, &lhs);
Box::new(recovery::pattern_to_expr(lhs))
self.alloc_box(recovery::pattern_to_expr(lhs, &self.allocator))
};
let rhs_pattern = self.parse_match_pattern_lhs(AllowStarPattern::No);
@@ -561,13 +560,13 @@ impl<'src> Parser<'src> {
rhs.value
} else {
self.add_error(ParseErrorType::ExpectedImaginaryNumber, &rhs_pattern);
Box::new(recovery::pattern_to_expr(rhs_pattern))
self.alloc_box(recovery::pattern_to_expr(rhs_pattern, &self.allocator))
};
let range = self.node_range(start);
ast::PatternMatchValue {
value: Box::new(Expr::BinOp(ast::ExprBinOp {
value: self.alloc_box(Expr::BinOp(ast::ExprBinOp {
left: lhs_value,
op: operator,
right: rhs_value,
@@ -578,7 +577,11 @@ impl<'src> Parser<'src> {
}
/// Parses an attribute expression until the current token is not a `.`.
fn parse_attr_expr_for_match_pattern(&mut self, mut lhs: Expr, start: TextSize) -> Expr {
fn parse_attr_expr_for_match_pattern(
&mut self,
mut lhs: Expr<'ast>,
start: TextSize,
) -> Expr<'ast> {
while self.current_token_kind() == TokenKind::Dot {
lhs = Expr::Attribute(self.parse_attribute_expression(lhs, start));
}
@@ -597,9 +600,9 @@ impl<'src> Parser<'src> {
/// [pattern arguments]: https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-pattern_arguments
fn parse_match_pattern_class(
&mut self,
cls: Pattern,
cls: Pattern<'ast>,
start: TextSize,
) -> ast::PatternMatchClass {
) -> ast::PatternMatchClass<'ast> {
let arguments_start = self.node_start();
let cls = match cls {
@@ -609,15 +612,15 @@ impl<'src> Parser<'src> {
..
}) => {
if ident.is_valid() {
Box::new(Expr::Name(ast::ExprName {
self.alloc_box(Expr::Name(ast::ExprName {
range: ident.range(),
id: ident.id,
ctx: ExprContext::Load,
}))
} else {
Box::new(Expr::Name(ast::ExprName {
self.alloc_box(Expr::Name(ast::ExprName {
range: ident.range(),
id: Name::empty(),
id: "",
ctx: ExprContext::Invalid,
}))
}
@@ -632,7 +635,7 @@ impl<'src> Parser<'src> {
ParseErrorType::OtherError("Invalid value for a class pattern".to_string()),
&pattern,
);
Box::new(recovery::pattern_to_expr(pattern))
self.alloc_box(recovery::pattern_to_expr(pattern, &self.allocator))
}
};
@@ -668,7 +671,7 @@ impl<'src> Parser<'src> {
&pattern,
);
ast::Identifier {
id: Name::empty(),
id: "",
range: parser.missing_node_range(),
}
};
@@ -724,7 +727,7 @@ impl AllowStarPattern {
/// Returns `true` if the given expression is a real number literal or a unary
/// addition or subtraction of a real number literal.
const fn is_real_number(expr: &Expr) -> bool {
fn is_real_number(expr: &Expr) -> bool {
match expr {
Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(_) | ast::Number::Float(_),

View File

@@ -1,4 +1,4 @@
use ruff_python_ast::name::Name;
use ruff_allocator::Allocator;
use ruff_python_ast::{self as ast, Expr, ExprContext, Pattern};
use ruff_text_size::{Ranged, TextLen, TextRange};
@@ -25,7 +25,10 @@ use ruff_text_size::{Ranged, TextLen, TextRange};
/// 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 {
pub(super) fn pattern_to_expr<'ast>(
pattern: Pattern<'ast>,
allocator: &'ast Allocator,
) -> Expr<'ast> {
match pattern {
Pattern::MatchSingleton(ast::PatternMatchSingleton { range, value }) => match value {
ast::Singleton::True => {
@@ -37,11 +40,14 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr {
}),
ast::Singleton::None => Expr::NoneLiteral(ast::ExprNoneLiteral { range }),
},
Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => *value,
Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => value.into_inner(),
// 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(),
elts: patterns
.into_iter()
.map(|pattern| pattern_to_expr(pattern, allocator))
.collect(),
ctx: ExprContext::Store,
range,
})
@@ -57,7 +63,7 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr {
.zip(patterns)
.map(|(key, pattern)| ast::DictItem {
key: Some(key),
value: pattern_to_expr(pattern),
value: pattern_to_expr(pattern, allocator),
})
.collect();
if let Some(rest) = rest {
@@ -79,41 +85,46 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr {
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 {
args: allocator.alloc_slice_fill_iter(
arguments
.patterns
.into_iter()
.map(|pattern| pattern_to_expr(pattern, allocator)),
),
keywords: allocator.alloc_slice_fill_iter(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(),
value: pattern_to_expr(keyword_pattern.pattern, allocator),
},
)),
},
}),
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,
})),
value: ruff_allocator::Box::new_in(
Expr::Name(ast::ExprName {
range: name.range,
id: name.id,
ctx: ExprContext::Store,
}),
allocator,
),
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,
})),
value: ruff_allocator::Box::new_in(
Expr::Name(ast::ExprName {
range: TextRange::new(range.end() - "_".text_len(), range.end()),
id: "_",
ctx: ExprContext::Store,
}),
allocator,
),
ctx: ExprContext::Store,
})
}
@@ -125,10 +136,10 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr {
}) => match (pattern, name) {
(Some(_), Some(_)) => Expr::Name(ast::ExprName {
range,
id: Name::empty(),
id: "",
ctx: ExprContext::Invalid,
}),
(Some(pattern), None) => pattern_to_expr(*pattern),
(Some(pattern), None) => pattern_to_expr(pattern.into_inner(), allocator),
(None, Some(name)) => Expr::Name(ast::ExprName {
range: name.range,
id: name.id,
@@ -136,16 +147,16 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr {
}),
(None, None) => Expr::Name(ast::ExprName {
range,
id: Name::new_static("_"),
id: "_",
ctx: ExprContext::Store,
}),
},
Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => {
let to_bin_expr = |left: Pattern, right: Pattern| ast::ExprBinOp {
let to_bin_expr = |left: Pattern<'ast>, right: Pattern<'ast>| ast::ExprBinOp {
range: TextRange::new(left.start(), right.end()),
left: Box::new(pattern_to_expr(left)),
left: ruff_allocator::Box::new_in(pattern_to_expr(left, allocator), allocator),
op: ast::Operator::BitOr,
right: Box::new(pattern_to_expr(right)),
right: ruff_allocator::Box::new_in(pattern_to_expr(right, allocator), allocator),
};
let mut iter = patterns.into_iter();
@@ -155,9 +166,12 @@ pub(super) fn pattern_to_expr(pattern: Pattern) -> Expr {
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)),
left: ruff_allocator::Box::new_in(Expr::BinOp(expr_bin_op), allocator),
op: ast::Operator::BitOr,
right: Box::new(pattern_to_expr(pattern)),
right: ruff_allocator::Box::new_in(
pattern_to_expr(pattern, allocator),
allocator,
),
}
}))
}

View File

@@ -0,0 +1,12 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
assertion_line: 56
expression: parsed.expr()
---
Name(
ExprName {
range: 0..5,
id: "first",
ctx: Load,
},
)

View File

@@ -0,0 +1,400 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
assertion_line: 144
expression: parsed.syntax()
---
Module(
ModModule {
range: 0..929,
body: [
Expr(
StmtExpr {
range: 21..42,
value: BinOp(
ExprBinOp {
range: 27..40,
left: Name(
ExprName {
range: 27..28,
id: "a",
ctx: Load,
},
),
op: Mod,
right: Name(
ExprName {
range: 39..40,
id: "b",
ctx: Load,
},
),
},
),
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 66..73,
kind: Help2,
value: "a.foo",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 74..80,
kind: Help,
value: "a.foo",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 81..88,
kind: Help,
value: "a.foo",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 89..100,
kind: Help2,
value: "a.foo()",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 115..128,
kind: Magic,
value: "timeit a = b",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 129..147,
kind: Magic,
value: "timeit foo(b) % 3",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 148..176,
kind: Magic,
value: "alias showPath pwd && ls -a",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 177..205,
kind: Magic,
value: "timeit a = foo(b); b = 2",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 206..226,
kind: Magic,
value: "matplotlib --inline",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 227..253,
kind: Magic,
value: "matplotlib --inline",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 277..309,
kind: Shell,
value: "pwd && ls -a | sed 's/^/\\ /'",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 310..347,
kind: Shell,
value: "pwd && ls -a | sed 's/^/\\\\ /'",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 348..393,
kind: ShCap,
value: "cd /Users/foo/Library/Application\\ Support/",
},
),
FunctionDef(
StmtFunctionDef {
range: 566..626,
is_async: false,
decorator_list: [],
name: Identifier {
id: "foo",
range: 570..573,
},
type_params: None,
parameters: Parameters {
range: 573..575,
posonlyargs: [],
args: [],
vararg: None,
kwonlyargs: [],
kwarg: None,
},
returns: None,
body: [
Return(
StmtReturn {
range: 581..626,
value: Some(
Compare(
ExprCompare {
range: 598..620,
left: Name(
ExprName {
range: 598..599,
id: "a",
ctx: Load,
},
),
ops: [
NotEq,
],
comparators: [
Name(
ExprName {
range: 619..620,
id: "b",
ctx: Load,
},
),
],
},
),
),
},
),
],
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 656..664,
kind: Paren,
value: "foo 1 2",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 665..673,
kind: Quote2,
value: "foo 1 2",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 674..682,
kind: Quote,
value: "foo 1 2",
},
),
For(
StmtFor {
range: 711..737,
is_async: false,
target: Name(
ExprName {
range: 715..716,
id: "a",
ctx: Store,
},
),
iter: Call(
ExprCall {
range: 720..728,
func: Name(
ExprName {
range: 720..725,
id: "range",
ctx: Load,
},
),
arguments: Arguments {
range: 725..728,
args: [
NumberLiteral(
ExprNumberLiteral {
range: 726..727,
value: Int(
5,
),
},
),
],
keywords: [],
},
},
),
body: [
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 734..737,
kind: Shell,
value: "ls",
},
),
],
orelse: [],
},
),
Assign(
StmtAssign {
range: 739..748,
targets: [
Name(
ExprName {
range: 739..741,
id: "p1",
ctx: Store,
},
),
],
value: IpyEscapeCommand(
ExprIpyEscapeCommand {
range: 744..748,
kind: Shell,
value: "pwd",
},
),
},
),
AnnAssign(
StmtAnnAssign {
range: 749..763,
target: Name(
ExprName {
range: 749..751,
id: "p2",
ctx: Store,
},
),
annotation: Name(
ExprName {
range: 753..756,
id: "str",
ctx: Load,
},
),
value: Some(
IpyEscapeCommand(
ExprIpyEscapeCommand {
range: 759..763,
kind: Shell,
value: "pwd",
},
),
),
simple: true,
},
),
Assign(
StmtAssign {
range: 764..784,
targets: [
Name(
ExprName {
range: 764..767,
id: "foo",
ctx: Store,
},
),
],
value: IpyEscapeCommand(
ExprIpyEscapeCommand {
range: 770..784,
kind: Magic,
value: "foo bar",
},
),
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 786..791,
kind: Magic,
value: " foo",
},
),
Assign(
StmtAssign {
range: 792..813,
targets: [
Name(
ExprName {
range: 792..795,
id: "foo",
ctx: Store,
},
),
],
value: IpyEscapeCommand(
ExprIpyEscapeCommand {
range: 798..813,
kind: Magic,
value: "foo # comment",
},
),
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 838..842,
kind: Help,
value: "foo",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 843..852,
kind: Help2,
value: "foo.bar",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 853..865,
kind: Help,
value: "foo.bar.baz",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 866..874,
kind: Help2,
value: "foo[0]",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 875..885,
kind: Help,
value: "foo[0][1]",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 886..905,
kind: Help2,
value: "foo.bar[0].baz[1]",
},
),
IpyEscapeCommand(
StmtIpyEscapeCommand {
range: 906..929,
kind: Help2,
value: "foo.bar[0].baz[2].egg",
},
),
],
},
)

View File

@@ -0,0 +1,39 @@
---
source: crates/ruff_python_parser/src/parser/tests.rs
assertion_line: 66
expression: suite
---
[
Assign(
StmtAssign {
range: 0..37,
targets: [
Name(
ExprName {
range: 0..1,
id: "x",
ctx: Store,
},
),
],
value: StringLiteral(
ExprStringLiteral {
range: 4..37,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 4..37,
value: "\u{8}another cool trick",
flags: StringLiteralFlags {
quote_style: Double,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
},
),
]

View File

@@ -1,9 +1,7 @@
use compact_str::CompactString;
use std::fmt::Display;
use rustc_hash::{FxBuildHasher, FxHashSet};
use ruff_python_ast::name::Name;
use ruff_python_ast::{
self as ast, ExceptHandler, Expr, ExprContext, IpyEscapeKind, Operator, Stmt, WithItem,
};
@@ -77,7 +75,7 @@ const AUGMENTED_ASSIGN_SET: TokenSet = TokenSet::new([
TokenKind::RightShiftEqual,
]);
impl<'src> Parser<'src> {
impl<'src, 'ast> Parser<'src, 'ast> {
/// Returns `true` if the current token is the start of a compound statement.
pub(super) fn at_compound_stmt(&self) -> bool {
self.at_ts(COMPOUND_STMT_SET)
@@ -109,7 +107,7 @@ impl<'src> Parser<'src> {
/// See:
/// - <https://docs.python.org/3/reference/compound_stmts.html>
/// - <https://docs.python.org/3/reference/simple_stmts.html>
pub(super) fn parse_statement(&mut self) -> Stmt {
pub(super) fn parse_statement(&mut self) -> Stmt<'ast> {
let start = self.node_start();
match self.current_token_kind() {
@@ -150,7 +148,7 @@ impl<'src> Parser<'src> {
/// This statement must be terminated by a newline or semicolon.
///
/// Use [`Parser::parse_simple_statements`] to parse a sequence of simple statements.
fn parse_single_simple_statement(&mut self) -> Stmt {
fn parse_single_simple_statement(&mut self) -> Stmt<'ast> {
let stmt = self.parse_simple_statement();
// The order of the token is important here.
@@ -189,7 +187,7 @@ impl<'src> Parser<'src> {
/// Matches the `simple_stmts` rule in the [Python grammar].
///
/// [Python grammar]: https://docs.python.org/3/reference/grammar.html
fn parse_simple_statements(&mut self) -> Vec<Stmt> {
fn parse_simple_statements(&mut self) -> Vec<Stmt<'ast>> {
let mut stmts = vec![];
let mut progress = ParserProgress::default();
@@ -259,7 +257,7 @@ impl<'src> Parser<'src> {
/// Parses a simple statement.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html>
fn parse_simple_statement(&mut self) -> Stmt {
fn parse_simple_statement(&mut self) -> Stmt<'ast> {
match self.current_token_kind() {
TokenKind::Return => Stmt::Return(self.parse_return_statement()),
TokenKind::Import => Stmt::Import(self.parse_import_statement()),
@@ -311,7 +309,7 @@ impl<'src> Parser<'src> {
} else {
Stmt::Expr(ast::StmtExpr {
range: self.node_range(start),
value: Box::new(parsed_expr.expr),
value: self.alloc_box(parsed_expr.expr),
})
}
}
@@ -325,7 +323,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `del` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#grammar-token-python-grammar-del_stmt>
fn parse_delete_statement(&mut self) -> ast::StmtDelete {
fn parse_delete_statement(&mut self) -> ast::StmtDelete<'ast> {
let start = self.node_start();
self.bump(TokenKind::Del);
@@ -377,7 +375,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `return` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#grammar-token-python-grammar-return_stmt>
fn parse_return_statement(&mut self) -> ast::StmtReturn {
fn parse_return_statement(&mut self) -> ast::StmtReturn<'ast> {
let start = self.node_start();
self.bump(TokenKind::Return);
@@ -388,10 +386,10 @@ impl<'src> Parser<'src> {
// return x := 1
// return *x and y
let value = self.at_expr().then(|| {
Box::new(
self.parse_expression_list(ExpressionContext::starred_bitwise_or())
.expr,
)
let value = self
.parse_expression_list(ExpressionContext::starred_bitwise_or())
.expr;
self.alloc_box(value)
});
ast::StmtReturn {
@@ -407,7 +405,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `raise` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#grammar-token-python-grammar-raise_stmt>
fn parse_raise_statement(&mut self) -> ast::StmtRaise {
fn parse_raise_statement(&mut self) -> ast::StmtRaise<'ast> {
let start = self.node_start();
self.bump(TokenKind::Raise);
@@ -432,7 +430,7 @@ impl<'src> Parser<'src> {
self.add_error(ParseErrorType::UnparenthesizedTupleExpression, &exc);
}
Some(Box::new(exc.expr))
Some(self.alloc_box(exc.expr))
};
let cause = (exc.is_some() && self.eat(TokenKind::From)).then(|| {
@@ -453,7 +451,7 @@ impl<'src> Parser<'src> {
self.add_error(ParseErrorType::UnparenthesizedTupleExpression, &cause);
}
Box::new(cause.expr)
self.alloc_box(cause.expr)
});
ast::StmtRaise {
@@ -470,7 +468,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at an `import` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#the-import-statement>
fn parse_import_statement(&mut self) -> ast::StmtImport {
fn parse_import_statement(&mut self) -> ast::StmtImport<'ast> {
let start = self.node_start();
self.bump(TokenKind::Import);
@@ -510,7 +508,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `from` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#grammar-token-python-grammar-import_stmt>
fn parse_from_import_statement(&mut self) -> ast::StmtImportFrom {
fn parse_from_import_statement(&mut self) -> ast::StmtImportFrom<'ast> {
let start = self.node_start();
self.bump(TokenKind::From);
@@ -619,15 +617,12 @@ impl<'src> Parser<'src> {
/// See:
/// - <https://docs.python.org/3/reference/simple_stmts.html#the-import-statement>
/// - <https://docs.python.org/3/library/ast.html#ast.alias>
fn parse_alias(&mut self, style: ImportStyle) -> ast::Alias {
fn parse_alias(&mut self, style: ImportStyle) -> ast::Alias<'ast> {
let start = self.node_start();
if self.eat(TokenKind::Star) {
let range = self.node_range(start);
return ast::Alias {
name: ast::Identifier {
id: Name::new_static("*"),
range,
},
name: ast::Identifier { id: "*", range },
asname: None,
range,
};
@@ -668,10 +663,11 @@ impl<'src> Parser<'src> {
/// Parses a dotted name.
///
/// A dotted name is a sequence of identifiers separated by a single dot.
fn parse_dotted_name(&mut self) -> ast::Identifier {
fn parse_dotted_name(&mut self) -> ast::Identifier<'ast> {
let start = self.node_start();
let mut dotted_name: CompactString = self.parse_identifier().id.into();
let id = self.parse_identifier().id;
let mut dotted_name = ruff_allocator::String::from_str_in(id, &self.allocator);
let mut progress = ParserProgress::default();
while self.eat(TokenKind::Dot) {
@@ -688,7 +684,7 @@ impl<'src> Parser<'src> {
// import a.b.c
// import a . b . c
ast::Identifier {
id: Name::from(dotted_name),
id: dotted_name.into_bump_str(),
range: self.node_range(start),
}
}
@@ -745,7 +741,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at an `assert` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement>
fn parse_assert_statement(&mut self) -> ast::StmtAssert {
fn parse_assert_statement(&mut self) -> ast::StmtAssert<'ast> {
let start = self.node_start();
self.bump(TokenKind::Assert);
@@ -766,7 +762,8 @@ impl<'src> Parser<'src> {
// assert False, assert x
// assert False, yield x
// assert False, x := 1
Some(Box::new(self.parse_conditional_expression_or_higher().expr))
let expression = self.parse_conditional_expression_or_higher().expr;
Some(self.alloc_box(expression))
} else {
// test_err assert_empty_msg
// assert x,
@@ -781,7 +778,7 @@ impl<'src> Parser<'src> {
};
ast::StmtAssert {
test: Box::new(test.expr),
test: self.alloc_box(test.expr),
msg,
range: self.node_range(start),
}
@@ -794,7 +791,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `global` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#grammar-token-python-grammar-global_stmt>
fn parse_global_statement(&mut self) -> ast::StmtGlobal {
fn parse_global_statement(&mut self) -> ast::StmtGlobal<'ast> {
let start = self.node_start();
self.bump(TokenKind::Global);
@@ -832,7 +829,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `nonlocal` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#grammar-token-python-grammar-nonlocal_stmt>
fn parse_nonlocal_statement(&mut self) -> ast::StmtNonlocal {
fn parse_nonlocal_statement(&mut self) -> ast::StmtNonlocal<'ast> {
let start = self.node_start();
self.bump(TokenKind::Nonlocal);
@@ -873,7 +870,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `type` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#the-type-statement>
fn parse_type_alias_statement(&mut self) -> ast::StmtTypeAlias {
fn parse_type_alias_statement(&mut self) -> ast::StmtTypeAlias<'ast> {
let start = self.node_start();
self.bump(TokenKind::Type);
@@ -897,9 +894,9 @@ impl<'src> Parser<'src> {
let value = self.parse_conditional_expression_or_higher();
ast::StmtTypeAlias {
name: Box::new(name),
name: self.alloc_box(name),
type_params,
value: Box::new(value.expr),
value: self.alloc_box(value.expr),
range: self.node_range(start),
}
}
@@ -909,7 +906,7 @@ impl<'src> Parser<'src> {
/// # Panics
///
/// If the parser isn't positioned at an `IpyEscapeCommand` token.
fn parse_ipython_escape_command_statement(&mut self) -> ast::StmtIpyEscapeCommand {
fn parse_ipython_escape_command_statement(&mut self) -> ast::StmtIpyEscapeCommand<'ast> {
let start = self.node_start();
let TokenValue::IpyEscapeCommand { value, kind } =
@@ -923,7 +920,11 @@ impl<'src> Parser<'src> {
self.add_error(ParseErrorType::UnexpectedIpythonEscapeCommand, range);
}
ast::StmtIpyEscapeCommand { range, kind, value }
ast::StmtIpyEscapeCommand {
range,
kind,
value: self.alloc_str(&value),
}
}
/// Parses an IPython help end escape command at the statement level.
@@ -933,15 +934,19 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `?` token.
fn parse_ipython_help_end_escape_command_statement(
&mut self,
parsed_expr: &ParsedExpr,
) -> ast::StmtIpyEscapeCommand {
parsed_expr: &ParsedExpr<'ast>,
) -> ast::StmtIpyEscapeCommand<'ast> {
// We are permissive than the original implementation because we would allow whitespace
// between the expression and the suffix while the IPython implementation doesn't allow it.
// For example, `foo ?` would be valid in our case but invalid for IPython.
fn unparse_expr(parser: &mut Parser, expr: &Expr, buffer: &mut String) {
fn unparse_expr<'ast>(
parser: &mut Parser<'_, 'ast>,
expr: &Expr<'ast>,
buffer: &mut ruff_allocator::String,
) {
match expr {
Expr::Name(ast::ExprName { id, .. }) => {
buffer.push_str(id.as_str());
buffer.push_str(id);
}
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
unparse_expr(parser, value, buffer);
@@ -1012,11 +1017,11 @@ impl<'src> Parser<'src> {
);
}
let mut value = String::new();
let mut value = ruff_allocator::String::new_in(&self.allocator);
unparse_expr(self, &parsed_expr.expr, &mut value);
ast::StmtIpyEscapeCommand {
value: value.into_boxed_str(),
value: value.into_bump_str(),
kind,
range: self.node_range(parsed_expr.start()),
}
@@ -1029,7 +1034,11 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at an `=` token.
///
/// See: <https://docs.python.org/3/reference/simple_stmts.html#assignment-statements>
fn parse_assign_statement(&mut self, target: ParsedExpr, start: TextSize) -> ast::StmtAssign {
fn parse_assign_statement(
&mut self,
target: ParsedExpr<'ast>,
start: TextSize,
) -> ast::StmtAssign<'ast> {
self.bump(TokenKind::Equal);
let mut targets = vec![target.expr];
@@ -1084,7 +1093,7 @@ impl<'src> Parser<'src> {
ast::StmtAssign {
targets,
value: Box::new(value.expr),
value: self.alloc_box(value.expr),
range: self.node_range(start),
}
}
@@ -1098,9 +1107,9 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements>
fn parse_annotated_assignment_statement(
&mut self,
mut target: ParsedExpr,
mut target: ParsedExpr<'ast>,
start: TextSize,
) -> ast::StmtAnnAssign {
) -> ast::StmtAnnAssign<'ast> {
self.bump(TokenKind::Colon);
// test_err ann_assign_stmt_invalid_target
@@ -1142,10 +1151,10 @@ impl<'src> Parser<'src> {
// x: Any = *a and b
// x: Any = x := 1
// x: list = [x, *a | b, *a or b]
Some(Box::new(
self.parse_expression_list(ExpressionContext::yield_or_starred_bitwise_or())
.expr,
))
let expression = self
.parse_expression_list(ExpressionContext::yield_or_starred_bitwise_or())
.expr;
Some(self.alloc_box(expression))
} else {
// test_err ann_assign_stmt_missing_rhs
// x: int =
@@ -1160,8 +1169,8 @@ impl<'src> Parser<'src> {
};
ast::StmtAnnAssign {
target: Box::new(target.expr),
annotation: Box::new(annotation.expr),
target: self.alloc_box(target.expr),
annotation: self.alloc_box(annotation.expr),
value,
simple,
range: self.node_range(start),
@@ -1177,10 +1186,10 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements>
fn parse_augmented_assignment_statement(
&mut self,
mut target: ParsedExpr,
mut target: ParsedExpr<'ast>,
op: Operator,
start: TextSize,
) -> ast::StmtAugAssign {
) -> ast::StmtAugAssign<'ast> {
// Consume the operator
self.bump_ts(AUGMENTED_ASSIGN_SET);
@@ -1215,9 +1224,9 @@ impl<'src> Parser<'src> {
let value = self.parse_expression_list(ExpressionContext::yield_or_starred_bitwise_or());
ast::StmtAugAssign {
target: Box::new(target.expr),
target: self.alloc_box(target.expr),
op,
value: Box::new(value.expr),
value: self.alloc_box(value.expr),
range: self.node_range(start),
}
}
@@ -1229,7 +1238,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at an `if` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-if-statement>
fn parse_if_statement(&mut self) -> ast::StmtIf {
fn parse_if_statement(&mut self) -> ast::StmtIf<'ast> {
let start = self.node_start();
self.bump(TokenKind::If);
@@ -1270,7 +1279,7 @@ impl<'src> Parser<'src> {
}
ast::StmtIf {
test: Box::new(test.expr),
test: self.alloc_box(test.expr),
body,
elif_else_clauses,
range: self.node_range(start),
@@ -1282,7 +1291,7 @@ impl<'src> Parser<'src> {
/// # Panics
///
/// If the parser isn't positioned at an `elif` or `else` token.
fn parse_elif_or_else_clause(&mut self, kind: ElifOrElse) -> ast::ElifElseClause {
fn parse_elif_or_else_clause(&mut self, kind: ElifOrElse) -> ast::ElifElseClause<'ast> {
let start = self.node_start();
self.bump(kind.as_token_kind());
@@ -1327,7 +1336,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `try` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-try-statement>
fn parse_try_statement(&mut self) -> ast::StmtTry {
fn parse_try_statement(&mut self) -> ast::StmtTry<'ast> {
let try_start = self.node_start();
self.bump(TokenKind::Try);
self.expect(TokenKind::Colon);
@@ -1436,7 +1445,7 @@ impl<'src> Parser<'src> {
/// # Panics
///
/// If the parser isn't positioned at an `except` token.
fn parse_except_clause(&mut self) -> (ExceptHandler, ExceptClauseKind) {
fn parse_except_clause(&mut self) -> (ExceptHandler<'ast>, ExceptClauseKind) {
let start = self.node_start();
self.bump(TokenKind::Except);
@@ -1484,7 +1493,7 @@ impl<'src> Parser<'src> {
&parsed_expr,
);
}
Some(Box::new(parsed_expr.expr))
Some(self.alloc_box(parsed_expr.expr))
} else {
if block_kind.is_star() || self.at(TokenKind::As) {
// test_err except_stmt_missing_exception
@@ -1566,7 +1575,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `for` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-for-statement>
fn parse_for_statement(&mut self, start: TextSize) -> ast::StmtFor {
fn parse_for_statement(&mut self, start: TextSize) -> ast::StmtFor<'ast> {
self.bump(TokenKind::For);
// test_err for_stmt_missing_target
@@ -1634,8 +1643,8 @@ impl<'src> Parser<'src> {
};
ast::StmtFor {
target: Box::new(target.expr),
iter: Box::new(iter.expr),
target: self.alloc_box(target.expr),
iter: self.alloc_box(iter.expr),
is_async: false,
body,
orelse,
@@ -1650,7 +1659,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `while` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-while-statement>
fn parse_while_statement(&mut self) -> ast::StmtWhile {
fn parse_while_statement(&mut self) -> ast::StmtWhile<'ast> {
let start = self.node_start();
self.bump(TokenKind::While);
@@ -1683,7 +1692,7 @@ impl<'src> Parser<'src> {
};
ast::StmtWhile {
test: Box::new(test.expr),
test: self.alloc_box(test.expr),
body,
orelse,
range: self.node_range(start),
@@ -1704,9 +1713,9 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/compound_stmts.html#function-definitions>
fn parse_function_definition(
&mut self,
decorator_list: Vec<ast::Decorator>,
decorator_list: Vec<ast::Decorator<'ast>>,
start: TextSize,
) -> ast::StmtFunctionDef {
) -> ast::StmtFunctionDef<'ast> {
self.bump(TokenKind::Def);
// test_err function_def_missing_identifier
@@ -1770,7 +1779,7 @@ impl<'src> Parser<'src> {
);
}
Some(Box::new(returns.expr))
Some(self.alloc_box(returns.expr))
} else {
// test_err function_def_missing_return_type
// def foo() -> : ...
@@ -1795,8 +1804,8 @@ impl<'src> Parser<'src> {
ast::StmtFunctionDef {
name,
type_params: type_params.map(Box::new),
parameters: Box::new(parameters),
type_params: type_params.map(|params| self.alloc_box(params)),
parameters: self.alloc_box(parameters),
body,
decorator_list,
is_async: false,
@@ -1817,9 +1826,9 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-classdef>
fn parse_class_definition(
&mut self,
decorator_list: Vec<ast::Decorator>,
decorator_list: Vec<ast::Decorator<'ast>>,
start: TextSize,
) -> ast::StmtClassDef {
) -> ast::StmtClassDef<'ast> {
self.bump(TokenKind::Class);
// test_err class_def_missing_name
@@ -1837,9 +1846,10 @@ impl<'src> Parser<'src> {
// test_ok class_def_arguments
// class Foo: ...
// class Foo(): ...
let arguments = self
.at(TokenKind::Lpar)
.then(|| Box::new(self.parse_arguments()));
let arguments = self.at(TokenKind::Lpar).then(|| {
let arguments = self.parse_arguments();
self.alloc_box(arguments)
});
self.expect(TokenKind::Colon);
@@ -1853,7 +1863,7 @@ impl<'src> Parser<'src> {
range: self.node_range(start),
decorator_list,
name,
type_params: type_params.map(Box::new),
type_params: type_params.map(|params| self.alloc_box(params)),
arguments,
body,
}
@@ -1869,7 +1879,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `with` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-with-statement>
fn parse_with_statement(&mut self, start: TextSize) -> ast::StmtWith {
fn parse_with_statement(&mut self, start: TextSize) -> ast::StmtWith<'ast> {
self.bump(TokenKind::With);
let items = self.parse_with_items();
@@ -1888,7 +1898,7 @@ impl<'src> Parser<'src> {
/// Parses a list of with items.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-with-statement>
fn parse_with_items(&mut self) -> Vec<WithItem> {
fn parse_with_items(&mut self) -> Vec<WithItem<'ast>> {
if !self.at_expr() {
self.add_error(
ParseErrorType::OtherError(
@@ -1959,7 +1969,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `(` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-with_stmt_contents>
fn try_parse_parenthesized_with_items(&mut self) -> Option<Vec<WithItem>> {
fn try_parse_parenthesized_with_items(&mut self) -> Option<Vec<WithItem<'ast>>> {
let checkpoint = self.checkpoint();
// We'll start with the assumption that the with items are parenthesized.
@@ -2067,7 +2077,7 @@ impl<'src> Parser<'src> {
/// Parses a single `with` item.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-with_item>
fn parse_with_item(&mut self, state: WithItemParsingState) -> ParsedWithItem {
fn parse_with_item(&mut self, state: WithItemParsingState) -> ParsedWithItem<'ast> {
let start = self.node_start();
// The grammar for the context expression of a with item depends on the state
@@ -2099,9 +2109,10 @@ impl<'src> Parser<'src> {
WithItemParsingState::Regular => self.parse_conditional_expression_or_higher(),
};
let optional_vars = self
.at(TokenKind::As)
.then(|| Box::new(self.parse_with_item_optional_vars().expr));
let optional_vars = self.at(TokenKind::As).then(|| {
let vars = self.parse_with_item_optional_vars().expr;
self.alloc_box(vars)
});
ParsedWithItem {
is_parenthesized: context_expr.is_parenthesized,
@@ -2118,7 +2129,7 @@ impl<'src> Parser<'src> {
/// # Panics
///
/// If the parser isn't positioned at an `as` token.
fn parse_with_item_optional_vars(&mut self) -> ParsedExpr {
fn parse_with_item_optional_vars(&mut self) -> ParsedExpr<'ast> {
self.bump(TokenKind::As);
let mut target = self
@@ -2158,7 +2169,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `match` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-match-statement>
fn try_parse_match_statement(&mut self) -> Option<ast::StmtMatch> {
fn try_parse_match_statement(&mut self) -> Option<ast::StmtMatch<'ast>> {
let checkpoint = self.checkpoint();
let start = self.node_start();
@@ -2174,7 +2185,7 @@ impl<'src> Parser<'src> {
let cases = self.parse_match_body();
Some(ast::StmtMatch {
subject: Box::new(subject),
subject: self.alloc_box(subject),
cases,
range: self.node_range(start),
})
@@ -2196,7 +2207,7 @@ impl<'src> Parser<'src> {
let cases = self.parse_match_body();
Some(ast::StmtMatch {
subject: Box::new(subject),
subject: self.alloc_box(subject),
cases,
range: self.node_range(start),
})
@@ -2217,7 +2228,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `match` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#the-match-statement>
fn parse_match_statement(&mut self) -> ast::StmtMatch {
fn parse_match_statement(&mut self) -> ast::StmtMatch<'ast> {
let start = self.node_start();
self.bump(TokenKind::Match);
@@ -2227,14 +2238,14 @@ impl<'src> Parser<'src> {
let cases = self.parse_match_body();
ast::StmtMatch {
subject: Box::new(subject),
subject: self.alloc_box(subject),
cases,
range: self.node_range(start),
}
}
/// Parses the subject expression for a `match` statement.
fn parse_match_subject_expression(&mut self) -> Expr {
fn parse_match_subject_expression(&mut self) -> Expr<'ast> {
let start = self.node_start();
// Subject expression grammar is:
@@ -2288,7 +2299,7 @@ impl<'src> Parser<'src> {
///
/// This method expects that the parser is positioned at a `Newline` token. If not, it adds a
/// syntax error and continues parsing.
fn parse_match_body(&mut self) -> Vec<ast::MatchCase> {
fn parse_match_body(&mut self) -> Vec<ast::MatchCase<'ast>> {
// test_err match_stmt_no_newline_before_case
// match foo: case _: ...
self.expect(TokenKind::Newline);
@@ -2315,7 +2326,7 @@ impl<'src> Parser<'src> {
}
/// Parses a list of match case blocks.
fn parse_match_case_blocks(&mut self) -> Vec<ast::MatchCase> {
fn parse_match_case_blocks(&mut self) -> Vec<ast::MatchCase<'ast>> {
let mut cases = vec![];
if !self.at(TokenKind::Case) {
@@ -2349,7 +2360,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `case` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-case_block>
fn parse_match_case(&mut self) -> ast::MatchCase {
fn parse_match_case(&mut self) -> ast::MatchCase<'ast> {
let start = self.node_start();
self.bump(TokenKind::Case);
@@ -2377,10 +2388,10 @@ impl<'src> Parser<'src> {
// case y if (*a): ...
// match x:
// case y if yield x: ...
Some(Box::new(
self.parse_named_expression_or_higher(ExpressionContext::default())
.expr,
))
let guard = self
.parse_named_expression_or_higher(ExpressionContext::default())
.expr;
Some(self.alloc_box(guard))
} else {
// test_err match_stmt_missing_guard_expr
// match x:
@@ -2420,7 +2431,7 @@ impl<'src> Parser<'src> {
/// - <https://docs.python.org/3/reference/compound_stmts.html#the-async-with-statement>
/// - <https://docs.python.org/3/reference/compound_stmts.html#the-async-for-statement>
/// - <https://docs.python.org/3/reference/compound_stmts.html#coroutine-function-definition>
fn parse_async_statement(&mut self) -> Stmt {
fn parse_async_statement(&mut self) -> Stmt<'ast> {
let async_start = self.node_start();
self.bump(TokenKind::Async);
@@ -2469,7 +2480,7 @@ impl<'src> Parser<'src> {
/// Parses a decorator list followed by a class, function or async function definition.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-decorators>
fn parse_decorators(&mut self) -> Stmt {
fn parse_decorators(&mut self) -> Stmt<'ast> {
let start = self.node_start();
let mut decorators = vec![];
@@ -2558,7 +2569,7 @@ impl<'src> Parser<'src> {
///
/// This could either be a single statement that's on the same line as the
/// clause header or an indented block.
fn parse_body(&mut self, parent_clause: Clause) -> Vec<Stmt> {
fn parse_body(&mut self, parent_clause: Clause) -> Vec<Stmt<'ast>> {
// Note: The test cases in this method chooses a clause at random to test
// the error logic.
@@ -2604,7 +2615,7 @@ impl<'src> Parser<'src> {
/// # Panics
///
/// If the parser isn't positioned at an `Indent` token.
fn parse_block(&mut self) -> Vec<Stmt> {
fn parse_block(&mut self) -> Vec<Stmt<'ast>> {
self.bump(TokenKind::Indent);
let statements =
@@ -2630,7 +2641,7 @@ impl<'src> Parser<'src> {
start: TextSize,
function_kind: FunctionKind,
allow_star_annotation: AllowStarAnnotation,
) -> ast::Parameter {
) -> ast::Parameter<'ast> {
let name = self.parse_identifier();
// Annotations are only allowed for function definition. For lambda expression,
@@ -2668,7 +2679,7 @@ impl<'src> Parser<'src> {
self.parse_conditional_expression_or_higher()
}
};
Some(Box::new(parsed_expr.expr))
Some(self.alloc_box(parsed_expr.expr))
} else {
// test_err param_missing_annotation
// def foo(x:): ...
@@ -2702,7 +2713,7 @@ impl<'src> Parser<'src> {
&mut self,
start: TextSize,
function_kind: FunctionKind,
) -> ast::ParameterWithDefault {
) -> ast::ParameterWithDefault<'ast> {
let parameter = self.parse_parameter(start, function_kind, AllowStarAnnotation::No);
let default = if self.eat(TokenKind::Equal) {
@@ -2717,7 +2728,8 @@ impl<'src> Parser<'src> {
// def foo(x=*int): ...
// def foo(x=(*int)): ...
// def foo(x=yield y): ...
Some(Box::new(self.parse_conditional_expression_or_higher().expr))
let expression = self.parse_conditional_expression_or_higher().expr;
Some(self.alloc_box(expression))
} else {
// test_err param_missing_default
// def foo(x=): ...
@@ -2742,7 +2754,10 @@ impl<'src> Parser<'src> {
/// Parses a parameter list for the given function kind.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-parameter_list>
pub(super) fn parse_parameters(&mut self, function_kind: FunctionKind) -> ast::Parameters {
pub(super) fn parse_parameters(
&mut self,
function_kind: FunctionKind,
) -> ast::Parameters<'ast> {
let start = self.node_start();
if matches!(function_kind, FunctionKind::FunctionDef) {
@@ -2813,7 +2828,7 @@ impl<'src> Parser<'src> {
// TODO(dhruvmanila): The AST doesn't allow multiple `vararg`, so let's
// choose to keep the first one so that the parameters remain in preorder.
if parameters.vararg.is_none() {
parameters.vararg = Some(Box::new(param));
parameters.vararg = Some(parser.alloc_box(param));
}
last_keyword_only_separator_range = None;
@@ -2887,7 +2902,7 @@ impl<'src> Parser<'src> {
);
}
parameters.kwarg = Some(Box::new(param));
parameters.kwarg = Some(parser.alloc_box(param));
last_keyword_only_separator_range = None;
}
TokenKind::Slash => {
@@ -3009,7 +3024,7 @@ impl<'src> Parser<'src> {
/// type parameter list, return `None`.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists>
fn try_parse_type_params(&mut self) -> Option<ast::TypeParams> {
fn try_parse_type_params(&mut self) -> Option<ast::TypeParams<'ast>> {
self.at(TokenKind::Lsqb).then(|| self.parse_type_params())
}
@@ -3020,7 +3035,7 @@ impl<'src> Parser<'src> {
/// If the parser isn't positioned at a `[` token.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists>
fn parse_type_params(&mut self) -> ast::TypeParams {
fn parse_type_params(&mut self) -> ast::TypeParams<'ast> {
let start = self.node_start();
self.bump(TokenKind::Lsqb);
@@ -3048,7 +3063,7 @@ impl<'src> Parser<'src> {
/// Parses a type parameter.
///
/// See: <https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-type_param>
fn parse_type_param(&mut self) -> ast::TypeParam {
fn parse_type_param(&mut self) -> ast::TypeParam<'ast> {
let start = self.node_start();
// TODO(dhruvmanila): CPython throws an error if `TypeVarTuple` or `ParamSpec`
@@ -3078,12 +3093,12 @@ impl<'src> Parser<'src> {
// type X[*Ts = yield x] = int
// type X[*Ts = yield from x] = int
// type X[*Ts = x := int] = int
Some(Box::new(
self.parse_conditional_expression_or_higher_impl(
let expression = self
.parse_conditional_expression_or_higher_impl(
ExpressionContext::starred_bitwise_or(),
)
.expr,
))
.expr;
Some(self.alloc_box(expression))
} else {
// test_err type_param_type_var_tuple_missing_default
// type X[*Ts =] = int
@@ -3122,7 +3137,8 @@ impl<'src> Parser<'src> {
// type X[**P = yield from x] = int
// type X[**P = x := int] = int
// type X[**P = *int] = int
Some(Box::new(self.parse_conditional_expression_or_higher().expr))
let expression = self.parse_conditional_expression_or_higher().expr;
Some(self.alloc_box(expression))
} else {
// test_err type_param_param_spec_missing_default
// type X[**P =] = int
@@ -3160,7 +3176,8 @@ impl<'src> Parser<'src> {
// type X[T: yield x] = int
// type X[T: yield from x] = int
// type X[T: x := int] = int
Some(Box::new(self.parse_conditional_expression_or_higher().expr))
let expression = self.parse_conditional_expression_or_higher().expr;
Some(self.alloc_box(expression))
} else {
// test_err type_param_missing_bound
// type X[T: ] = int
@@ -3184,7 +3201,8 @@ impl<'src> Parser<'src> {
// type X[T = yield from x] = int
// type X[T = x := int] = int
// type X[T: int = *int] = int
Some(Box::new(self.parse_conditional_expression_or_higher().expr))
let expression = self.parse_conditional_expression_or_higher().expr;
Some(self.alloc_box(expression))
} else {
// test_err type_param_type_var_missing_default
// type X[T =] = int
@@ -3215,7 +3233,7 @@ impl<'src> Parser<'src> {
/// If it's a starred expression, then validate the value of the starred expression.
///
/// Report an error for each invalid assignment expression found.
pub(super) fn validate_assignment_target(&mut self, expr: &Expr) {
pub(super) fn validate_assignment_target(&mut self, expr: &Expr<'ast>) {
match expr {
Expr::Starred(ast::ExprStarred { value, .. }) => self.validate_assignment_target(value),
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
@@ -3232,7 +3250,7 @@ impl<'src> Parser<'src> {
///
/// Unlike [`Parser::validate_assignment_target`], starred, list and tuple
/// expressions aren't allowed here.
fn validate_annotated_assignment_target(&mut self, expr: &Expr) {
fn validate_annotated_assignment_target(&mut self, expr: &Expr<'ast>) {
match expr {
Expr::List(_) => self.add_error(
ParseErrorType::OtherError(
@@ -3256,7 +3274,7 @@ impl<'src> Parser<'src> {
/// If the expression is a list or tuple, then validate each element in the list.
///
/// See: <https://github.com/python/cpython/blob/d864b0094f9875c5613cbb0b7f7f3ca8f1c6b606/Parser/action_helpers.c#L1150-L1180>
fn validate_delete_target(&mut self, expr: &Expr) {
fn validate_delete_target(&mut self, expr: &Expr<'ast>) {
match expr {
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
for expr in elts {
@@ -3271,7 +3289,7 @@ impl<'src> Parser<'src> {
/// Validate that the given parameters doesn't have any duplicate names.
///
/// Report errors for all the duplicate names found.
fn validate_parameters(&mut self, parameters: &ast::Parameters) {
fn validate_parameters(&mut self, parameters: &ast::Parameters<'ast>) {
let mut all_arg_names =
FxHashSet::with_capacity_and_hasher(parameters.len(), FxBuildHasher);
@@ -3431,7 +3449,7 @@ impl<'src> Parser<'src> {
fn parse_clauses<T>(
&mut self,
clause: Clause,
mut parse_clause: impl FnMut(&mut Parser<'src>) -> T,
mut parse_clause: impl FnMut(&mut Parser<'src, 'ast>) -> T,
) -> Vec<T> {
let mut clauses = Vec::new();
let mut progress = ParserProgress::default();
@@ -3544,9 +3562,9 @@ enum WithItemParsingState {
}
#[derive(Debug)]
struct ParsedWithItem {
struct ParsedWithItem<'ast> {
/// The contained with item.
item: WithItem,
item: WithItem<'ast>,
/// If the context expression of the item is parenthesized.
is_parenthesized: bool,
}

View File

@@ -1,51 +1,57 @@
use crate::{parse, parse_expression, parse_module, Mode};
use ruff_allocator::Allocator;
#[test]
fn test_modes() {
let allocator = Allocator::new();
let source = "a[0][1][2][3][4]";
assert!(parse(source, Mode::Expression).is_ok());
assert!(parse(source, Mode::Module).is_ok());
assert!(parse(source, Mode::Expression, &allocator).is_ok());
assert!(parse(source, Mode::Module, &allocator).is_ok());
}
#[test]
fn test_expr_mode_invalid_syntax1() {
let allocator = Allocator::new();
let source = "first second";
let error = parse_expression(source).unwrap_err();
let error = parse_expression(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_expr_mode_invalid_syntax2() {
let allocator = Allocator::new();
let source = r"first
second
";
let error = parse_expression(source).unwrap_err();
let error = parse_expression(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_expr_mode_invalid_syntax3() {
let allocator = Allocator::new();
let source = r"first
second
third
";
let error = parse_expression(source).unwrap_err();
let error = parse_expression(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_expr_mode_valid_syntax() {
let allocator = Allocator::new();
let source = "first
";
let parsed = parse_expression(source).unwrap();
let parsed = parse_expression(source, &allocator).unwrap();
insta::assert_debug_snapshot!(parsed.expr());
}
@@ -53,14 +59,16 @@ fn test_expr_mode_valid_syntax() {
#[test]
fn test_unicode_aliases() {
// https://github.com/RustPython/RustPython/issues/4566
let allocator = Allocator::new();
let source = r#"x = "\N{BACKSPACE}another cool trick""#;
let suite = parse_module(source).unwrap().into_suite();
let suite = parse_module(source, &allocator).unwrap().into_suite();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_ipython_escape_commands() {
let allocator = Allocator::new();
let parsed = parse(
r"
# Normal Python code
@@ -130,6 +138,7 @@ foo.bar[0].baz[2].egg??
"
.trim(),
Mode::Ipython,
&allocator,
)
.unwrap();
insta::assert_debug_snapshot!(parsed.syntax());

View File

@@ -0,0 +1,39 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 869
expression: suite
---
[
Assign(
StmtAssign {
range: 0..16,
targets: [
Name(
ExprName {
range: 0..4,
id: "bold",
ctx: Store,
},
),
],
value: StringLiteral(
ExprStringLiteral {
range: 7..16,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 7..16,
value: "\u{3}8[1m",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
},
),
]

View File

@@ -0,0 +1,81 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 802
expression: suite
---
[
Expr(
StmtExpr {
range: 0..22,
value: FString(
ExprFString {
range: 0..22,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..22,
elements: [
Literal(
FStringLiteralElement {
range: 2..5,
value: "aaa",
},
),
Expression(
FStringExpressionElement {
range: 5..10,
expression: Name(
ExprName {
range: 6..9,
id: "bbb",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
FStringLiteralElement {
range: 10..13,
value: "ccc",
},
),
Expression(
FStringExpressionElement {
range: 13..18,
expression: Name(
ExprName {
range: 14..17,
id: "ddd",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
FStringLiteralElement {
range: 18..21,
value: "eee",
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,54 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 819
expression: suite
---
[
Expr(
StmtExpr {
range: 0..8,
value: FString(
ExprFString {
range: 0..8,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..8,
elements: [
Literal(
FStringLiteralElement {
range: 2..4,
value: "\\",
},
),
Expression(
FStringExpressionElement {
range: 4..7,
expression: Name(
ExprName {
range: 5..6,
id: "x",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,54 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 794
expression: suite
---
[
Expr(
StmtExpr {
range: 0..8,
value: FString(
ExprFString {
range: 0..8,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..8,
elements: [
Literal(
FStringLiteralElement {
range: 2..4,
value: "\n",
},
),
Expression(
FStringExpressionElement {
range: 4..7,
expression: Name(
ExprName {
range: 5..6,
id: "x",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,56 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 844
expression: suite
---
[
Expr(
StmtExpr {
range: 0..9,
value: FString(
ExprFString {
range: 0..9,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..9,
elements: [
Literal(
FStringLiteralElement {
range: 3..5,
value: "\\\n",
},
),
Expression(
FStringExpressionElement {
range: 5..8,
expression: Name(
ExprName {
range: 6..7,
id: "x",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
},
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,53 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 569
expression: suite
---
[
Expr(
StmtExpr {
range: 0..10,
value: FString(
ExprFString {
range: 0..10,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..10,
elements: [
Expression(
FStringExpressionElement {
range: 2..9,
expression: Name(
ExprName {
range: 3..7,
id: "user",
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,85 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 577
expression: suite
---
[
Expr(
StmtExpr {
range: 0..38,
value: FString(
ExprFString {
range: 0..38,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..38,
elements: [
Literal(
FStringLiteralElement {
range: 2..6,
value: "mix ",
},
),
Expression(
FStringExpressionElement {
range: 6..13,
expression: Name(
ExprName {
range: 7..11,
id: "user",
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
Literal(
FStringLiteralElement {
range: 13..28,
value: " with text and ",
},
),
Expression(
FStringExpressionElement {
range: 28..37,
expression: Name(
ExprName {
range: 29..35,
id: "second",
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,65 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 585
expression: suite
---
[
Expr(
StmtExpr {
range: 0..14,
value: FString(
ExprFString {
range: 0..14,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..14,
elements: [
Expression(
FStringExpressionElement {
range: 2..13,
expression: Name(
ExprName {
range: 3..7,
id: "user",
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "=",
},
),
conversion: None,
format_spec: Some(
FStringFormatSpec {
range: 9..12,
elements: [
Literal(
FStringLiteralElement {
range: 9..12,
value: ">10",
},
),
],
},
),
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,54 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 811
expression: suite
---
[
Expr(
StmtExpr {
range: 0..11,
value: FString(
ExprFString {
range: 0..11,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..11,
elements: [
Literal(
FStringLiteralElement {
range: 4..5,
value: "\n",
},
),
Expression(
FStringExpressionElement {
range: 5..8,
expression: Name(
ExprName {
range: 6..7,
id: "x",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: true,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,69 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 537
expression: suite
---
[
Expr(
StmtExpr {
range: 0..18,
value: FString(
ExprFString {
range: 0..18,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..18,
elements: [
Expression(
FStringExpressionElement {
range: 2..5,
expression: Name(
ExprName {
range: 3..4,
id: "a",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Expression(
FStringExpressionElement {
range: 5..10,
expression: Name(
ExprName {
range: 7..8,
id: "b",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
Literal(
FStringLiteralElement {
range: 10..17,
value: "{foo}",
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,94 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 860
expression: suite
---
[
Expr(
StmtExpr {
range: 0..16,
value: FString(
ExprFString {
range: 0..16,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..16,
elements: [
Expression(
FStringExpressionElement {
range: 2..15,
expression: Name(
ExprName {
range: 3..6,
id: "foo",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
range: 7..14,
elements: [
Expression(
FStringExpressionElement {
range: 7..14,
expression: StringLiteral(
ExprStringLiteral {
range: 8..13,
value: StringLiteralValue {
inner: Concatenated(
ConcatenatedStringLiteral {
strings: [
StringLiteral {
range: 8..10,
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
StringLiteral {
range: 11..13,
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
],
value: "",
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,69 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 545
expression: suite
---
[
Expr(
StmtExpr {
range: 0..15,
value: FString(
ExprFString {
range: 0..15,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..15,
elements: [
Expression(
FStringExpressionElement {
range: 2..14,
expression: Name(
ExprName {
range: 3..6,
id: "foo",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
range: 7..13,
elements: [
Expression(
FStringExpressionElement {
range: 7..13,
expression: Name(
ExprName {
range: 8..12,
id: "spec",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,80 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 852
expression: suite
---
[
Expr(
StmtExpr {
range: 0..13,
value: FString(
ExprFString {
range: 0..13,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..13,
elements: [
Expression(
FStringExpressionElement {
range: 2..12,
expression: Name(
ExprName {
range: 3..6,
id: "foo",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
range: 7..11,
elements: [
Expression(
FStringExpressionElement {
range: 7..11,
expression: StringLiteral(
ExprStringLiteral {
range: 8..10,
value: StringLiteralValue {
inner: Single(
StringLiteral {
range: 8..10,
value: "",
flags: StringLiteralFlags {
quote_style: Single,
prefix: Empty,
triple_quoted: false,
},
},
),
},
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
},
),
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,60 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 553
expression: suite
---
[
Expr(
StmtExpr {
range: 0..13,
value: FString(
ExprFString {
range: 0..13,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..13,
elements: [
Expression(
FStringExpressionElement {
range: 2..12,
expression: Name(
ExprName {
range: 3..6,
id: "foo",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: Some(
FStringFormatSpec {
range: 7..11,
elements: [
Literal(
FStringLiteralElement {
range: 7..11,
value: "spec",
},
),
],
},
),
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,53 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 639
expression: suite
---
[
Expr(
StmtExpr {
range: 0..10,
value: FString(
ExprFString {
range: 0..10,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..10,
elements: [
Expression(
FStringExpressionElement {
range: 2..9,
expression: Name(
ExprName {
range: 3..4,
id: "x",
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: " =",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,53 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 647
expression: suite
---
[
Expr(
StmtExpr {
range: 0..10,
value: FString(
ExprFString {
range: 0..10,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..10,
elements: [
Expression(
FStringExpressionElement {
range: 2..9,
expression: Name(
ExprName {
range: 3..4,
id: "x",
ctx: Load,
},
),
debug_text: Some(
DebugText {
leading: "",
trailing: "= ",
},
),
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Regular,
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,50 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 827
expression: suite
---
[
Expr(
StmtExpr {
range: 0..7,
value: FString(
ExprFString {
range: 0..7,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..7,
elements: [
Expression(
FStringExpressionElement {
range: 3..6,
expression: Name(
ExprName {
range: 4..5,
id: "x",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
},
triple_quoted: false,
},
},
),
),
},
},
),
},
),
]

View File

@@ -0,0 +1,50 @@
---
source: crates/ruff_python_parser/src/string.rs
assertion_line: 835
expression: suite
---
[
Expr(
StmtExpr {
range: 0..11,
value: FString(
ExprFString {
range: 0..11,
value: FStringValue {
inner: Single(
FString(
FString {
range: 0..11,
elements: [
Expression(
FStringExpressionElement {
range: 5..8,
expression: Name(
ExprName {
range: 6..7,
id: "x",
ctx: Load,
},
),
debug_text: None,
conversion: None,
format_spec: None,
},
),
],
flags: FStringFlags {
quote_style: Double,
prefix: Raw {
uppercase_r: false,
},
triple_quoted: true,
},
},
),
),
},
},
),
},
),
]

View File

@@ -1,20 +1,20 @@
//! Parsing of string literals, bytes literals, and implicit string concatenation.
use bstr::ByteSlice;
use ruff_allocator::Allocator;
use ruff_python_ast::{self as ast, AnyStringFlags, Expr, StringFlags};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::error::{LexicalError, LexicalErrorType};
#[derive(Debug)]
pub(crate) enum StringType {
Str(ast::StringLiteral),
Bytes(ast::BytesLiteral),
FString(ast::FString),
pub(crate) enum StringType<'ast> {
Str(ast::StringLiteral<'ast>),
Bytes(ast::BytesLiteral<'ast>),
FString(ast::FString<'ast>),
}
impl Ranged for StringType {
impl Ranged for StringType<'_> {
fn range(&self) -> TextRange {
match self {
Self::Str(node) => node.range(),
@@ -24,8 +24,8 @@ impl Ranged for StringType {
}
}
impl From<StringType> for Expr {
fn from(string: StringType) -> Self {
impl<'ast> From<StringType<'ast>> for Expr<'ast> {
fn from(string: StringType<'ast>) -> Self {
match string {
StringType::Str(node) => Expr::from(node),
StringType::Bytes(node) => Expr::from(node),
@@ -39,9 +39,9 @@ enum EscapedChar {
Escape(char),
}
struct StringParser {
struct StringParser<'source, 'ast> {
/// The raw content of the string e.g., the `foo` part in `"foo"`.
source: Box<str>,
source: &'source str,
/// Current position of the parser in the source.
cursor: usize,
/// Flags that can be used to query information about the string.
@@ -50,21 +50,29 @@ struct StringParser {
offset: TextSize,
/// The range of the string literal.
range: TextRange,
allocator: &'ast Allocator,
}
impl StringParser {
fn new(source: Box<str>, flags: AnyStringFlags, offset: TextSize, range: TextRange) -> Self {
impl<'source, 'ast> StringParser<'source, 'ast> {
fn new(
source: &'source str,
flags: AnyStringFlags,
offset: TextSize,
range: TextRange,
allocator: &'ast Allocator,
) -> Self {
Self {
source,
cursor: 0,
flags,
offset,
range,
allocator,
}
}
#[inline]
fn skip_bytes(&mut self, bytes: usize) -> &str {
fn skip_bytes(&mut self, bytes: usize) -> &'source str {
let skipped_str = &self.source[self.cursor..self.cursor + bytes];
self.cursor += bytes;
skipped_str
@@ -232,16 +240,16 @@ impl StringParser {
Ok(Some(EscapedChar::Literal(new_char)))
}
fn parse_fstring_middle(mut self) -> Result<ast::FStringLiteralElement, LexicalError> {
fn parse_fstring_middle(mut self) -> Result<ast::FStringLiteralElement<'ast>, LexicalError> {
// Fast-path: if the f-string doesn't contain any escape sequences, return the literal.
let Some(mut index) = memchr::memchr3(b'{', b'}', b'\\', self.source.as_bytes()) else {
return Ok(ast::FStringLiteralElement {
value: self.source,
value: self.allocator.alloc_str(&self.source),
range: self.range,
});
};
let mut value = String::with_capacity(self.source.len());
let mut value = ruff_allocator::String::with_capacity_in(self.source.len(), self.allocator);
loop {
// Add the characters before the escape sequence (or curly brace) to the string.
let before_with_slash_or_brace = self.skip_bytes(index + 1);
@@ -313,12 +321,12 @@ impl StringParser {
}
Ok(ast::FStringLiteralElement {
value: value.into_boxed_str(),
value: value.into_bump_str(),
range: self.range,
})
}
fn parse_bytes(mut self) -> Result<StringType, LexicalError> {
fn parse_bytes(mut self) -> Result<StringType<'ast>, LexicalError> {
if let Some(index) = self.source.as_bytes().find_non_ascii_byte() {
let ch = self.source.chars().nth(index).unwrap();
return Err(LexicalError::new(
@@ -333,7 +341,7 @@ impl StringParser {
if self.flags.is_raw_string() {
// For raw strings, no escaping is necessary.
return Ok(StringType::Bytes(ast::BytesLiteral {
value: self.source.into_boxed_bytes(),
value: self.allocator.alloc_str(self.source).as_bytes(),
range: self.range,
flags: self.flags.into(),
}));
@@ -342,14 +350,14 @@ impl StringParser {
let Some(mut escape) = memchr::memchr(b'\\', self.source.as_bytes()) else {
// If the string doesn't contain any escape sequences, return the owned string.
return Ok(StringType::Bytes(ast::BytesLiteral {
value: self.source.into_boxed_bytes(),
value: self.allocator.alloc_str(self.source).as_bytes(),
range: self.range,
flags: self.flags.into(),
}));
};
// If the string contains escape sequences, we need to parse them.
let mut value = Vec::with_capacity(self.source.len());
let mut value = ruff_allocator::Vec::with_capacity_in(self.source.len(), self.allocator);
loop {
// Add the characters before the escape sequence to the string.
let before_with_slash = self.skip_bytes(escape + 1);
@@ -379,17 +387,17 @@ impl StringParser {
}
Ok(StringType::Bytes(ast::BytesLiteral {
value: value.into_boxed_slice(),
value: value.into_bump_slice(),
range: self.range,
flags: self.flags.into(),
}))
}
fn parse_string(mut self) -> Result<StringType, LexicalError> {
fn parse_string(mut self) -> Result<StringType<'ast>, LexicalError> {
if self.flags.is_raw_string() {
// For raw strings, no escaping is necessary.
return Ok(StringType::Str(ast::StringLiteral {
value: self.source,
value: self.allocator.alloc_str(self.source),
range: self.range,
flags: self.flags.into(),
}));
@@ -398,14 +406,14 @@ impl StringParser {
let Some(mut escape) = memchr::memchr(b'\\', self.source.as_bytes()) else {
// If the string doesn't contain any escape sequences, return the owned string.
return Ok(StringType::Str(ast::StringLiteral {
value: self.source,
value: self.allocator.alloc_str(self.source),
range: self.range,
flags: self.flags.into(),
}));
};
// If the string contains escape sequences, we need to parse them.
let mut value = String::with_capacity(self.source.len());
let mut value = ruff_allocator::String::with_capacity_in(self.source.len(), self.allocator);
loop {
// Add the characters before the escape sequence to the string.
@@ -435,13 +443,13 @@ impl StringParser {
}
Ok(StringType::Str(ast::StringLiteral {
value: value.into_boxed_str(),
value: value.into_bump_str(),
range: self.range,
flags: self.flags.into(),
}))
}
fn parse(self) -> Result<StringType, LexicalError> {
fn parse(self) -> Result<StringType<'ast>, LexicalError> {
if self.flags.is_byte_string() {
self.parse_bytes()
} else {
@@ -450,25 +458,35 @@ impl StringParser {
}
}
pub(crate) fn parse_string_literal(
source: Box<str>,
pub(crate) fn parse_string_literal<'source, 'ast>(
source: &'source str,
flags: AnyStringFlags,
range: TextRange,
) -> Result<StringType, LexicalError> {
StringParser::new(source, flags, range.start() + flags.opener_len(), range).parse()
allocator: &'ast Allocator,
) -> Result<StringType<'ast>, LexicalError> {
StringParser::new(
source,
flags,
range.start() + flags.opener_len(),
range,
allocator,
)
.parse()
}
// TODO(dhruvmanila): Move this to the new parser
pub(crate) fn parse_fstring_literal_element(
source: Box<str>,
pub(crate) fn parse_fstring_literal_element<'ast>(
source: &str,
flags: AnyStringFlags,
range: TextRange,
) -> Result<ast::FStringLiteralElement, LexicalError> {
StringParser::new(source, flags, range.start(), range).parse_fstring_middle()
allocator: &'ast Allocator,
) -> Result<ast::FStringLiteralElement<'ast>, LexicalError> {
StringParser::new(source, flags, range.start(), range, allocator).parse_fstring_middle()
}
#[cfg(test)]
mod tests {
use ruff_allocator::Allocator;
use ruff_python_ast::Suite;
use crate::error::LexicalErrorType;
@@ -478,84 +496,98 @@ mod tests {
const MAC_EOL: &str = "\r";
const UNIX_EOL: &str = "\n";
fn parse_suite(source: &str) -> Result<Suite, ParseError> {
parse_module(source).map(Parsed::into_suite)
fn parse_suite<'ast>(
source: &str,
allocator: &'ast Allocator,
) -> Result<Suite<'ast>, ParseError> {
parse_module(source, allocator).map(Parsed::into_suite)
}
fn string_parser_escaped_eol(eol: &str) -> Suite {
fn string_parser_escaped_eol<'ast>(eol: &str, allocator: &'ast Allocator) -> Suite<'ast> {
let source = format!(r"'text \{eol}more text'");
parse_suite(&source).unwrap()
parse_suite(&source, &allocator).unwrap()
}
#[test]
fn test_string_parser_escaped_unix_eol() {
let suite = string_parser_escaped_eol(UNIX_EOL);
let allocator = Allocator::new();
let suite = string_parser_escaped_eol(UNIX_EOL, &allocator);
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_string_parser_escaped_mac_eol() {
let suite = string_parser_escaped_eol(MAC_EOL);
let allocator = Allocator::new();
let suite = string_parser_escaped_eol(MAC_EOL, &allocator);
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_string_parser_escaped_windows_eol() {
let suite = string_parser_escaped_eol(WINDOWS_EOL);
let allocator = Allocator::new();
let suite = string_parser_escaped_eol(WINDOWS_EOL, &allocator);
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring() {
let source = r#"f"{a}{ b }{{foo}}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_nested_spec() {
let source = r#"f"{foo:{spec}}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_not_nested_spec() {
let source = r#"f"{foo:spec}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_empty_fstring() {
let source = r#"f"""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_fstring_parse_self_documenting_base() {
let source = r#"f"{user=}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_fstring_parse_self_documenting_base_more() {
let source = r#"f"mix {user=} with text and {second=}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_fstring_parse_self_documenting_format() {
let source = r#"f"{user=:>10}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
fn parse_fstring_error(source: &str) -> FStringErrorType {
parse_suite(source)
let allocator = Allocator::new();
parse_suite(source, &allocator)
.map_err(|e| match e.error {
ParseErrorType::Lexical(LexicalErrorType::FStringError(e)) => e,
ParseErrorType::FStringError(e) => e,
@@ -579,111 +611,127 @@ mod tests {
// error appears after the unexpected `FStringMiddle` token, which is between the
// `:` and the `{`.
// assert_eq!(parse_fstring_error("f'{lambda x: {x}}'"), LambdaWithoutParentheses);
assert!(parse_suite(r#"f"{class}""#).is_err());
let allocator = Allocator::new();
assert!(parse_suite(r#"f"{class}""#, &allocator).is_err());
}
#[test]
fn test_parse_fstring_not_equals() {
let source = r#"f"{1 != 2}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_equals() {
let source = r#"f"{42 == 42}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_self_doc_prec_space() {
let source = r#"f"{x =}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_self_doc_trailing_space() {
let source = r#"f"{x= }""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_yield_expr() {
let source = r#"f"{yield}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_string_concat() {
let source = "'Hello ' 'world'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_u_string_concat_1() {
let source = "'Hello ' u'world'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_u_string_concat_2() {
let source = "u'Hello ' 'world'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_f_string_concat_1() {
let source = "'Hello ' f'world'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_f_string_concat_2() {
let source = "'Hello ' f'world'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_f_string_concat_3() {
let source = "'Hello ' f'world{\"!\"}'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_f_string_concat_4() {
let source = "'Hello ' f'world{\"!\"}' 'again!'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_u_f_string_concat_1() {
let source = "u'Hello ' f'world'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_u_f_string_concat_2() {
let source = "u'Hello ' f'world' '!'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_string_triple_quotes_with_kind() {
let source = "u'''Hello, world!'''";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
@@ -691,7 +739,8 @@ mod tests {
fn test_single_quoted_byte() {
// single quote
let source = r##"b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'"##;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
@@ -699,7 +748,8 @@ mod tests {
fn test_double_quoted_byte() {
// double quote
let source = r##"b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff""##;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
@@ -707,42 +757,48 @@ mod tests {
fn test_escape_char_in_byte_literal() {
// backslash does not escape
let source = r#"b"omkmok\Xaa""#; // spell-checker:ignore omkmok
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_raw_byte_literal_1() {
let source = r"rb'\x1z'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_raw_byte_literal_2() {
let source = r"rb'\\'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_escape_octet() {
let source = r"b'\43a\4\1234'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_fstring_escaped_newline() {
let source = r#"f"\n{x}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_fstring_constant_range() {
let source = r#"f"aaa{bbb}ccc{ddd}eee""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
@@ -750,28 +806,32 @@ mod tests {
fn test_fstring_unescaped_newline() {
let source = r#"f"""
{x}""""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_fstring_escaped_character() {
let source = r#"f"\\{x}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_raw_fstring() {
let source = r#"rf"{x}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_triple_quoted_raw_fstring() {
let source = r#"rf"""{x}""""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
@@ -779,21 +839,24 @@ mod tests {
fn test_fstring_line_continuation() {
let source = r#"rf"\
{x}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_nested_string_spec() {
let source = r#"f"{foo:{''}}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_parse_fstring_nested_concatenation_string_spec() {
let source = r#"f"{foo:{'' ''}}""#;
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
@@ -801,42 +864,48 @@ mod tests {
#[test]
fn test_dont_panic_on_8_in_octal_escape() {
let source = r"bold = '\038[1m'";
let suite = parse_suite(source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
#[test]
fn test_invalid_unicode_literal() {
let source = r"'\x1ó34'";
let error = parse_suite(source).unwrap_err();
let allocator = Allocator::new();
let error = parse_suite(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_missing_unicode_lbrace_error() {
let source = r"'\N '";
let error = parse_suite(source).unwrap_err();
let allocator = Allocator::new();
let error = parse_suite(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_missing_unicode_rbrace_error() {
let source = r"'\N{SPACE'";
let error = parse_suite(source).unwrap_err();
let allocator = Allocator::new();
let error = parse_suite(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_invalid_unicode_name_error() {
let source = r"'\N{INVALID}'";
let error = parse_suite(source).unwrap_err();
let allocator = Allocator::new();
let error = parse_suite(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
#[test]
fn test_invalid_byte_literal_error() {
let source = r"b'123a𝐁c'";
let error = parse_suite(source).unwrap_err();
let allocator = Allocator::new();
let error = parse_suite(source, &allocator).unwrap_err();
insta::assert_debug_snapshot!(error);
}
@@ -846,7 +915,8 @@ mod tests {
#[test]
fn $name() {
let source = format!(r#""\N{{{0}}}""#, $alias);
let suite = parse_suite(&source).unwrap();
let allocator = Allocator::new();
let suite = parse_suite(&source, &allocator).unwrap();
insta::assert_debug_snapshot!(suite);
}
)*

View File

@@ -1,5 +1,6 @@
//! This module takes care of parsing a type annotation.
use ruff_allocator::Allocator;
use ruff_python_ast::relocate::relocate_expr;
use ruff_python_ast::str::raw_contents;
use ruff_python_ast::{ExprStringLiteral, ModExpression, StringFlags, StringLiteral};
@@ -31,10 +32,11 @@ impl AnnotationKind {
/// Parses the given string expression node as a type annotation. The given `source` is the entire
/// source code.
pub fn parse_type_annotation(
string_expr: &ExprStringLiteral,
source: &str,
) -> Result<(Parsed<ModExpression>, AnnotationKind), ParseError> {
pub fn parse_type_annotation<'a>(
string_expr: &ExprStringLiteral<'a>,
source: &'a str,
allocator: &'a Allocator,
) -> Result<(Parsed<ModExpression<'a>>, AnnotationKind), ParseError> {
let expr_text = &source[string_expr.range()];
if let [string_literal] = string_expr.value.as_slice() {
@@ -43,22 +45,23 @@ pub fn parse_type_annotation(
if raw_contents(expr_text)
.is_some_and(|raw_contents| raw_contents == string_literal.as_str())
{
parse_simple_type_annotation(string_literal, source)
parse_simple_type_annotation(string_literal, source, allocator)
} else {
// The raw contents of the string doesn't match the parsed content. This could be the
// case for annotations that contain escaped quotes.
parse_complex_type_annotation(string_expr)
parse_complex_type_annotation(string_expr, allocator)
}
} else {
// String is implicitly concatenated.
parse_complex_type_annotation(string_expr)
parse_complex_type_annotation(string_expr, allocator)
}
}
fn parse_simple_type_annotation(
string_literal: &StringLiteral,
source: &str,
) -> Result<(Parsed<ModExpression>, AnnotationKind), ParseError> {
fn parse_simple_type_annotation<'a>(
string_literal: &StringLiteral<'a>,
source: &'a str,
allocator: &'a Allocator,
) -> Result<(Parsed<ModExpression<'a>>, AnnotationKind), ParseError> {
Ok((
parse_expression_range(
source,
@@ -66,15 +69,17 @@ fn parse_simple_type_annotation(
.range()
.add_start(string_literal.flags.opener_len())
.sub_end(string_literal.flags.closer_len()),
allocator,
)?,
AnnotationKind::Simple,
))
}
fn parse_complex_type_annotation(
string_expr: &ExprStringLiteral,
) -> Result<(Parsed<ModExpression>, AnnotationKind), ParseError> {
let mut parsed = parse_expression(string_expr.value.to_str())?;
fn parse_complex_type_annotation<'ast>(
string_expr: &ExprStringLiteral<'_>,
allocator: &'ast Allocator,
) -> Result<(Parsed<ModExpression<'ast>>, AnnotationKind), ParseError> {
let mut parsed = parse_expression(string_expr.value.to_str(), allocator)?;
relocate_expr(parsed.expr_mut(), string_expr.range());
Ok((parsed, AnnotationKind::Complex))
}