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_FORM);
|
||||||
registry.register_lint(&INVALID_TYPE_GUARD_DEFINITION);
|
registry.register_lint(&INVALID_TYPE_GUARD_DEFINITION);
|
||||||
registry.register_lint(&INVALID_TYPE_GUARD_CALL);
|
registry.register_lint(&INVALID_TYPE_GUARD_CALL);
|
||||||
|
registry.register_lint(&INVALID_TYPE_PARAM_ORDER);
|
||||||
registry.register_lint(&INVALID_TYPE_VARIABLE_CONSTRAINTS);
|
registry.register_lint(&INVALID_TYPE_VARIABLE_CONSTRAINTS);
|
||||||
registry.register_lint(&MISSING_ARGUMENT);
|
registry.register_lint(&MISSING_ARGUMENT);
|
||||||
registry.register_lint(&NO_MATCHING_OVERLOAD);
|
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! {
|
declare_lint! {
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for the creation of invalid `NewType`s
|
/// 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>(
|
pub(crate) fn report_rebound_typevar<'db>(
|
||||||
context: &InferContext<'db, '_>,
|
context: &InferContext<'db, '_>,
|
||||||
typevar_name: &ast::name::Name,
|
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_exception_tuple_caught, report_invalid_generator_function_return_type,
|
||||||
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
||||||
report_invalid_return_type, report_invalid_type_checking_constant,
|
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_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
||||||
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
||||||
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
|
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());
|
let scope = class.body_scope(self.db()).scope(self.db());
|
||||||
if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS)
|
if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS)
|
||||||
&& let Some(parent) = scope.parent()
|
&& 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": {
|
"invalid-type-variable-constraints": {
|
||||||
"title": "detects 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",
|
"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