mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 22:54:42 +00:00
[ruff
] Exempt NewType
calls where the original type is immutable (RUF009
) (#15588)
Some checks are pending
CI / cargo fmt (push) Waiting to run
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
Some checks are pending
CI / cargo fmt (push) Waiting to run
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary Resolves #6447. ## Test Plan `cargo nextest run` and `cargo insta test`.
This commit is contained in:
parent
134fefa945
commit
4cfa355519
3 changed files with 124 additions and 3 deletions
|
@ -69,3 +69,31 @@ class IntConversionDescriptor:
|
||||||
@dataclass
|
@dataclass
|
||||||
class InventoryItem:
|
class InventoryItem:
|
||||||
quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
|
quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
|
||||||
|
|
||||||
|
|
||||||
|
# Regression tests for:
|
||||||
|
# https://github.com/astral-sh/ruff/issues/6447
|
||||||
|
from typing import NewType
|
||||||
|
|
||||||
|
ListOfStrings = NewType("ListOfStrs", list[str])
|
||||||
|
StringsToInts = NewType("IntsToStrings", dict[str, int])
|
||||||
|
|
||||||
|
SpecialString = NewType(name="SpecialString", tp=str)
|
||||||
|
NegativeInteger = NewType("NegInt", tp=int)
|
||||||
|
|
||||||
|
Invalid1 = NewType(*Foo)
|
||||||
|
Invalid2 = NewType("Invalid2", name=Foo)
|
||||||
|
Invalid3 = NewType("Invalid3", name=Foo, lorem="ipsum")
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DataclassWithNewTypeFields:
|
||||||
|
# Errors
|
||||||
|
a: ListOfStrings = ListOfStrings([])
|
||||||
|
b: StringsToInts = StringsToInts()
|
||||||
|
c: Invalid1 = Invalid1()
|
||||||
|
d: Invalid2 = Invalid2()
|
||||||
|
e: Invalid3 = Invalid3()
|
||||||
|
|
||||||
|
# No errors
|
||||||
|
e: SpecialString = SpecialString("Lorem ipsum")
|
||||||
|
f: NegativeInteger = NegativeInteger(-110)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
use ruff_python_ast::{self as ast, Expr, ExprCall, Stmt, StmtAssign};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
|
||||||
use ruff_python_semantic::analyze::typing::is_immutable_func;
|
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_immutable_func};
|
||||||
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -137,6 +138,7 @@ pub(crate) fn function_call_in_dataclass_default(
|
||||||
|| is_class_var_annotation(annotation, checker.semantic())
|
|| is_class_var_annotation(annotation, checker.semantic())
|
||||||
|| is_immutable_func(func, checker.semantic(), &extend_immutable_calls)
|
|| is_immutable_func(func, checker.semantic(), &extend_immutable_calls)
|
||||||
|| is_descriptor_class(func, checker.semantic())
|
|| is_descriptor_class(func, checker.semantic())
|
||||||
|
|| is_immutable_newtype_call(func, checker.semantic(), &extend_immutable_calls)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -156,3 +158,46 @@ fn any_annotated(class_body: &[Stmt]) -> bool {
|
||||||
.iter()
|
.iter()
|
||||||
.any(|stmt| matches!(stmt, Stmt::AnnAssign(..)))
|
.any(|stmt| matches!(stmt, Stmt::AnnAssign(..)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_immutable_newtype_call(
|
||||||
|
func: &Expr,
|
||||||
|
semantic: &SemanticModel,
|
||||||
|
extend_immutable_calls: &[QualifiedName],
|
||||||
|
) -> bool {
|
||||||
|
let Expr::Name(name) = func else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !binding.kind.is_assignment() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(Stmt::Assign(StmtAssign { value, .. })) = binding.statement(semantic) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Expr::Call(ExprCall {
|
||||||
|
func, arguments, ..
|
||||||
|
}) = value.as_ref()
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !semantic.match_typing_expr(func, "NewType") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.len() != 2 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(original_type) = arguments.find_argument_value("tp", 1) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
is_immutable_annotation(original_type, semantic, extend_immutable_calls)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
RUF009.py:20:41: RUF009 Do not perform function call `default_function` in dataclass defaults
|
RUF009.py:20:41: RUF009 Do not perform function call `default_function` in dataclass defaults
|
||||||
|
|
|
|
||||||
|
@ -41,3 +40,52 @@ RUF009.py:45:34: RUF009 Do not perform function call `ImmutableType` in dataclas
|
||||||
46 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
|
46 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
|
||||||
47 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
|
47 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
|
||||||
|
|
|
|
||||||
|
|
||||||
|
RUF009.py:91:24: RUF009 Do not perform function call `ListOfStrings` in dataclass defaults
|
||||||
|
|
|
||||||
|
89 | class DataclassWithNewTypeFields:
|
||||||
|
90 | # Errors
|
||||||
|
91 | a: ListOfStrings = ListOfStrings([])
|
||||||
|
| ^^^^^^^^^^^^^^^^^ RUF009
|
||||||
|
92 | b: StringsToInts = StringsToInts()
|
||||||
|
93 | c: Invalid1 = Invalid1()
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF009.py:92:24: RUF009 Do not perform function call `StringsToInts` in dataclass defaults
|
||||||
|
|
|
||||||
|
90 | # Errors
|
||||||
|
91 | a: ListOfStrings = ListOfStrings([])
|
||||||
|
92 | b: StringsToInts = StringsToInts()
|
||||||
|
| ^^^^^^^^^^^^^^^ RUF009
|
||||||
|
93 | c: Invalid1 = Invalid1()
|
||||||
|
94 | d: Invalid2 = Invalid2()
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF009.py:93:19: RUF009 Do not perform function call `Invalid1` in dataclass defaults
|
||||||
|
|
|
||||||
|
91 | a: ListOfStrings = ListOfStrings([])
|
||||||
|
92 | b: StringsToInts = StringsToInts()
|
||||||
|
93 | c: Invalid1 = Invalid1()
|
||||||
|
| ^^^^^^^^^^ RUF009
|
||||||
|
94 | d: Invalid2 = Invalid2()
|
||||||
|
95 | e: Invalid3 = Invalid3()
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF009.py:94:19: RUF009 Do not perform function call `Invalid2` in dataclass defaults
|
||||||
|
|
|
||||||
|
92 | b: StringsToInts = StringsToInts()
|
||||||
|
93 | c: Invalid1 = Invalid1()
|
||||||
|
94 | d: Invalid2 = Invalid2()
|
||||||
|
| ^^^^^^^^^^ RUF009
|
||||||
|
95 | e: Invalid3 = Invalid3()
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF009.py:95:19: RUF009 Do not perform function call `Invalid3` in dataclass defaults
|
||||||
|
|
|
||||||
|
93 | c: Invalid1 = Invalid1()
|
||||||
|
94 | d: Invalid2 = Invalid2()
|
||||||
|
95 | e: Invalid3 = Invalid3()
|
||||||
|
| ^^^^^^^^^^ RUF009
|
||||||
|
96 |
|
||||||
|
97 | # No errors
|
||||||
|
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue