Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/ruff_linter/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ impl SemanticSyntaxContext for Checker<'_> {
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder(_)
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_) => {
self.semantic_errors.borrow_mut().push(error);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class C[T = int, U]: ...
57 changes: 52 additions & 5 deletions crates/ruff_python_parser/src/semantic_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines 147 to 155
Copy link
Member

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:

In [1]: type Int[T = int, U] = int
  Cell In[1], line 1
    type Int[T = int, U] = int
                      ^
SyntaxError: non-default type parameter 'U' follows default type parameter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

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 -

type X[*Ts = x := int] = int

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 ?

}
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() {
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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]: ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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,
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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)]
Expand Down
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
|