Skip to content

Commit f68080b

Browse files
authored
[syntax-error] Default type parameter followed by non-default type parameter (#21657)
## Summary This PR implements syntax error where a default type parameter is followed by a non-default type parameter. #17412 (comment) ## Test Plan I have written inline tests as directed in #17412 --------- Signed-off-by: 11happy <[email protected]> Signed-off-by: 11happy <[email protected]>
1 parent abaa49f commit f68080b

File tree

7 files changed

+359
-5
lines changed

7 files changed

+359
-5
lines changed

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ impl SemanticSyntaxContext for Checker<'_> {
747747
| SemanticSyntaxErrorKind::LoadBeforeNonlocalDeclaration { .. }
748748
| SemanticSyntaxErrorKind::NonlocalAndGlobal(_)
749749
| SemanticSyntaxErrorKind::AnnotatedGlobal(_)
750+
| SemanticSyntaxErrorKind::TypeParameterDefaultOrder(_)
750751
| SemanticSyntaxErrorKind::AnnotatedNonlocal(_) => {
751752
self.semantic_errors.borrow_mut().push(error);
752753
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class C[T = int, U]: ...
2+
class C[T1, T2 = int, T3, T4]: ...
3+
type Alias[T = int, U] = ...

crates/ruff_python_parser/src/semantic_errors.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,16 @@ impl SemanticSyntaxChecker {
144144
}
145145
}
146146
}
147-
Stmt::ClassDef(ast::StmtClassDef { type_params, .. })
148-
| Stmt::TypeAlias(ast::StmtTypeAlias { type_params, .. }) => {
149-
if let Some(type_params) = type_params {
150-
Self::duplicate_type_parameter_name(type_params, ctx);
151-
}
147+
Stmt::ClassDef(ast::StmtClassDef {
148+
type_params: Some(type_params),
149+
..
150+
})
151+
| Stmt::TypeAlias(ast::StmtTypeAlias {
152+
type_params: Some(type_params),
153+
..
154+
}) => {
155+
Self::duplicate_type_parameter_name(type_params, ctx);
156+
Self::type_parameter_default_order(type_params, ctx);
152157
}
153158
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
154159
if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() {
@@ -611,6 +616,39 @@ impl SemanticSyntaxChecker {
611616
}
612617
}
613618

619+
fn type_parameter_default_order<Ctx: SemanticSyntaxContext>(
620+
type_params: &ast::TypeParams,
621+
ctx: &Ctx,
622+
) {
623+
let mut seen_default = false;
624+
for type_param in type_params.iter() {
625+
let has_default = match type_param {
626+
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { default, .. })
627+
| ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { default, .. })
628+
| ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { default, .. }) => {
629+
default.is_some()
630+
}
631+
};
632+
633+
if seen_default && !has_default {
634+
// test_err type_parameter_default_order
635+
// class C[T = int, U]: ...
636+
// class C[T1, T2 = int, T3, T4]: ...
637+
// type Alias[T = int, U] = ...
638+
Self::add_error(
639+
ctx,
640+
SemanticSyntaxErrorKind::TypeParameterDefaultOrder(
641+
type_param.name().id.to_string(),
642+
),
643+
type_param.range(),
644+
);
645+
}
646+
if has_default {
647+
seen_default = true;
648+
}
649+
}
650+
}
651+
614652
fn duplicate_parameter_name<Ctx: SemanticSyntaxContext>(
615653
parameters: &ast::Parameters,
616654
ctx: &Ctx,
@@ -1066,6 +1104,12 @@ impl Display for SemanticSyntaxError {
10661104
SemanticSyntaxErrorKind::DuplicateTypeParameter => {
10671105
f.write_str("duplicate type parameter")
10681106
}
1107+
SemanticSyntaxErrorKind::TypeParameterDefaultOrder(name) => {
1108+
write!(
1109+
f,
1110+
"non default type parameter `{name}` follows default type parameter"
1111+
)
1112+
}
10691113
SemanticSyntaxErrorKind::MultipleCaseAssignment(name) => {
10701114
write!(f, "multiple assignments to name `{name}` in pattern")
10711115
}
@@ -1572,6 +1616,9 @@ pub enum SemanticSyntaxErrorKind {
15721616

15731617
/// Represents a nonlocal statement for a name that has no binding in an enclosing scope.
15741618
NonlocalWithoutBinding(String),
1619+
1620+
/// Represents a default type parameter followed by a non-default type parameter.
1621+
TypeParameterDefaultOrder(String),
15751622
}
15761623

15771624
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]

crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_param_spec_invalid_default_expr.py.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,12 @@ Module(
375375
4 | type X[**P = x := int] = int
376376
5 | type X[**P = *int] = int
377377
|
378+
379+
380+
|
381+
2 | type X[**P = yield x] = int
382+
3 | type X[**P = yield from x] = int
383+
4 | type X[**P = x := int] = int
384+
| ^^^ Syntax Error: non default type parameter `int` follows default type parameter
385+
5 | type X[**P = *int] = int
386+
|

crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_invalid_default_expr.py.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,12 @@ Module(
459459
5 | type X[T = x := int] = int
460460
6 | type X[T: int = *int] = int
461461
|
462+
463+
464+
|
465+
3 | type X[T = (yield x)] = int
466+
4 | type X[T = yield from x] = int
467+
5 | type X[T = x := int] = int
468+
| ^^^ Syntax Error: non default type parameter `int` follows default type parameter
469+
6 | type X[T: int = *int] = int
470+
|

crates/ruff_python_parser/tests/snapshots/invalid_syntax@type_param_type_var_tuple_invalid_default_expr.py.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,11 @@ Module(
384384
| ^^^^^^^^^^^^ Syntax Error: yield expression cannot be used within a TypeVarTuple default
385385
5 | type X[*Ts = x := int] = int
386386
|
387+
388+
389+
|
390+
3 | type X[*Ts = yield x] = int
391+
4 | type X[*Ts = yield from x] = int
392+
5 | type X[*Ts = x := int] = int
393+
| ^^^ Syntax Error: non default type parameter `int` follows default type parameter
394+
|

0 commit comments

Comments
 (0)