diff --git a/crates/ruff_python_parser/resources/inline/err/type_params_empty.py b/crates/ruff_python_parser/resources/inline/err/type_params_empty.py new file mode 100644 index 0000000000000..8a2342a93457b --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/type_params_empty.py @@ -0,0 +1,3 @@ +def foo[](): + pass +type ListOrSet[] = list | set diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 0cb0c2d7df659..0088d9bc8d9ad 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -99,6 +99,8 @@ pub enum ParseErrorType { EmptyDeleteTargets, /// An empty import names list was found during parsing. EmptyImportNames, + /// An empty type parameter list was found during parsing. + EmptyTypeParams, /// An unparenthesized named expression was found where it is not allowed. UnparenthesizedNamedExpression, @@ -242,6 +244,7 @@ impl std::fmt::Display for ParseErrorType { ParseErrorType::EmptyImportNames => { f.write_str("Expected one or more symbol names after import") } + ParseErrorType::EmptyTypeParams => f.write_str("Type parameter list cannot be empty"), ParseErrorType::ParamAfterVarKeywordParam => { f.write_str("Parameter cannot follow var-keyword parameter") } diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index b7733ef732d53..0ae5a02dce139 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -3027,6 +3027,14 @@ impl<'src> Parser<'src> { Parser::parse_type_param, ); + if type_params.is_empty() { + // test_err type_params_empty + // def foo[](): + // pass + // type ListOrSet[] = list | set + self.add_error(ParseErrorType::EmptyTypeParams, self.current_token_range()); + } + self.expect(TokenKind::Rsqb); ast::TypeParams { diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap new file mode 100644 index 0000000000000..3baa5f941a44f --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap @@ -0,0 +1,102 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/type_params_empty.py +--- +## AST + +``` +Module( + ModModule { + range: 0..52, + body: [ + FunctionDef( + StmtFunctionDef { + range: 0..21, + is_async: false, + decorator_list: [], + name: Identifier { + id: "foo", + range: 4..7, + }, + type_params: Some( + TypeParams { + range: 7..9, + type_params: [], + }, + ), + parameters: Parameters { + range: 9..11, + posonlyargs: [], + args: [], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Pass( + StmtPass { + range: 17..21, + }, + ), + ], + }, + ), + TypeAlias( + StmtTypeAlias { + range: 22..51, + name: Name( + ExprName { + range: 27..36, + id: "ListOrSet", + ctx: Store, + }, + ), + type_params: Some( + TypeParams { + range: 36..38, + type_params: [], + }, + ), + value: BinOp( + ExprBinOp { + range: 41..51, + left: Name( + ExprName { + range: 41..45, + id: "list", + ctx: Load, + }, + ), + op: BitOr, + right: Name( + ExprName { + range: 48..51, + id: "set", + ctx: Load, + }, + ), + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | def foo[](): + | ^ Syntax Error: Type parameter list cannot be empty +2 | pass +3 | type ListOrSet[] = list | set + | + + + | +1 | def foo[](): +2 | pass +3 | type ListOrSet[] = list | set + | ^ Syntax Error: Type parameter list cannot be empty + |