[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.
https://github.com/astral-sh/ruff/issues/17412#issuecomment-3584088217


## Test Plan

I have written inline tests as directed in #17412

---------

Signed-off-by: 11happy <bhuminjaysoni@gmail.com>
Signed-off-by: 11happy <soni5happy@gmail.com>
This commit is contained in:
Bhuminjay Soni
2025-12-03 12:01:31 +05:30
committed by GitHub
parent abaa49f552
commit f68080b55e
7 changed files with 359 additions and 5 deletions

View File

@@ -144,11 +144,16 @@ 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),
..
})
| Stmt::TypeAlias(ast::StmtTypeAlias {
type_params: Some(type_params),
..
}) => {
Self::duplicate_type_parameter_name(type_params, ctx);
Self::type_parameter_default_order(type_params, ctx);
}
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() {
@@ -611,6 +616,39 @@ 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]: ...
// class C[T1, T2 = int, T3, T4]: ...
// type Alias[T = int, U] = ...
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)]