mirror of https://github.com/astral-sh/ruff
feat: implement error when invalid typevar order
Signed-off-by: 11happy <bhuminjaysoni@gmail.com>
This commit is contained in:
parent
c7eea1f2e3
commit
a1e88c31a0
|
|
@ -0,0 +1,24 @@
|
|||
# Invalid Type Param Order
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
T1 = TypeVar("T1", default=int)
|
||||
T2 = TypeVar("T2")
|
||||
T3 = TypeVar("T3")
|
||||
|
||||
# error [invalid-type-param-order]
|
||||
class Foo(Generic[T1, T2]):
|
||||
pass
|
||||
|
||||
# error [invalid-type-param-order]
|
||||
class Bar(Generic[T2, T1, T3]):
|
||||
pass
|
||||
```
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: invalid_type_param_order.md - Invalid Type Param Order
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_type_param_order.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar, Generic
|
||||
2 |
|
||||
3 | T1 = TypeVar("T1", default=int)
|
||||
4 | T2 = TypeVar("T2")
|
||||
5 | T3 = TypeVar("T3")
|
||||
6 |
|
||||
7 | # error [invalid-type-param-order]
|
||||
8 | class Foo(Generic[T1, T2]):
|
||||
9 | pass
|
||||
10 |
|
||||
11 | # error [invalid-type-param-order]
|
||||
12 | class Bar(Generic[T2, T1, T3]):
|
||||
13 | pass
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-type-param-order]: Type parameter T2 without a default follows type parameter with a default
|
||||
--> src/mdtest_snippet.py:8:7
|
||||
|
|
||||
7 | # error [invalid-type-param-order]
|
||||
8 | class Foo(Generic[T1, T2]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
9 | pass
|
||||
|
|
||||
info: rule `invalid-type-param-order` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-type-param-order]: Type parameter T3 without a default follows type parameter with a default
|
||||
--> src/mdtest_snippet.py:12:7
|
||||
|
|
||||
11 | # error [invalid-type-param-order]
|
||||
12 | class Bar(Generic[T2, T1, T3]):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
13 | pass
|
||||
|
|
||||
info: rule `invalid-type-param-order` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -89,6 +89,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
|||
registry.register_lint(&INVALID_TYPE_FORM);
|
||||
registry.register_lint(&INVALID_TYPE_GUARD_DEFINITION);
|
||||
registry.register_lint(&INVALID_TYPE_GUARD_CALL);
|
||||
registry.register_lint(&INVALID_TYPE_PARAM_ORDER);
|
||||
registry.register_lint(&INVALID_TYPE_VARIABLE_CONSTRAINTS);
|
||||
registry.register_lint(&MISSING_ARGUMENT);
|
||||
registry.register_lint(&NO_MATCHING_OVERLOAD);
|
||||
|
|
@ -987,6 +988,34 @@ declare_lint! {
|
|||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for type parameters without defaults that come after type parameters with defaults.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Type parameters without defaults must come before type parameters with defaults.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// from typing import Generic, TypeVar
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
/// U = TypeVar("U")
|
||||
/// # Error: T has no default but comes after U which has a default
|
||||
/// class Foo(Generic[U = int, T]): ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 696: Type defaults for type parameters](https://peps.python.org/pep-0696/)
|
||||
|
||||
pub(crate) static INVALID_TYPE_PARAM_ORDER = {
|
||||
summary: "detects invalid type parameter order",
|
||||
status: LintStatus::stable("0.0.1-alpha.1"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for the creation of invalid `NewType`s
|
||||
|
|
@ -3695,6 +3724,20 @@ pub(crate) fn report_cannot_pop_required_field_on_typed_dict<'db>(
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report_invalid_type_param_order<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
class: ClassLiteral<'db>,
|
||||
name: &str,
|
||||
) {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_TYPE_PARAM_ORDER, class.header_range(context.db()))
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Type parameter {name} without a default follows type parameter with a default",
|
||||
));
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn report_rebound_typevar<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
typevar_name: &ast::name::Name,
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ use crate::types::diagnostic::{
|
|||
report_invalid_exception_tuple_caught, report_invalid_generator_function_return_type,
|
||||
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
||||
report_invalid_return_type, report_invalid_type_checking_constant,
|
||||
report_named_tuple_field_with_leading_underscore,
|
||||
report_named_tuple_field_with_leading_underscore, report_invalid_type_param_order,
|
||||
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
||||
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
||||
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
|
||||
|
|
@ -949,6 +949,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
|
||||
let type_vars = class.typevars_referenced_in_definition(self.db());
|
||||
let mut seen_default = false;
|
||||
for type_var in type_vars {
|
||||
let has_default = type_var
|
||||
.typevar(self.db())
|
||||
.default_type(self.db())
|
||||
.is_some();
|
||||
if seen_default && !has_default {
|
||||
report_invalid_type_param_order(
|
||||
&self.context,
|
||||
class,
|
||||
type_var.typevar(self.db()).name(self.db()).as_str(),
|
||||
);
|
||||
}
|
||||
if has_default {
|
||||
seen_default = true;
|
||||
}
|
||||
}
|
||||
let scope = class.body_scope(self.db()).scope(self.db());
|
||||
if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS)
|
||||
&& let Some(parent) = scope.parent()
|
||||
|
|
|
|||
|
|
@ -813,6 +813,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"invalid-type-param-order": {
|
||||
"title": "detects invalid type parameter order",
|
||||
"description": "## What it does\nChecks for type parameters without defaults that come after type parameters with defaults.\n\n## Why is this bad?\nType parameters without defaults must come before type parameters with defaults.\n\n## Example\n```python\nfrom typing import Generic, TypeVar\n\nT = TypeVar(\"T\")\nU = TypeVar(\"U\")\n# Error: T has no default but comes after U which has a default\nclass Foo(Generic[U = int, T]): ...\n```\n\n## References\n- [PEP 696: Type defaults for type parameters](https://peps.python.org/pep-0696/)",
|
||||
"default": "error",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Level"
|
||||
}
|
||||
]
|
||||
},
|
||||
"invalid-type-variable-constraints": {
|
||||
"title": "detects invalid type variable constraints",
|
||||
"description": "## What it does\nChecks for constrained [type variables] with only one constraint.\n\n## Why is this bad?\nA constrained type variable must have at least two constraints.\n\n## Examples\n```python\nfrom typing import TypeVar\n\nT = TypeVar('T', str) # invalid constrained TypeVar\n```\n\nUse instead:\n```python\nT = TypeVar('T', str, int) # valid constrained TypeVar\n# or\nT = TypeVar('T', bound=str) # valid bound TypeVar\n```\n\n[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar",
|
||||
|
|
|
|||
Loading…
Reference in New Issue