mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 21:40:51 -05:00
[ty] Fix completion in decorators with missing declaration (#22177)
This commit is contained in:
@@ -477,6 +477,17 @@ impl<'src> Parser<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_missing_name(&mut self) -> ast::ExprName {
|
||||
let identifier = self.parse_missing_identifier();
|
||||
|
||||
ast::ExprName {
|
||||
range: identifier.range,
|
||||
id: identifier.id,
|
||||
ctx: ExprContext::Invalid,
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an identifier.
|
||||
///
|
||||
/// For an invalid identifier, the `id` field will be an empty string.
|
||||
@@ -524,16 +535,20 @@ impl<'src> Parser<'src> {
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}
|
||||
} else {
|
||||
self.add_error(
|
||||
ParseErrorType::OtherError("Expected an identifier".into()),
|
||||
range,
|
||||
);
|
||||
self.parse_missing_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
ast::Identifier {
|
||||
id: Name::empty(),
|
||||
range: self.missing_node_range(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}
|
||||
fn parse_missing_identifier(&mut self) -> ast::Identifier {
|
||||
self.add_error(
|
||||
ParseErrorType::OtherError("Expected an identifier".into()),
|
||||
self.current_token_range(),
|
||||
);
|
||||
|
||||
ast::Identifier {
|
||||
id: Name::empty(),
|
||||
range: self.missing_node_range(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2782,13 +2782,20 @@ impl<'src> Parser<'src> {
|
||||
// def foo(): ...
|
||||
// @@
|
||||
// def foo(): ...
|
||||
// @test
|
||||
// @
|
||||
// class Test
|
||||
while self.at(TokenKind::At) {
|
||||
progress.assert_progressing(self);
|
||||
|
||||
let decorator_start = self.node_start();
|
||||
self.bump(TokenKind::At);
|
||||
|
||||
let parsed_expr = self.parse_named_expression_or_higher(ExpressionContext::default());
|
||||
let parsed_expr = if self.at(TokenKind::Def) || self.at(TokenKind::Class) {
|
||||
Expr::Name(self.parse_missing_name()).into()
|
||||
} else {
|
||||
self.parse_named_expression_or_higher(ExpressionContext::default())
|
||||
};
|
||||
|
||||
if self.options.target_version < PythonVersion::PY39 {
|
||||
// test_ok decorator_expression_dotted_ident_py38
|
||||
@@ -2914,21 +2921,27 @@ impl<'src> Parser<'src> {
|
||||
self.current_token_range(),
|
||||
);
|
||||
|
||||
// TODO(dhruvmanila): It seems that this recovery drops all the parsed
|
||||
// decorators. Maybe we could convert them into statement expression
|
||||
// with a flag indicating that this expression is part of a decorator.
|
||||
// It's only possible to keep them if it's a function or class definition.
|
||||
// We could possibly keep them if there's indentation error:
|
||||
//
|
||||
// ```python
|
||||
// @decorator
|
||||
// @decorator
|
||||
// def foo(): ...
|
||||
// ```
|
||||
//
|
||||
// Or, parse it as a binary expression where the left side is missing.
|
||||
// We would need to convert each decorator into a binary expression.
|
||||
self.parse_statement()
|
||||
let range = self.node_range(start);
|
||||
|
||||
ast::StmtFunctionDef {
|
||||
node_index: AtomicNodeIndex::default(),
|
||||
range,
|
||||
is_async: false,
|
||||
decorator_list: decorators,
|
||||
name: ast::Identifier {
|
||||
id: Name::empty(),
|
||||
range: self.missing_node_range(),
|
||||
node_index: AtomicNodeIndex::NONE,
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Box::new(ast::Parameters {
|
||||
range: self.missing_node_range(),
|
||||
..ast::Parameters::default()
|
||||
}),
|
||||
returns: None,
|
||||
body: vec![],
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user