-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[syntax-error]: default type parameter followed by non-default type parameter #21657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| class C[T = int, U]: ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -144,11 +144,18 @@ impl SemanticSyntaxChecker { | |
| } | ||
| } | ||
| } | ||
| Stmt::ClassDef(ast::StmtClassDef { type_params, .. }) | ||
| | Stmt::TypeAlias(ast::StmtTypeAlias { type_params, .. }) => { | ||
| if let Some(type_params) = type_params { | ||
| Self::duplicate_type_parameter_name(type_params, ctx); | ||
| } | ||
| Stmt::ClassDef(ast::StmtClassDef { | ||
| type_params: Some(type_params), | ||
| .. | ||
| }) => { | ||
| Self::duplicate_type_parameter_name(type_params, ctx); | ||
| Self::type_parameter_default_order(type_params, ctx); | ||
| } | ||
| Stmt::TypeAlias(ast::StmtTypeAlias { | ||
| type_params: Some(type_params), | ||
| .. | ||
| }) => { | ||
| Self::duplicate_type_parameter_name(type_params, ctx); | ||
| } | ||
| Stmt::Assign(ast::StmtAssign { targets, value, .. }) => { | ||
| if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() { | ||
|
|
@@ -611,6 +618,37 @@ impl SemanticSyntaxChecker { | |
| } | ||
| } | ||
|
|
||
| fn type_parameter_default_order<Ctx: SemanticSyntaxContext>( | ||
| type_params: &ast::TypeParams, | ||
| ctx: &Ctx, | ||
| ) { | ||
| let mut seen_default = false; | ||
| for type_param in type_params.iter() { | ||
| let has_default = match type_param { | ||
| ast::TypeParam::TypeVar(ast::TypeParamTypeVar { default, .. }) | ||
| | ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { default, .. }) | ||
| | ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { default, .. }) => { | ||
| default.is_some() | ||
| } | ||
| }; | ||
|
|
||
| if seen_default && !has_default { | ||
| // test_err type_parameter_default_order | ||
| // class C[T = int, U]: ... | ||
|
Comment on lines
+634
to
+635
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add some cases where the default is not at the start of the list and it has multiple non-default type parameter following a type parameter with default like: class C[T1, T2 = int, T3, T4]: ...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| Self::add_error( | ||
| ctx, | ||
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder( | ||
| type_param.name().id.to_string(), | ||
| ), | ||
| type_param.range(), | ||
| ); | ||
| } | ||
| if has_default { | ||
| seen_default = true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn duplicate_parameter_name<Ctx: SemanticSyntaxContext>( | ||
| parameters: &ast::Parameters, | ||
| ctx: &Ctx, | ||
|
|
@@ -1066,6 +1104,12 @@ impl Display for SemanticSyntaxError { | |
| SemanticSyntaxErrorKind::DuplicateTypeParameter => { | ||
| f.write_str("duplicate type parameter") | ||
| } | ||
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder(name) => { | ||
| write!( | ||
| f, | ||
| "non default type parameter `{name}` follows default type parameter" | ||
| ) | ||
| } | ||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(name) => { | ||
| write!(f, "multiple assignments to name `{name}` in pattern") | ||
| } | ||
|
|
@@ -1572,6 +1616,9 @@ pub enum SemanticSyntaxErrorKind { | |
|
|
||
| /// Represents a nonlocal statement for a name that has no binding in an enclosing scope. | ||
| NonlocalWithoutBinding(String), | ||
|
|
||
| /// Represents a default type parameter followed by a non-default type parameter. | ||
| TypeParameterDefaultOrder(String), | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| --- | ||
| source: crates/ruff_python_parser/tests/fixtures.rs | ||
| input_file: crates/ruff_python_parser/resources/inline/err/type_parameter_default_order.py | ||
| --- | ||
| ## AST | ||
|
|
||
| ``` | ||
| Module( | ||
| ModModule { | ||
| node_index: NodeIndex(None), | ||
| range: 0..25, | ||
| body: [ | ||
| ClassDef( | ||
| StmtClassDef { | ||
| node_index: NodeIndex(None), | ||
| range: 0..24, | ||
| decorator_list: [], | ||
| name: Identifier { | ||
| id: Name("C"), | ||
| range: 6..7, | ||
| node_index: NodeIndex(None), | ||
| }, | ||
| type_params: Some( | ||
| TypeParams { | ||
| range: 7..19, | ||
| node_index: NodeIndex(None), | ||
| type_params: [ | ||
| TypeVar( | ||
| TypeParamTypeVar { | ||
| node_index: NodeIndex(None), | ||
| range: 8..15, | ||
| name: Identifier { | ||
| id: Name("T"), | ||
| range: 8..9, | ||
| node_index: NodeIndex(None), | ||
| }, | ||
| bound: None, | ||
| default: Some( | ||
| Name( | ||
| ExprName { | ||
| node_index: NodeIndex(None), | ||
| range: 12..15, | ||
| id: Name("int"), | ||
| ctx: Load, | ||
| }, | ||
| ), | ||
| ), | ||
| }, | ||
| ), | ||
| TypeVar( | ||
| TypeParamTypeVar { | ||
| node_index: NodeIndex(None), | ||
| range: 17..18, | ||
| name: Identifier { | ||
| id: Name("U"), | ||
| range: 17..18, | ||
| node_index: NodeIndex(None), | ||
| }, | ||
| bound: None, | ||
| default: None, | ||
| }, | ||
| ), | ||
| ], | ||
| }, | ||
| ), | ||
| arguments: None, | ||
| body: [ | ||
| Expr( | ||
| StmtExpr { | ||
| node_index: NodeIndex(None), | ||
| range: 21..24, | ||
| value: EllipsisLiteral( | ||
| ExprEllipsisLiteral { | ||
| node_index: NodeIndex(None), | ||
| range: 21..24, | ||
| }, | ||
| ), | ||
| }, | ||
| ), | ||
| ], | ||
| }, | ||
| ), | ||
| ], | ||
| }, | ||
| ) | ||
| ``` | ||
| ## Semantic Syntax Errors | ||
|
|
||
| | | ||
| 1 | class C[T = int, U]: ... | ||
| | ^ Syntax Error: non default type parameter `U` follows default type parameter | ||
| | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check needs to be applied to type aliases as well:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
however some additional detection's are there for example -
in type params:
type_params: TypeParams { range: 6..22, node_index: NodeIndex(None), type_params: [TypeVarTuple(TypeParamTypeVarTuple { node_index: NodeIndex(None), range: 7..14, name: Identifier { id: Name("Ts"), range: 8..10, node_index: NodeIndex(None) }, default: Some(Name(ExprName { node_index: NodeIndex(None), range: 13..14, id: Name("x"), ctx: Load })) }), TypeVar(TypeParamTypeVar { node_index: NodeIndex(None), range: 18..21, name: Identifier { id: Name("int"), range: 18..21, node_index: NodeIndex(None) }, bound: None, default: None })]
int is getting detected as seperate non default typevar, whereas I think should it be parsed as single expression ?