Add support for PEP 701 (#7376)

## Summary

This PR adds support for PEP 701 in Ruff. This is a rollup PR of all the
other individual PRs. The separate PRs were created for logic separation
and code reviews. Refer to each pull request for a detail description on
the change.

Refer to the PR description for the list of pull requests within this PR.

## Test Plan

### Formatter ecosystem checks

Explanation for the change in ecosystem check:
https://github.com/astral-sh/ruff/pull/7597#issue-1908878183

#### `main`

```
| project      | similarity index  | total files       | changed files     |
|--------------|------------------:|------------------:|------------------:|
| cpython      |           0.76083 |              1789 |              1631 |
| django       |           0.99983 |              2760 |                36 |
| transformers |           0.99963 |              2587 |               319 |
| twine        |           1.00000 |                33 |                 0 |
| typeshed     |           0.99983 |              3496 |                18 |
| warehouse    |           0.99967 |               648 |                15 |
| zulip        |           0.99972 |              1437 |                21 |
```

#### `dhruv/pep-701`

```
| project      | similarity index  | total files       | changed files     |
|--------------|------------------:|------------------:|------------------:|
| cpython      |           0.76051 |              1789 |              1632 |
| django       |           0.99983 |              2760 |                36 |
| transformers |           0.99963 |              2587 |               319 |
| twine        |           1.00000 |                33 |                 0 |
| typeshed     |           0.99983 |              3496 |                18 |
| warehouse    |           0.99967 |               648 |                15 |
| zulip        |           0.99972 |              1437 |                21 |
```
This commit is contained in:
Dhruv Manilawala 2023-09-29 08:25:39 +05:30 committed by GitHub
parent 78b8741352
commit e62e245c61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
115 changed files with 44780 additions and 31370 deletions

View file

@ -3,19 +3,20 @@
// See also: file:///usr/share/doc/python/html/reference/compound_stmts.html#function-definitions
// See also: https://greentreesnakes.readthedocs.io/en/latest/nodes.html#keyword
use ruff_text_size::{Ranged, TextSize};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_python_ast::{self as ast, Int, IpyEscapeKind};
use crate::{
FStringErrorType,
Mode,
lexer::{LexicalError, LexicalErrorType},
function::{ArgumentList, parse_arguments, validate_pos_params, validate_arguments},
context::set_context,
string::parse_strings,
string::{StringType, concatenate_strings, parse_fstring_middle, parse_string_literal},
token::{self, StringKind},
};
use lalrpop_util::ParseError;
grammar(mode: Mode);
grammar(source_code: &str, mode: Mode);
// This is a hack to reduce the amount of lalrpop tables generated:
// For each public entry point, a full parse table is generated.
@ -667,8 +668,8 @@ LiteralPattern: ast::Pattern = {
value: Box::new(value.into()),
range: (location..end_location).into()
}.into(),
<location:@L> <s:(@L string @R)+> <end_location:@R> =>? Ok(ast::PatternMatchValue {
value: Box::new(parse_strings(s)?),
<location:@L> <strings:StringLiteralOrFString+> <end_location:@R> =>? Ok(ast::PatternMatchValue {
value: Box::new(concatenate_strings(strings, (location..end_location).into())?),
range: (location..end_location).into()
}.into()),
}
@ -725,7 +726,7 @@ MappingKey: ast::Expr = {
value: false.into(),
range: (location..end_location).into()
}.into(),
<location:@L> <s:(@L string @R)+> =>? Ok(parse_strings(s)?),
<location:@L> <strings:StringLiteralOrFString+> <end_location:@R> =>? Ok(concatenate_strings(strings, (location..end_location).into())?),
}
MatchMappingEntry: (ast::Expr, ast::Pattern) = {
@ -1349,7 +1350,13 @@ NamedExpression: ast::ParenthesizedExpr = {
};
LambdaDef: ast::ParenthesizedExpr = {
<location:@L> "lambda" <location_args:@L> <parameters:ParameterList<UntypedParameter, StarUntypedParameter, StarUntypedParameter>?> <end_location_args:@R> ":" <body:Test<"all">> <end_location:@R> =>? {
<location:@L> "lambda" <location_args:@L> <parameters:ParameterList<UntypedParameter, StarUntypedParameter, StarUntypedParameter>?> <end_location_args:@R> ":" <fstring_middle:fstring_middle?> <body:Test<"all">> <end_location:@R> =>? {
if fstring_middle.is_some() {
return Err(LexicalError {
error: LexicalErrorType::FStringError(FStringErrorType::LambdaWithoutParentheses),
location,
})?;
}
parameters.as_ref().map(validate_arguments).transpose()?;
Ok(ast::ExprLambda {
@ -1572,8 +1579,105 @@ SliceOp: Option<ast::ParenthesizedExpr> = {
<location:@L> ":" <e:Test<"all">?> => e,
}
StringLiteralOrFString: StringType = {
StringLiteral,
FStringExpr,
};
StringLiteral: StringType = {
<start_location:@L> <string:string> =>? {
let (source, kind, triple_quoted) = string;
Ok(parse_string_literal(&source, kind, triple_quoted, start_location)?)
}
};
FStringExpr: StringType = {
<location:@L> FStringStart <values:FStringMiddlePattern*> FStringEnd <end_location:@R> => {
StringType::FString(ast::ExprFString {
values,
implicit_concatenated: false,
range: (location..end_location).into()
})
}
};
FStringMiddlePattern: ast::Expr = {
FStringReplacementField,
<start_location:@L> <fstring_middle:fstring_middle> =>? {
let (source, is_raw) = fstring_middle;
Ok(parse_fstring_middle(&source, is_raw, start_location)?)
}
};
FStringReplacementField: ast::Expr = {
<location:@L> "{" <value:TestListOrYieldExpr> <debug:"="?> <conversion:FStringConversion?> <format_spec:FStringFormatSpecSuffix?> "}" <end_location:@R> =>? {
if value.expr.is_lambda_expr() && !value.is_parenthesized() {
return Err(LexicalError {
error: LexicalErrorType::FStringError(FStringErrorType::LambdaWithoutParentheses),
location: value.start(),
})?;
}
let debug_text = debug.map(|_| {
let start_offset = location + "{".text_len();
let end_offset = if let Some((conversion_start, _)) = conversion {
conversion_start
} else {
format_spec.as_ref().map_or_else(
|| end_location - "}".text_len(),
|spec| spec.range().start() - ":".text_len(),
)
};
ast::DebugText {
leading: source_code[TextRange::new(start_offset, value.range().start())].to_string(),
trailing: source_code[TextRange::new(value.range().end(), end_offset)].to_string(),
}
});
Ok(
ast::ExprFormattedValue {
value: Box::new(value.into()),
debug_text,
conversion: conversion.map_or(ast::ConversionFlag::None, |(_, conversion_flag)| {
conversion_flag
}),
format_spec: format_spec.map(Box::new),
range: (location..end_location).into(),
}
.into()
)
}
};
FStringFormatSpecSuffix: ast::Expr = {
":" <format_spec:FStringFormatSpec> => format_spec
};
FStringFormatSpec: ast::Expr = {
<location:@L> <values:FStringMiddlePattern*> <end_location:@R> => {
ast::ExprFString {
values,
implicit_concatenated: false,
range: (location..end_location).into()
}.into()
},
};
FStringConversion: (TextSize, ast::ConversionFlag) = {
<location:@L> "!" <s:name> =>? {
let conversion = match s.as_str() {
"s" => ast::ConversionFlag::Str,
"r" => ast::ConversionFlag::Repr,
"a" => ast::ConversionFlag::Ascii,
_ => Err(LexicalError {
error: LexicalErrorType::FStringError(FStringErrorType::InvalidConversionFlag),
location,
})?
};
Ok((location, conversion))
}
};
Atom<Goal>: ast::ParenthesizedExpr = {
<location:@L> <s:(@L string @R)+> =>? Ok(parse_strings(s)?.into()),
<location:@L> <strings:StringLiteralOrFString+> <end_location:@R> =>? Ok(concatenate_strings(strings, (location..end_location).into())?.into()),
<location:@L> <value:Constant> <end_location:@R> => ast::ExprConstant {
value,
range: (location..end_location).into(),
@ -1842,6 +1946,9 @@ extern {
Dedent => token::Tok::Dedent,
StartModule => token::Tok::StartModule,
StartExpression => token::Tok::StartExpression,
FStringStart => token::Tok::FStringStart,
FStringEnd => token::Tok::FStringEnd,
"!" => token::Tok::Exclamation,
"?" => token::Tok::Question,
"+" => token::Tok::Plus,
"-" => token::Tok::Minus,
@ -1935,6 +2042,10 @@ extern {
kind: <StringKind>,
triple_quoted: <bool>
},
fstring_middle => token::Tok::FStringMiddle {
value: <String>,
is_raw: <bool>
},
name => token::Tok::Name { name: <String> },
ipy_escape_command => token::Tok::IpyEscapeCommand {
kind: <IpyEscapeKind>,