From 17ccf3762e4dc06e9f22517735b505ea1e042b96 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Mon, 3 Mar 2025 14:04:50 -0500 Subject: [PATCH 01/10] [syntax-errors] Type parameter lists before Python 3.12 Summary -- Another simple one, just detect type parameter lists in `type` statements, functions, and classes. [pyright] only reports the `type` statement in the alias case, whereas we'll currently report both diagnostics, in combination with Test Plan -- Inline tests. [pyright]: https://pyright-play.net/?pythonVersion=3.8&strict=true&code=C4TwDgpgBAHg2gFQLpQLxQJYDthA --- .../resources/inline/err/type_params_py311.py | 4 + .../resources/inline/ok/type_params_py312.py | 4 + crates/ruff_python_parser/src/error.rs | 3 + .../src/parser/statement.rs | 20 +- .../invalid_syntax@type_params_py311.py.snap | 182 ++++++++++++++++++ .../valid_syntax@type_params_py312.py.snap | 156 +++++++++++++++ 6 files changed, 365 insertions(+), 4 deletions(-) create mode 100644 crates/ruff_python_parser/resources/inline/err/type_params_py311.py create mode 100644 crates/ruff_python_parser/resources/inline/ok/type_params_py312.py create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap diff --git a/crates/ruff_python_parser/resources/inline/err/type_params_py311.py b/crates/ruff_python_parser/resources/inline/err/type_params_py311.py new file mode 100644 index 0000000000000..e8edd544a20e0 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/type_params_py311.py @@ -0,0 +1,4 @@ +# parse_options: {"target-version": "3.11"} +type List[T] = list | set +def foo[T](): ... +class Foo[T]: ... diff --git a/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py b/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py new file mode 100644 index 0000000000000..77e04ba9cb89d --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py @@ -0,0 +1,4 @@ +# parse_options: {"target-version": "3.12"} +type List[T] = list | set +def foo[T](): ... +class Foo[T]: ... diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 191068c2a1128..ed2d6116869ad 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -449,6 +449,7 @@ pub enum UnsupportedSyntaxErrorKind { Match, Walrus, ExceptStar, + TypeParams, } impl Display for UnsupportedSyntaxError { @@ -457,6 +458,7 @@ impl Display for UnsupportedSyntaxError { UnsupportedSyntaxErrorKind::Match => "`match` statement", UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)", UnsupportedSyntaxErrorKind::ExceptStar => "`except*`", + UnsupportedSyntaxErrorKind::TypeParams => "type parameter lists", }; write!( f, @@ -474,6 +476,7 @@ impl UnsupportedSyntaxErrorKind { UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310, UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38, UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311, + UnsupportedSyntaxErrorKind::TypeParams => PythonVersion::PY312, } } } diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 5f7f870a00c2f..8bb8ae0fa1abc 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -3106,10 +3106,22 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Rsqb); - ast::TypeParams { - range: self.node_range(start), - type_params, - } + // test_ok type_params_py312 + // # parse_options: {"target-version": "3.12"} + // type List[T] = list | set + // def foo[T](): ... + // class Foo[T]: ... + + // test_err type_params_py311 + // # parse_options: {"target-version": "3.11"} + // type List[T] = list | set + // def foo[T](): ... + // class Foo[T]: ... + + let range = self.node_range(start); + self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::TypeParams, range); + + ast::TypeParams { range, type_params } } /// Parses a type parameter. diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap new file mode 100644 index 0000000000000..7683edfe614db --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap @@ -0,0 +1,182 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/type_params_py311.py +--- +## AST + +``` +Module( + ModModule { + range: 0..106, + body: [ + TypeAlias( + StmtTypeAlias { + range: 44..69, + name: Name( + ExprName { + range: 49..53, + id: Name("List"), + ctx: Store, + }, + ), + type_params: Some( + TypeParams { + range: 53..56, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 54..55, + name: Identifier { + id: Name("T"), + range: 54..55, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + value: BinOp( + ExprBinOp { + range: 59..69, + left: Name( + ExprName { + range: 59..63, + id: Name("list"), + ctx: Load, + }, + ), + op: BitOr, + right: Name( + ExprName { + range: 66..69, + id: Name("set"), + ctx: Load, + }, + ), + }, + ), + }, + ), + FunctionDef( + StmtFunctionDef { + range: 70..87, + is_async: false, + decorator_list: [], + name: Identifier { + id: Name("foo"), + range: 74..77, + }, + type_params: Some( + TypeParams { + range: 77..80, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 78..79, + name: Identifier { + id: Name("T"), + range: 78..79, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + parameters: Parameters { + range: 80..82, + posonlyargs: [], + args: [], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Expr( + StmtExpr { + range: 84..87, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 84..87, + }, + ), + }, + ), + ], + }, + ), + ClassDef( + StmtClassDef { + range: 88..105, + decorator_list: [], + name: Identifier { + id: Name("Foo"), + range: 94..97, + }, + type_params: Some( + TypeParams { + range: 97..100, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 98..99, + name: Identifier { + id: Name("T"), + range: 98..99, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + arguments: None, + body: [ + Expr( + StmtExpr { + range: 102..105, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 102..105, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.11"} +2 | type List[T] = list | set + | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) +3 | def foo[T](): ... +4 | class Foo[T]: ... + | + + + | +1 | # parse_options: {"target-version": "3.11"} +2 | type List[T] = list | set +3 | def foo[T](): ... + | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) +4 | class Foo[T]: ... + | + + + | +2 | type List[T] = list | set +3 | def foo[T](): ... +4 | class Foo[T]: ... + | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap new file mode 100644 index 0000000000000..c17833e122d00 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap @@ -0,0 +1,156 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/type_params_py312.py +--- +## AST + +``` +Module( + ModModule { + range: 0..106, + body: [ + TypeAlias( + StmtTypeAlias { + range: 44..69, + name: Name( + ExprName { + range: 49..53, + id: Name("List"), + ctx: Store, + }, + ), + type_params: Some( + TypeParams { + range: 53..56, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 54..55, + name: Identifier { + id: Name("T"), + range: 54..55, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + value: BinOp( + ExprBinOp { + range: 59..69, + left: Name( + ExprName { + range: 59..63, + id: Name("list"), + ctx: Load, + }, + ), + op: BitOr, + right: Name( + ExprName { + range: 66..69, + id: Name("set"), + ctx: Load, + }, + ), + }, + ), + }, + ), + FunctionDef( + StmtFunctionDef { + range: 70..87, + is_async: false, + decorator_list: [], + name: Identifier { + id: Name("foo"), + range: 74..77, + }, + type_params: Some( + TypeParams { + range: 77..80, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 78..79, + name: Identifier { + id: Name("T"), + range: 78..79, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + parameters: Parameters { + range: 80..82, + posonlyargs: [], + args: [], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Expr( + StmtExpr { + range: 84..87, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 84..87, + }, + ), + }, + ), + ], + }, + ), + ClassDef( + StmtClassDef { + range: 88..105, + decorator_list: [], + name: Identifier { + id: Name("Foo"), + range: 94..97, + }, + type_params: Some( + TypeParams { + range: 97..100, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 98..99, + name: Identifier { + id: Name("T"), + range: 98..99, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + arguments: None, + body: [ + Expr( + StmtExpr { + range: 102..105, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 102..105, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +``` From 5686e97a73e50b6be8beeff5a1be9d299e0ad0df Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 4 Mar 2025 12:29:21 -0500 Subject: [PATCH 02/10] document variant --- crates/ruff_python_parser/src/error.rs | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index ed2d6116869ad..f7e8e9b4e41a0 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -449,6 +449,34 @@ pub enum UnsupportedSyntaxErrorKind { Match, Walrus, ExceptStar, + /// Represents the use of a [type parameter list] before Python 3.12. + /// + /// ## Examples + /// + /// Before Python 3.12, generic parameters had to be declared separately using a class like + /// [`typing.TypeVar`], which could then be used in a function or class definition: + /// + /// ```python + /// from typing import Generic, TypeVar + /// + /// T = TypeVar("T") + /// + /// def f(t: T): ... + /// class C(Generic[T]): ... + /// ``` + /// + /// [PEP 695], included in Python 3.12, introduced the new type parameter syntax, which allows + /// these to be written more compactly and without a separate type variable: + /// + /// ```python + /// def f[T](t: T): ... + /// class C[T]: ... + /// ``` + /// + /// [type parameter list]: + /// https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists + /// [PEP 695]: https://peps.python.org/pep-0695/ + /// [`typing.TypeVar`]: https://docs.python.org/3/library/typing.html#typevar TypeParams, } From 1b259f106b0b16d7ee9fcf4ab9e3a2387ba47a6d Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 4 Mar 2025 12:29:55 -0500 Subject: [PATCH 03/10] TypeParams -> TypeParameterList --- crates/ruff_python_parser/src/error.rs | 6 +++--- crates/ruff_python_parser/src/parser/statement.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index f7e8e9b4e41a0..03949f394254a 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -477,7 +477,7 @@ pub enum UnsupportedSyntaxErrorKind { /// https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists /// [PEP 695]: https://peps.python.org/pep-0695/ /// [`typing.TypeVar`]: https://docs.python.org/3/library/typing.html#typevar - TypeParams, + TypeParameterList, } impl Display for UnsupportedSyntaxError { @@ -486,7 +486,7 @@ impl Display for UnsupportedSyntaxError { UnsupportedSyntaxErrorKind::Match => "`match` statement", UnsupportedSyntaxErrorKind::Walrus => "named assignment expression (`:=`)", UnsupportedSyntaxErrorKind::ExceptStar => "`except*`", - UnsupportedSyntaxErrorKind::TypeParams => "type parameter lists", + UnsupportedSyntaxErrorKind::TypeParameterList => "type parameter lists", }; write!( f, @@ -504,7 +504,7 @@ impl UnsupportedSyntaxErrorKind { UnsupportedSyntaxErrorKind::Match => PythonVersion::PY310, UnsupportedSyntaxErrorKind::Walrus => PythonVersion::PY38, UnsupportedSyntaxErrorKind::ExceptStar => PythonVersion::PY311, - UnsupportedSyntaxErrorKind::TypeParams => PythonVersion::PY312, + UnsupportedSyntaxErrorKind::TypeParameterList => PythonVersion::PY312, } } } diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 8bb8ae0fa1abc..486bcd721368d 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -3119,7 +3119,7 @@ impl<'src> Parser<'src> { // class Foo[T]: ... let range = self.node_range(start); - self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::TypeParams, range); + self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::TypeParameterList, range); ast::TypeParams { range, type_params } } From 6bc987669e0ffbc30aca771354dc45bc078c993e Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 4 Mar 2025 12:43:31 -0500 Subject: [PATCH 04/10] include multiple type parameters (and all kinds) in class test --- .../resources/inline/err/type_params_py311.py | 2 +- .../resources/inline/ok/type_params_py312.py | 2 +- .../src/parser/statement.rs | 4 +- .../invalid_syntax@type_params_py311.py.snap | 106 ++++++++++++++++-- .../valid_syntax@type_params_py312.py.snap | 98 ++++++++++++++-- 5 files changed, 188 insertions(+), 24 deletions(-) diff --git a/crates/ruff_python_parser/resources/inline/err/type_params_py311.py b/crates/ruff_python_parser/resources/inline/err/type_params_py311.py index e8edd544a20e0..de87a649c6926 100644 --- a/crates/ruff_python_parser/resources/inline/err/type_params_py311.py +++ b/crates/ruff_python_parser/resources/inline/err/type_params_py311.py @@ -1,4 +1,4 @@ # parse_options: {"target-version": "3.11"} type List[T] = list | set def foo[T](): ... -class Foo[T]: ... +class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... diff --git a/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py b/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py index 77e04ba9cb89d..2e36bc9b71dc0 100644 --- a/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py +++ b/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py @@ -1,4 +1,4 @@ # parse_options: {"target-version": "3.12"} type List[T] = list | set def foo[T](): ... -class Foo[T]: ... +class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 486bcd721368d..f93c746c113d5 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -3110,13 +3110,13 @@ impl<'src> Parser<'src> { // # parse_options: {"target-version": "3.12"} // type List[T] = list | set // def foo[T](): ... - // class Foo[T]: ... + // class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... // test_err type_params_py311 // # parse_options: {"target-version": "3.11"} // type List[T] = list | set // def foo[T](): ... - // class Foo[T]: ... + // class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... let range = self.node_range(start); self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::TypeParameterList, range); diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap index 7683edfe614db..cf06ea1a3e46b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap @@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_params_py311.py ``` Module( ModModule { - range: 0..106, + range: 0..149, body: [ TypeAlias( StmtTypeAlias { @@ -111,7 +111,7 @@ Module( ), ClassDef( StmtClassDef { - range: 88..105, + range: 88..148, decorator_list: [], name: Identifier { id: Name("Foo"), @@ -119,19 +119,101 @@ Module( }, type_params: Some( TypeParams { - range: 97..100, + range: 97..143, type_params: [ TypeVar( TypeParamTypeVar { - range: 98..99, + range: 98..113, name: Identifier { - id: Name("T"), + id: Name("S"), range: 98..99, }, - bound: None, + bound: Some( + Tuple( + ExprTuple { + range: 101..113, + elts: [ + Name( + ExprName { + range: 102..105, + id: Name("str"), + ctx: Load, + }, + ), + Name( + ExprName { + range: 107..112, + id: Name("bytes"), + ctx: Load, + }, + ), + ], + ctx: Load, + parenthesized: true, + }, + ), + ), + default: None, + }, + ), + TypeVar( + TypeParamTypeVar { + range: 115..123, + name: Identifier { + id: Name("T"), + range: 115..116, + }, + bound: Some( + Name( + ExprName { + range: 118..123, + id: Name("float"), + ctx: Load, + }, + ), + ), + default: None, + }, + ), + TypeVarTuple( + TypeParamTypeVarTuple { + range: 125..128, + name: Identifier { + id: Name("Ts"), + range: 126..128, + }, default: None, }, ), + ParamSpec( + TypeParamParamSpec { + range: 130..133, + name: Identifier { + id: Name("P"), + range: 132..133, + }, + default: None, + }, + ), + TypeVar( + TypeParamTypeVar { + range: 135..142, + name: Identifier { + id: Name("D"), + range: 135..136, + }, + bound: None, + default: Some( + Name( + ExprName { + range: 139..142, + id: Name("int"), + ctx: Load, + }, + ), + ), + }, + ), ], }, ), @@ -139,10 +221,10 @@ Module( body: [ Expr( StmtExpr { - range: 102..105, + range: 145..148, value: EllipsisLiteral( ExprEllipsisLiteral { - range: 102..105, + range: 145..148, }, ), }, @@ -161,7 +243,7 @@ Module( 2 | type List[T] = list | set | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) 3 | def foo[T](): ... -4 | class Foo[T]: ... +4 | class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... | @@ -170,13 +252,13 @@ Module( 2 | type List[T] = list | set 3 | def foo[T](): ... | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) -4 | class Foo[T]: ... +4 | class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... | | 2 | type List[T] = list | set 3 | def foo[T](): ... -4 | class Foo[T]: ... - | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) +4 | class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap index c17833e122d00..7ee3de3d4a1c2 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap @@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/ok/type_params_py312.py ``` Module( ModModule { - range: 0..106, + range: 0..149, body: [ TypeAlias( StmtTypeAlias { @@ -111,7 +111,7 @@ Module( ), ClassDef( StmtClassDef { - range: 88..105, + range: 88..148, decorator_list: [], name: Identifier { id: Name("Foo"), @@ -119,19 +119,101 @@ Module( }, type_params: Some( TypeParams { - range: 97..100, + range: 97..143, type_params: [ TypeVar( TypeParamTypeVar { - range: 98..99, + range: 98..113, name: Identifier { - id: Name("T"), + id: Name("S"), range: 98..99, }, - bound: None, + bound: Some( + Tuple( + ExprTuple { + range: 101..113, + elts: [ + Name( + ExprName { + range: 102..105, + id: Name("str"), + ctx: Load, + }, + ), + Name( + ExprName { + range: 107..112, + id: Name("bytes"), + ctx: Load, + }, + ), + ], + ctx: Load, + parenthesized: true, + }, + ), + ), + default: None, + }, + ), + TypeVar( + TypeParamTypeVar { + range: 115..123, + name: Identifier { + id: Name("T"), + range: 115..116, + }, + bound: Some( + Name( + ExprName { + range: 118..123, + id: Name("float"), + ctx: Load, + }, + ), + ), default: None, }, ), + TypeVarTuple( + TypeParamTypeVarTuple { + range: 125..128, + name: Identifier { + id: Name("Ts"), + range: 126..128, + }, + default: None, + }, + ), + ParamSpec( + TypeParamParamSpec { + range: 130..133, + name: Identifier { + id: Name("P"), + range: 132..133, + }, + default: None, + }, + ), + TypeVar( + TypeParamTypeVar { + range: 135..142, + name: Identifier { + id: Name("D"), + range: 135..136, + }, + bound: None, + default: Some( + Name( + ExprName { + range: 139..142, + id: Name("int"), + ctx: Load, + }, + ), + ), + }, + ), ], }, ), @@ -139,10 +221,10 @@ Module( body: [ Expr( StmtExpr { - range: 102..105, + range: 145..148, value: EllipsisLiteral( ExprEllipsisLiteral { - range: 102..105, + range: 145..148, }, ), }, From ed689c7b96304f9032b263d8f99c87329be04856 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 4 Mar 2025 12:48:26 -0500 Subject: [PATCH 05/10] revert default addition after merge --- .../resources/inline/err/type_params_py311.py | 2 +- .../resources/inline/ok/type_params_py312.py | 2 +- .../src/parser/statement.rs | 4 +- .../invalid_syntax@type_params_py311.py.snap | 46 ++++++++----------- .../valid_syntax@type_params_py312.py.snap | 29 ++---------- 5 files changed, 27 insertions(+), 56 deletions(-) diff --git a/crates/ruff_python_parser/resources/inline/err/type_params_py311.py b/crates/ruff_python_parser/resources/inline/err/type_params_py311.py index de87a649c6926..7f14833aa36f8 100644 --- a/crates/ruff_python_parser/resources/inline/err/type_params_py311.py +++ b/crates/ruff_python_parser/resources/inline/err/type_params_py311.py @@ -1,4 +1,4 @@ # parse_options: {"target-version": "3.11"} type List[T] = list | set def foo[T](): ... -class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... +class Foo[S: (str, bytes), T: float, *Ts, **P]: ... diff --git a/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py b/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py index 2e36bc9b71dc0..57ccfed897225 100644 --- a/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py +++ b/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py @@ -1,4 +1,4 @@ # parse_options: {"target-version": "3.12"} type List[T] = list | set def foo[T](): ... -class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... +class Foo[S: (str, bytes), T: float, *Ts, **P]: ... diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index a551faa12eb96..d101e38655e55 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -3125,13 +3125,13 @@ impl<'src> Parser<'src> { // # parse_options: {"target-version": "3.12"} // type List[T] = list | set // def foo[T](): ... - // class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... + // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... // test_err type_params_py311 // # parse_options: {"target-version": "3.11"} // type List[T] = list | set // def foo[T](): ... - // class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... + // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... let range = self.node_range(start); self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::TypeParameterList, range); diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap index cf06ea1a3e46b..64173609e0cd8 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap @@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/err/type_params_py311.py ``` Module( ModModule { - range: 0..149, + range: 0..140, body: [ TypeAlias( StmtTypeAlias { @@ -111,7 +111,7 @@ Module( ), ClassDef( StmtClassDef { - range: 88..148, + range: 88..139, decorator_list: [], name: Identifier { id: Name("Foo"), @@ -119,7 +119,7 @@ Module( }, type_params: Some( TypeParams { - range: 97..143, + range: 97..134, type_params: [ TypeVar( TypeParamTypeVar { @@ -195,25 +195,6 @@ Module( default: None, }, ), - TypeVar( - TypeParamTypeVar { - range: 135..142, - name: Identifier { - id: Name("D"), - range: 135..136, - }, - bound: None, - default: Some( - Name( - ExprName { - range: 139..142, - id: Name("int"), - ctx: Load, - }, - ), - ), - }, - ), ], }, ), @@ -221,10 +202,10 @@ Module( body: [ Expr( StmtExpr { - range: 145..148, + range: 136..139, value: EllipsisLiteral( ExprEllipsisLiteral { - range: 145..148, + range: 136..139, }, ), }, @@ -238,12 +219,21 @@ Module( ``` ## Unsupported Syntax Errors + | +1 | # parse_options: {"target-version": "3.11"} +2 | type List[T] = list | set + | ^^^^ Syntax Error: Cannot use `type` alias statement on Python 3.11 (syntax was added in Python 3.12) +3 | def foo[T](): ... +4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... + | + + | 1 | # parse_options: {"target-version": "3.11"} 2 | type List[T] = list | set | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) 3 | def foo[T](): ... -4 | class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... +4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... | @@ -252,13 +242,13 @@ Module( 2 | type List[T] = list | set 3 | def foo[T](): ... | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) -4 | class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... +4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... | | 2 | type List[T] = list | set 3 | def foo[T](): ... -4 | class Foo[S: (str, bytes), T: float, *Ts, **P, D = int]: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) +4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap index 7ee3de3d4a1c2..8a529a75a2427 100644 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap @@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/ok/type_params_py312.py ``` Module( ModModule { - range: 0..149, + range: 0..140, body: [ TypeAlias( StmtTypeAlias { @@ -111,7 +111,7 @@ Module( ), ClassDef( StmtClassDef { - range: 88..148, + range: 88..139, decorator_list: [], name: Identifier { id: Name("Foo"), @@ -119,7 +119,7 @@ Module( }, type_params: Some( TypeParams { - range: 97..143, + range: 97..134, type_params: [ TypeVar( TypeParamTypeVar { @@ -195,25 +195,6 @@ Module( default: None, }, ), - TypeVar( - TypeParamTypeVar { - range: 135..142, - name: Identifier { - id: Name("D"), - range: 135..136, - }, - bound: None, - default: Some( - Name( - ExprName { - range: 139..142, - id: Name("int"), - ctx: Load, - }, - ), - ), - }, - ), ], }, ), @@ -221,10 +202,10 @@ Module( body: [ Expr( StmtExpr { - range: 145..148, + range: 136..139, value: EllipsisLiteral( ExprEllipsisLiteral { - range: 145..148, + range: 136..139, }, ), }, From 83b8d5600a285ffd3e5fbe56edaf5253d07ab898 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 4 Mar 2025 12:59:45 -0500 Subject: [PATCH 06/10] don't add a second error for type alias statements, reorganize tests --- ...ms_py311.py => class_type_params_py311.py} | 2 - .../inline/err/function_type_params_py311.py | 2 + ...ms_py312.py => class_type_params_py312.py} | 2 - .../inline/ok/function_type_params_py312.py | 2 + .../src/parser/statement.rs | 56 +++++--- ...lid_syntax@class_type_params_py311.py.snap | 126 ++++++++++++++++++ ..._syntax@function_type_params_py311.py.snap | 72 ++++++++++ .../invalid_syntax@type_params_py311.py.snap | 9 -- ...lid_syntax@class_type_params_py312.py.snap | 119 +++++++++++++++++ ..._syntax@function_type_params_py312.py.snap | 65 +++++++++ 10 files changed, 426 insertions(+), 29 deletions(-) rename crates/ruff_python_parser/resources/inline/err/{type_params_py311.py => class_type_params_py311.py} (68%) create mode 100644 crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py rename crates/ruff_python_parser/resources/inline/ok/{type_params_py312.py => class_type_params_py312.py} (68%) create mode 100644 crates/ruff_python_parser/resources/inline/ok/function_type_params_py312.py create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap diff --git a/crates/ruff_python_parser/resources/inline/err/type_params_py311.py b/crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py similarity index 68% rename from crates/ruff_python_parser/resources/inline/err/type_params_py311.py rename to crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py index 7f14833aa36f8..158b0f26be4f6 100644 --- a/crates/ruff_python_parser/resources/inline/err/type_params_py311.py +++ b/crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py @@ -1,4 +1,2 @@ # parse_options: {"target-version": "3.11"} -type List[T] = list | set -def foo[T](): ... class Foo[S: (str, bytes), T: float, *Ts, **P]: ... diff --git a/crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py b/crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py new file mode 100644 index 0000000000000..06073ce5ae345 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.11"} +def foo[T](): ... diff --git a/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py b/crates/ruff_python_parser/resources/inline/ok/class_type_params_py312.py similarity index 68% rename from crates/ruff_python_parser/resources/inline/ok/type_params_py312.py rename to crates/ruff_python_parser/resources/inline/ok/class_type_params_py312.py index 57ccfed897225..c167ad6d56e0e 100644 --- a/crates/ruff_python_parser/resources/inline/ok/type_params_py312.py +++ b/crates/ruff_python_parser/resources/inline/ok/class_type_params_py312.py @@ -1,4 +1,2 @@ # parse_options: {"target-version": "3.12"} -type List[T] = list | set -def foo[T](): ... class Foo[S: (str, bytes), T: float, *Ts, **P]: ... diff --git a/crates/ruff_python_parser/resources/inline/ok/function_type_params_py312.py b/crates/ruff_python_parser/resources/inline/ok/function_type_params_py312.py new file mode 100644 index 0000000000000..32f36b0be02c5 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/function_type_params_py312.py @@ -0,0 +1,2 @@ +# parse_options: {"target-version": "3.12"} +def foo[T](): ... diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index d101e38655e55..5c00ef67ad9fe 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -1786,6 +1786,24 @@ impl<'src> Parser<'src> { // x = 10 let type_params = self.try_parse_type_params(); + // test_ok function_type_params_py312 + // # parse_options: {"target-version": "3.12"} + // def foo[T](): ... + + // test_err function_type_params_py311 + // # parse_options: {"target-version": "3.11"} + // def foo[T](): ... + if let Some(ast::TypeParams { range, type_params }) = &type_params { + // Only emit the `ParseError` for an empty parameter list instead of also including an + // `UnsupportedSyntaxError`. + if !type_params.is_empty() { + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::TypeParameterList, + *range, + ); + } + } + // test_ok function_def_parameter_range // def foo( // first: int, @@ -1900,6 +1918,24 @@ impl<'src> Parser<'src> { // x = 10 let type_params = self.try_parse_type_params(); + // test_ok class_type_params_py312 + // # parse_options: {"target-version": "3.12"} + // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... + + // test_err class_type_params_py311 + // # parse_options: {"target-version": "3.11"} + // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... + if let Some(ast::TypeParams { range, type_params }) = &type_params { + // Only emit the `ParseError` for an empty parameter list instead of also including an + // `UnsupportedSyntaxError`. + if !type_params.is_empty() { + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::TypeParameterList, + *range, + ); + } + } + // test_ok class_def_arguments // class Foo: ... // class Foo(): ... @@ -3121,22 +3157,10 @@ impl<'src> Parser<'src> { self.expect(TokenKind::Rsqb); - // test_ok type_params_py312 - // # parse_options: {"target-version": "3.12"} - // type List[T] = list | set - // def foo[T](): ... - // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... - - // test_err type_params_py311 - // # parse_options: {"target-version": "3.11"} - // type List[T] = list | set - // def foo[T](): ... - // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... - - let range = self.node_range(start); - self.add_unsupported_syntax_error(UnsupportedSyntaxErrorKind::TypeParameterList, range); - - ast::TypeParams { range, type_params } + ast::TypeParams { + range: self.node_range(start), + type_params, + } } /// Parses a type parameter. diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap new file mode 100644 index 0000000000000..2066b6b09edaa --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap @@ -0,0 +1,126 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py +--- +## AST + +``` +Module( + ModModule { + range: 0..96, + body: [ + ClassDef( + StmtClassDef { + range: 44..95, + decorator_list: [], + name: Identifier { + id: Name("Foo"), + range: 50..53, + }, + type_params: Some( + TypeParams { + range: 53..90, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 54..69, + name: Identifier { + id: Name("S"), + range: 54..55, + }, + bound: Some( + Tuple( + ExprTuple { + range: 57..69, + elts: [ + Name( + ExprName { + range: 58..61, + id: Name("str"), + ctx: Load, + }, + ), + Name( + ExprName { + range: 63..68, + id: Name("bytes"), + ctx: Load, + }, + ), + ], + ctx: Load, + parenthesized: true, + }, + ), + ), + default: None, + }, + ), + TypeVar( + TypeParamTypeVar { + range: 71..79, + name: Identifier { + id: Name("T"), + range: 71..72, + }, + bound: Some( + Name( + ExprName { + range: 74..79, + id: Name("float"), + ctx: Load, + }, + ), + ), + default: None, + }, + ), + TypeVarTuple( + TypeParamTypeVarTuple { + range: 81..84, + name: Identifier { + id: Name("Ts"), + range: 82..84, + }, + default: None, + }, + ), + ParamSpec( + TypeParamParamSpec { + range: 86..89, + name: Identifier { + id: Name("P"), + range: 88..89, + }, + default: None, + }, + ), + ], + }, + ), + arguments: None, + body: [ + Expr( + StmtExpr { + range: 92..95, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 92..95, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.11"} +2 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap new file mode 100644 index 0000000000000..58a0da558dc65 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap @@ -0,0 +1,72 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py +--- +## AST + +``` +Module( + ModModule { + range: 0..62, + body: [ + FunctionDef( + StmtFunctionDef { + range: 44..61, + is_async: false, + decorator_list: [], + name: Identifier { + id: Name("foo"), + range: 48..51, + }, + type_params: Some( + TypeParams { + range: 51..54, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 52..53, + name: Identifier { + id: Name("T"), + range: 52..53, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + parameters: Parameters { + range: 54..56, + posonlyargs: [], + args: [], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Expr( + StmtExpr { + range: 58..61, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 58..61, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +``` +## Unsupported Syntax Errors + + | +1 | # parse_options: {"target-version": "3.11"} +2 | def foo[T](): ... + | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap index 64173609e0cd8..39fbf89f77f83 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap @@ -228,15 +228,6 @@ Module( | - | -1 | # parse_options: {"target-version": "3.11"} -2 | type List[T] = list | set - | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) -3 | def foo[T](): ... -4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... - | - - | 1 | # parse_options: {"target-version": "3.11"} 2 | type List[T] = list | set diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap new file mode 100644 index 0000000000000..8ca2d420378d4 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_type_params_py312.py.snap @@ -0,0 +1,119 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/class_type_params_py312.py +--- +## AST + +``` +Module( + ModModule { + range: 0..96, + body: [ + ClassDef( + StmtClassDef { + range: 44..95, + decorator_list: [], + name: Identifier { + id: Name("Foo"), + range: 50..53, + }, + type_params: Some( + TypeParams { + range: 53..90, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 54..69, + name: Identifier { + id: Name("S"), + range: 54..55, + }, + bound: Some( + Tuple( + ExprTuple { + range: 57..69, + elts: [ + Name( + ExprName { + range: 58..61, + id: Name("str"), + ctx: Load, + }, + ), + Name( + ExprName { + range: 63..68, + id: Name("bytes"), + ctx: Load, + }, + ), + ], + ctx: Load, + parenthesized: true, + }, + ), + ), + default: None, + }, + ), + TypeVar( + TypeParamTypeVar { + range: 71..79, + name: Identifier { + id: Name("T"), + range: 71..72, + }, + bound: Some( + Name( + ExprName { + range: 74..79, + id: Name("float"), + ctx: Load, + }, + ), + ), + default: None, + }, + ), + TypeVarTuple( + TypeParamTypeVarTuple { + range: 81..84, + name: Identifier { + id: Name("Ts"), + range: 82..84, + }, + default: None, + }, + ), + ParamSpec( + TypeParamParamSpec { + range: 86..89, + name: Identifier { + id: Name("P"), + range: 88..89, + }, + default: None, + }, + ), + ], + }, + ), + arguments: None, + body: [ + Expr( + StmtExpr { + range: 92..95, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 92..95, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +``` diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap new file mode 100644 index 0000000000000..ed6c0528a6835 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@function_type_params_py312.py.snap @@ -0,0 +1,65 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/function_type_params_py312.py +--- +## AST + +``` +Module( + ModModule { + range: 0..62, + body: [ + FunctionDef( + StmtFunctionDef { + range: 44..61, + is_async: false, + decorator_list: [], + name: Identifier { + id: Name("foo"), + range: 48..51, + }, + type_params: Some( + TypeParams { + range: 51..54, + type_params: [ + TypeVar( + TypeParamTypeVar { + range: 52..53, + name: Identifier { + id: Name("T"), + range: 52..53, + }, + bound: None, + default: None, + }, + ), + ], + }, + ), + parameters: Parameters { + range: 54..56, + posonlyargs: [], + args: [], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Expr( + StmtExpr { + range: 58..61, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 58..61, + }, + ), + }, + ), + ], + }, + ), + ], + }, +) +``` From fc35635a2b4330be66dcbc0d61fd0a7953e121dc Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 4 Mar 2025 13:04:54 -0500 Subject: [PATCH 07/10] test that empty cases only give a ParseError --- .../inline/err/class_type_params_py311.py | 1 + .../inline/err/function_type_params_py311.py | 1 + .../src/parser/statement.rs | 2 + ...lid_syntax@class_type_params_py311.py.snap | 42 ++++++++++++++- ..._syntax@function_type_params_py311.py.snap | 51 ++++++++++++++++++- 5 files changed, 95 insertions(+), 2 deletions(-) diff --git a/crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py b/crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py index 158b0f26be4f6..31cf933a6eb3f 100644 --- a/crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py +++ b/crates/ruff_python_parser/resources/inline/err/class_type_params_py311.py @@ -1,2 +1,3 @@ # parse_options: {"target-version": "3.11"} class Foo[S: (str, bytes), T: float, *Ts, **P]: ... +class Foo[]: ... diff --git a/crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py b/crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py index 06073ce5ae345..2d9604058c46b 100644 --- a/crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py +++ b/crates/ruff_python_parser/resources/inline/err/function_type_params_py311.py @@ -1,2 +1,3 @@ # parse_options: {"target-version": "3.11"} def foo[T](): ... +def foo[](): ... diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 5c00ef67ad9fe..39b40f19b172c 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -1793,6 +1793,7 @@ impl<'src> Parser<'src> { // test_err function_type_params_py311 // # parse_options: {"target-version": "3.11"} // def foo[T](): ... + // def foo[](): ... if let Some(ast::TypeParams { range, type_params }) = &type_params { // Only emit the `ParseError` for an empty parameter list instead of also including an // `UnsupportedSyntaxError`. @@ -1925,6 +1926,7 @@ impl<'src> Parser<'src> { // test_err class_type_params_py311 // # parse_options: {"target-version": "3.11"} // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... + // class Foo[]: ... if let Some(ast::TypeParams { range, type_params }) = &type_params { // Only emit the `ParseError` for an empty parameter list instead of also including an // `UnsupportedSyntaxError`. diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap index 2066b6b09edaa..a57eac24260be 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap @@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/err/class_type_params_py3 ``` Module( ModModule { - range: 0..96, + range: 0..113, body: [ ClassDef( StmtClassDef { @@ -113,14 +113,54 @@ Module( ], }, ), + ClassDef( + StmtClassDef { + range: 96..112, + decorator_list: [], + name: Identifier { + id: Name("Foo"), + range: 102..105, + }, + type_params: Some( + TypeParams { + range: 105..107, + type_params: [], + }, + ), + arguments: None, + body: [ + Expr( + StmtExpr { + range: 109..112, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 109..112, + }, + ), + }, + ), + ], + }, + ), ], }, ) ``` +## Errors + + | +1 | # parse_options: {"target-version": "3.11"} +2 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... +3 | class Foo[]: ... + | ^ Syntax Error: Type parameter list cannot be empty + | + + ## Unsupported Syntax Errors | 1 | # parse_options: {"target-version": "3.11"} 2 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) +3 | class Foo[]: ... | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap index 58a0da558dc65..879a4fa8a6e44 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap @@ -7,7 +7,7 @@ input_file: crates/ruff_python_parser/resources/inline/err/function_type_params_ ``` Module( ModModule { - range: 0..62, + range: 0..79, body: [ FunctionDef( StmtFunctionDef { @@ -59,14 +59,63 @@ Module( ], }, ), + FunctionDef( + StmtFunctionDef { + range: 62..78, + is_async: false, + decorator_list: [], + name: Identifier { + id: Name("foo"), + range: 66..69, + }, + type_params: Some( + TypeParams { + range: 69..71, + type_params: [], + }, + ), + parameters: Parameters { + range: 71..73, + posonlyargs: [], + args: [], + vararg: None, + kwonlyargs: [], + kwarg: None, + }, + returns: None, + body: [ + Expr( + StmtExpr { + range: 75..78, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 75..78, + }, + ), + }, + ), + ], + }, + ), ], }, ) ``` +## Errors + + | +1 | # parse_options: {"target-version": "3.11"} +2 | def foo[T](): ... +3 | def foo[](): ... + | ^ Syntax Error: Type parameter list cannot be empty + | + + ## Unsupported Syntax Errors | 1 | # parse_options: {"target-version": "3.11"} 2 | def foo[T](): ... | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) +3 | def foo[](): ... | From 61f2ad765704cf4584b59a8e1df5f01675f595ce Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 4 Mar 2025 14:09:28 -0500 Subject: [PATCH 08/10] clean up unused snapshots --- .../invalid_syntax@type_params_py311.py.snap | 245 ------------------ .../valid_syntax@type_params_py312.py.snap | 219 ---------------- 2 files changed, 464 deletions(-) delete mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap delete mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap deleted file mode 100644 index 39fbf89f77f83..0000000000000 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_params_py311.py.snap +++ /dev/null @@ -1,245 +0,0 @@ ---- -source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/inline/err/type_params_py311.py ---- -## AST - -``` -Module( - ModModule { - range: 0..140, - body: [ - TypeAlias( - StmtTypeAlias { - range: 44..69, - name: Name( - ExprName { - range: 49..53, - id: Name("List"), - ctx: Store, - }, - ), - type_params: Some( - TypeParams { - range: 53..56, - type_params: [ - TypeVar( - TypeParamTypeVar { - range: 54..55, - name: Identifier { - id: Name("T"), - range: 54..55, - }, - bound: None, - default: None, - }, - ), - ], - }, - ), - value: BinOp( - ExprBinOp { - range: 59..69, - left: Name( - ExprName { - range: 59..63, - id: Name("list"), - ctx: Load, - }, - ), - op: BitOr, - right: Name( - ExprName { - range: 66..69, - id: Name("set"), - ctx: Load, - }, - ), - }, - ), - }, - ), - FunctionDef( - StmtFunctionDef { - range: 70..87, - is_async: false, - decorator_list: [], - name: Identifier { - id: Name("foo"), - range: 74..77, - }, - type_params: Some( - TypeParams { - range: 77..80, - type_params: [ - TypeVar( - TypeParamTypeVar { - range: 78..79, - name: Identifier { - id: Name("T"), - range: 78..79, - }, - bound: None, - default: None, - }, - ), - ], - }, - ), - parameters: Parameters { - range: 80..82, - posonlyargs: [], - args: [], - vararg: None, - kwonlyargs: [], - kwarg: None, - }, - returns: None, - body: [ - Expr( - StmtExpr { - range: 84..87, - value: EllipsisLiteral( - ExprEllipsisLiteral { - range: 84..87, - }, - ), - }, - ), - ], - }, - ), - ClassDef( - StmtClassDef { - range: 88..139, - decorator_list: [], - name: Identifier { - id: Name("Foo"), - range: 94..97, - }, - type_params: Some( - TypeParams { - range: 97..134, - type_params: [ - TypeVar( - TypeParamTypeVar { - range: 98..113, - name: Identifier { - id: Name("S"), - range: 98..99, - }, - bound: Some( - Tuple( - ExprTuple { - range: 101..113, - elts: [ - Name( - ExprName { - range: 102..105, - id: Name("str"), - ctx: Load, - }, - ), - Name( - ExprName { - range: 107..112, - id: Name("bytes"), - ctx: Load, - }, - ), - ], - ctx: Load, - parenthesized: true, - }, - ), - ), - default: None, - }, - ), - TypeVar( - TypeParamTypeVar { - range: 115..123, - name: Identifier { - id: Name("T"), - range: 115..116, - }, - bound: Some( - Name( - ExprName { - range: 118..123, - id: Name("float"), - ctx: Load, - }, - ), - ), - default: None, - }, - ), - TypeVarTuple( - TypeParamTypeVarTuple { - range: 125..128, - name: Identifier { - id: Name("Ts"), - range: 126..128, - }, - default: None, - }, - ), - ParamSpec( - TypeParamParamSpec { - range: 130..133, - name: Identifier { - id: Name("P"), - range: 132..133, - }, - default: None, - }, - ), - ], - }, - ), - arguments: None, - body: [ - Expr( - StmtExpr { - range: 136..139, - value: EllipsisLiteral( - ExprEllipsisLiteral { - range: 136..139, - }, - ), - }, - ), - ], - }, - ), - ], - }, -) -``` -## Unsupported Syntax Errors - - | -1 | # parse_options: {"target-version": "3.11"} -2 | type List[T] = list | set - | ^^^^ Syntax Error: Cannot use `type` alias statement on Python 3.11 (syntax was added in Python 3.12) -3 | def foo[T](): ... -4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... - | - - - | -1 | # parse_options: {"target-version": "3.11"} -2 | type List[T] = list | set -3 | def foo[T](): ... - | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) -4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... - | - - - | -2 | type List[T] = list | set -3 | def foo[T](): ... -4 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) - | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap deleted file mode 100644 index 8a529a75a2427..0000000000000 --- a/crates/ruff_python_parser/tests/snapshots/valid_syntax@type_params_py312.py.snap +++ /dev/null @@ -1,219 +0,0 @@ ---- -source: crates/ruff_python_parser/tests/fixtures.rs -input_file: crates/ruff_python_parser/resources/inline/ok/type_params_py312.py ---- -## AST - -``` -Module( - ModModule { - range: 0..140, - body: [ - TypeAlias( - StmtTypeAlias { - range: 44..69, - name: Name( - ExprName { - range: 49..53, - id: Name("List"), - ctx: Store, - }, - ), - type_params: Some( - TypeParams { - range: 53..56, - type_params: [ - TypeVar( - TypeParamTypeVar { - range: 54..55, - name: Identifier { - id: Name("T"), - range: 54..55, - }, - bound: None, - default: None, - }, - ), - ], - }, - ), - value: BinOp( - ExprBinOp { - range: 59..69, - left: Name( - ExprName { - range: 59..63, - id: Name("list"), - ctx: Load, - }, - ), - op: BitOr, - right: Name( - ExprName { - range: 66..69, - id: Name("set"), - ctx: Load, - }, - ), - }, - ), - }, - ), - FunctionDef( - StmtFunctionDef { - range: 70..87, - is_async: false, - decorator_list: [], - name: Identifier { - id: Name("foo"), - range: 74..77, - }, - type_params: Some( - TypeParams { - range: 77..80, - type_params: [ - TypeVar( - TypeParamTypeVar { - range: 78..79, - name: Identifier { - id: Name("T"), - range: 78..79, - }, - bound: None, - default: None, - }, - ), - ], - }, - ), - parameters: Parameters { - range: 80..82, - posonlyargs: [], - args: [], - vararg: None, - kwonlyargs: [], - kwarg: None, - }, - returns: None, - body: [ - Expr( - StmtExpr { - range: 84..87, - value: EllipsisLiteral( - ExprEllipsisLiteral { - range: 84..87, - }, - ), - }, - ), - ], - }, - ), - ClassDef( - StmtClassDef { - range: 88..139, - decorator_list: [], - name: Identifier { - id: Name("Foo"), - range: 94..97, - }, - type_params: Some( - TypeParams { - range: 97..134, - type_params: [ - TypeVar( - TypeParamTypeVar { - range: 98..113, - name: Identifier { - id: Name("S"), - range: 98..99, - }, - bound: Some( - Tuple( - ExprTuple { - range: 101..113, - elts: [ - Name( - ExprName { - range: 102..105, - id: Name("str"), - ctx: Load, - }, - ), - Name( - ExprName { - range: 107..112, - id: Name("bytes"), - ctx: Load, - }, - ), - ], - ctx: Load, - parenthesized: true, - }, - ), - ), - default: None, - }, - ), - TypeVar( - TypeParamTypeVar { - range: 115..123, - name: Identifier { - id: Name("T"), - range: 115..116, - }, - bound: Some( - Name( - ExprName { - range: 118..123, - id: Name("float"), - ctx: Load, - }, - ), - ), - default: None, - }, - ), - TypeVarTuple( - TypeParamTypeVarTuple { - range: 125..128, - name: Identifier { - id: Name("Ts"), - range: 126..128, - }, - default: None, - }, - ), - ParamSpec( - TypeParamParamSpec { - range: 130..133, - name: Identifier { - id: Name("P"), - range: 132..133, - }, - default: None, - }, - ), - ], - }, - ), - arguments: None, - body: [ - Expr( - StmtExpr { - range: 136..139, - value: EllipsisLiteral( - ExprEllipsisLiteral { - range: 136..139, - }, - ), - }, - ), - ], - }, - ), - ], - }, -) -``` From 1d6a6df6e2580c7680af2093a42f9d7f9210e868 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 5 Mar 2025 08:10:25 -0500 Subject: [PATCH 09/10] remove is_empty checks and accompanying comments --- .../src/parser/statement.rs | 28 +++++++------------ ...lid_syntax@class_type_params_py311.py.snap | 8 ++++++ ..._syntax@function_type_params_py311.py.snap | 8 ++++++ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/crates/ruff_python_parser/src/parser/statement.rs b/crates/ruff_python_parser/src/parser/statement.rs index 39b40f19b172c..c793297342cfc 100644 --- a/crates/ruff_python_parser/src/parser/statement.rs +++ b/crates/ruff_python_parser/src/parser/statement.rs @@ -1794,15 +1794,11 @@ impl<'src> Parser<'src> { // # parse_options: {"target-version": "3.11"} // def foo[T](): ... // def foo[](): ... - if let Some(ast::TypeParams { range, type_params }) = &type_params { - // Only emit the `ParseError` for an empty parameter list instead of also including an - // `UnsupportedSyntaxError`. - if !type_params.is_empty() { - self.add_unsupported_syntax_error( - UnsupportedSyntaxErrorKind::TypeParameterList, - *range, - ); - } + if let Some(ast::TypeParams { range, .. }) = &type_params { + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::TypeParameterList, + *range, + ); } // test_ok function_def_parameter_range @@ -1927,15 +1923,11 @@ impl<'src> Parser<'src> { // # parse_options: {"target-version": "3.11"} // class Foo[S: (str, bytes), T: float, *Ts, **P]: ... // class Foo[]: ... - if let Some(ast::TypeParams { range, type_params }) = &type_params { - // Only emit the `ParseError` for an empty parameter list instead of also including an - // `UnsupportedSyntaxError`. - if !type_params.is_empty() { - self.add_unsupported_syntax_error( - UnsupportedSyntaxErrorKind::TypeParameterList, - *range, - ); - } + if let Some(ast::TypeParams { range, .. }) = &type_params { + self.add_unsupported_syntax_error( + UnsupportedSyntaxErrorKind::TypeParameterList, + *range, + ); } // test_ok class_def_arguments diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap index a57eac24260be..60c7fd49741e2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@class_type_params_py311.py.snap @@ -164,3 +164,11 @@ Module( | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) 3 | class Foo[]: ... | + + + | +1 | # parse_options: {"target-version": "3.11"} +2 | class Foo[S: (str, bytes), T: float, *Ts, **P]: ... +3 | class Foo[]: ... + | ^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap index 879a4fa8a6e44..80edd73d05ae2 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@function_type_params_py311.py.snap @@ -119,3 +119,11 @@ Module( | ^^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) 3 | def foo[](): ... | + + + | +1 | # parse_options: {"target-version": "3.11"} +2 | def foo[T](): ... +3 | def foo[](): ... + | ^^ Syntax Error: Cannot use type parameter lists on Python 3.11 (syntax was added in Python 3.12) + | From a35bf2c6fe32b753bcd9560f4ecb6a61f7f53fa2 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 5 Mar 2025 08:13:15 -0500 Subject: [PATCH 10/10] join reference link --- crates/ruff_python_parser/src/error.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 2d6b1f9f97b72..c975698869e87 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -473,8 +473,7 @@ pub enum UnsupportedSyntaxErrorKind { /// class C[T]: ... /// ``` /// - /// [type parameter list]: - /// https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists + /// [type parameter list]: https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists /// [PEP 695]: https://peps.python.org/pep-0695/ /// [`typing.TypeVar`]: https://docs.python.org/3/library/typing.html#typevar TypeParameterList,