mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-22 03:15:44 +00:00
Avoid panics for implicitly concatenated forward references (#3700)
This commit is contained in:
parent
028329854b
commit
0f95056f13
6 changed files with 28 additions and 24 deletions
|
@ -8,3 +8,6 @@ def f() -> "A":
|
||||||
|
|
||||||
def g() -> "///":
|
def g() -> "///":
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
X: """List[int]"""'☃' = []
|
||||||
|
|
|
@ -5216,7 +5216,8 @@ impl<'a> Checker<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = str::raw_contents(contents);
|
// SAFETY: Safe for docstrings that pass `should_ignore_docstring`.
|
||||||
|
let body = str::raw_contents(contents).unwrap();
|
||||||
let docstring = Docstring {
|
let docstring = Docstring {
|
||||||
kind: definition.kind,
|
kind: definition.kind,
|
||||||
expr,
|
expr,
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub fn remove_unused_format_arguments_from_dict(
|
||||||
!matches!(e, DictElement::Simple {
|
!matches!(e, DictElement::Simple {
|
||||||
key: Expression::SimpleString(name),
|
key: Expression::SimpleString(name),
|
||||||
..
|
..
|
||||||
} if unused_arguments.contains(&raw_contents(name.value)))
|
} if raw_contents(name.value).map_or(false, |name| unused_arguments.contains(&name)))
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut state = CodegenState {
|
let mut state = CodegenState {
|
||||||
|
|
|
@ -15,4 +15,17 @@ expression: diagnostics
|
||||||
column: 16
|
column: 16
|
||||||
fix: ~
|
fix: ~
|
||||||
parent: ~
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
name: ForwardAnnotationSyntaxError
|
||||||
|
body: "Syntax error in forward annotation: `List[int]☃`"
|
||||||
|
suggestion: ~
|
||||||
|
fixable: false
|
||||||
|
location:
|
||||||
|
row: 13
|
||||||
|
column: 3
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 21
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
|
@ -17,25 +17,13 @@ pub const SINGLE_QUOTE_BYTE_PREFIXES: &[&str] = &[
|
||||||
const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];
|
const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];
|
||||||
const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];
|
const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];
|
||||||
|
|
||||||
/// Strip the leading and trailing quotes from a docstring.
|
/// Strip the leading and trailing quotes from a string.
|
||||||
pub fn raw_contents(contents: &str) -> &str {
|
/// Assumes that the string is a valid string literal, but does not verify that the string
|
||||||
for pattern in TRIPLE_QUOTE_STR_PREFIXES
|
/// is a "simple" string literal (i.e., that it does not contain any implicit concatenations).
|
||||||
.iter()
|
pub fn raw_contents(contents: &str) -> Option<&str> {
|
||||||
.chain(TRIPLE_QUOTE_BYTE_PREFIXES)
|
let leading_quote_str = leading_quote(contents)?;
|
||||||
{
|
let trailing_quote_str = trailing_quote(contents)?;
|
||||||
if contents.starts_with(pattern) {
|
Some(&contents[leading_quote_str.len()..contents.len() - trailing_quote_str.len()])
|
||||||
return &contents[pattern.len()..contents.len() - 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for pattern in SINGLE_QUOTE_STR_PREFIXES
|
|
||||||
.iter()
|
|
||||||
.chain(SINGLE_QUOTE_BYTE_PREFIXES)
|
|
||||||
{
|
|
||||||
if contents.starts_with(pattern) {
|
|
||||||
return &contents[pattern.len()..contents.len() - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable!("Expected docstring to start with a valid triple- or single-quote prefix")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the leading quote for a string or byte literal (e.g., `"""`).
|
/// Return the leading quote for a string or byte literal (e.g., `"""`).
|
||||||
|
|
|
@ -93,15 +93,14 @@ pub fn parse_type_annotation(
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
) -> Result<(Expr, AnnotationKind)> {
|
) -> Result<(Expr, AnnotationKind)> {
|
||||||
let expression = locator.slice(range);
|
let expression = locator.slice(range);
|
||||||
let body = str::raw_contents(expression);
|
if str::raw_contents(expression).map_or(false, |body| body == value) {
|
||||||
if body == value {
|
|
||||||
// The annotation is considered "simple" if and only if the raw representation (e.g.,
|
// The annotation is considered "simple" if and only if the raw representation (e.g.,
|
||||||
// `List[int]` within "List[int]") exactly matches the parsed representation. This
|
// `List[int]` within "List[int]") exactly matches the parsed representation. This
|
||||||
// isn't the case, e.g., for implicit concatenations, or for annotations that contain
|
// isn't the case, e.g., for implicit concatenations, or for annotations that contain
|
||||||
// escaped quotes.
|
// escaped quotes.
|
||||||
let leading_quote = str::leading_quote(expression).unwrap();
|
let leading_quote = str::leading_quote(expression).unwrap();
|
||||||
let expr = parser::parse_expression_located(
|
let expr = parser::parse_expression_located(
|
||||||
body,
|
value,
|
||||||
"<filename>",
|
"<filename>",
|
||||||
Location::new(
|
Location::new(
|
||||||
range.location.row(),
|
range.location.row(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue