From 7cb2619ef5b31fe517b7695d0eb38c25e85baaf7 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Wed, 26 Jun 2024 08:10:35 +0530 Subject: [PATCH] Add syntax error for empty type parameter list (#12030) ## Summary (I'm pretty sure I added this in the parser re-write but must've got lost in the rebase?) This PR raises a syntax error if the type parameter list is empty. As per the grammar, there should be at least one type parameter: ``` type_params: | invalid_type_params | '[' type_param_seq ']' type_param_seq: ','.type_param+ [','] ``` Verified via the builtin `ast` module as well: ```console $ python3.13 -m ast parser/_.py Traceback (most recent call last): [..] File "parser/_.py", line 1 def foo[](): ^ SyntaxError: Type parameter list cannot be empty ``` ## Test Plan Add inline test cases and update the snapshots. --- .../resources/inline/err/type_params_empty.py | 3 + crates/ruff_python_parser/src/error.rs | 3 + .../src/parser/statement.rs | 8 ++ .../invalid_syntax@type_params_empty.py.snap | 102 ++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 crates/ruff_python_parser/resources/inline/err/type_params_empty.py create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_empty.py.snap 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 + |