Allow more immutable funcs for RUF009 (#4660)

This commit is contained in:
qdegraaf 2023-05-26 21:18:52 +02:00 committed by GitHub
parent 12e45498e8
commit ccca11839a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 155 additions and 173 deletions

View file

@ -1,6 +1,7 @@
import collections
import datetime as dt
from decimal import Decimal
from fractions import Fraction
import logging
import operator
from pathlib import Path
@ -158,12 +159,37 @@ def float_infinity_literal(value=float("1e999")):
pass
# But don't allow standard floats
def float_int_is_wrong(value=float(3)):
# Allow standard floats
def float_int_okay(value=float(3)):
pass
def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
def float_str_not_inf_or_nan_okay(value=float("3.14")):
pass
# Allow immutable str() value
def str_okay(value=str("foo")):
pass
# Allow immutable bool() value
def bool_okay(value=bool("bar")):
pass
# Allow immutable int() value
def int_okay(value=int("12")):
pass
# Allow immutable complex() value
def complex_okay(value=complex(1,2)):
pass
# Allow immutable Fraction() value
def fraction_okay(value=Fraction(1,2)):
pass

View file

@ -2,10 +2,10 @@ import datetime
import re
import typing
from dataclasses import dataclass, field
from fractions import Fraction
from pathlib import Path
from typing import ClassVar, NamedTuple
def default_function() -> list[int]:
return []
@ -25,7 +25,12 @@ class A:
fine_timedelta: datetime.timedelta = datetime.timedelta(hours=7)
fine_tuple: tuple[int] = tuple([1])
fine_regex: re.Pattern = re.compile(r".*")
fine_float: float = float('-inf')
fine_int: int = int(12)
fine_complex: complex = complex(1, 2)
fine_str: str = str("foo")
fine_bool: bool = bool("foo")
fine_fraction: Fraction = Fraction(1,2)
DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40)
DEFAULT_A_FOR_ALL_DATACLASSES = A([1, 2, 3])

View file

@ -1,5 +1,5 @@
use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, Arguments, Constant, Expr, Ranged};
use rustpython_parser::ast::{self, Arguments, Expr, Ranged};
use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
@ -94,10 +94,9 @@ where
{
fn visit_expr(&mut self, expr: &'b Expr) {
match expr {
Expr::Call(ast::ExprCall { func, args, .. }) => {
Expr::Call(ast::ExprCall { func, .. }) => {
if !is_mutable_func(self.model, func)
&& !is_immutable_func(self.model, func, &self.extend_immutable_calls)
&& !is_nan_or_infinity(func, args)
{
self.diagnostics.push((
FunctionCallInDefaultArgument {
@ -115,29 +114,6 @@ where
}
}
fn is_nan_or_infinity(expr: &Expr, args: &[Expr]) -> bool {
let Expr::Name(ast::ExprName { id, .. }) = expr else {
return false;
};
if id != "float" {
return false;
}
let Some(arg) = args.first() else {
return false;
};
let Expr::Constant(ast::ExprConstant {
value: Constant::Str(value),
..
} )= arg else {
return false;
};
let lowercased = value.to_lowercase();
matches!(
lowercased.as_str(),
"nan" | "+nan" | "-nan" | "inf" | "+inf" | "-inf" | "infinity" | "+infinity" | "-infinity"
)
}
/// B008
pub(crate) fn function_call_argument_default(checker: &mut Checker, arguments: &Arguments) {
// Map immutable calls to (module, member) format.

View file

@ -1,113 +1,113 @@
---
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
---
B006_B008.py:62:25: B006 Do not use mutable data structures for argument defaults
B006_B008.py:63:25: B006 Do not use mutable data structures for argument defaults
|
62 | def this_is_wrong(value=[1, 2, 3]):
63 | def this_is_wrong(value=[1, 2, 3]):
| ^^^^^^^^^ B006
63 | ...
64 | ...
|
B006_B008.py:66:30: B006 Do not use mutable data structures for argument defaults
B006_B008.py:67:30: B006 Do not use mutable data structures for argument defaults
|
66 | def this_is_also_wrong(value={}):
67 | def this_is_also_wrong(value={}):
| ^^ B006
67 | ...
68 | ...
|
B006_B008.py:70:20: B006 Do not use mutable data structures for argument defaults
B006_B008.py:71:20: B006 Do not use mutable data structures for argument defaults
|
70 | def and_this(value=set()):
71 | def and_this(value=set()):
| ^^^^^ B006
71 | ...
72 | ...
|
B006_B008.py:74:20: B006 Do not use mutable data structures for argument defaults
B006_B008.py:75:20: B006 Do not use mutable data structures for argument defaults
|
74 | def this_too(value=collections.OrderedDict()):
75 | def this_too(value=collections.OrderedDict()):
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
75 | ...
76 | ...
|
B006_B008.py:78:32: B006 Do not use mutable data structures for argument defaults
B006_B008.py:79:32: B006 Do not use mutable data structures for argument defaults
|
78 | async def async_this_too(value=collections.defaultdict()):
79 | async def async_this_too(value=collections.defaultdict()):
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B006
79 | ...
80 | ...
|
B006_B008.py:82:26: B006 Do not use mutable data structures for argument defaults
B006_B008.py:83:26: B006 Do not use mutable data structures for argument defaults
|
82 | def dont_forget_me(value=collections.deque()):
83 | def dont_forget_me(value=collections.deque()):
| ^^^^^^^^^^^^^^^^^^^ B006
83 | ...
84 | ...
|
B006_B008.py:87:46: B006 Do not use mutable data structures for argument defaults
B006_B008.py:88:46: B006 Do not use mutable data structures for argument defaults
|
87 | # N.B. we're also flagging the function call in the comprehension
88 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
88 | # N.B. we're also flagging the function call in the comprehension
89 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
89 | pass
90 | pass
|
B006_B008.py:91:46: B006 Do not use mutable data structures for argument defaults
B006_B008.py:92:46: B006 Do not use mutable data structures for argument defaults
|
91 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
92 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
92 | pass
93 | pass
|
B006_B008.py:95:45: B006 Do not use mutable data structures for argument defaults
B006_B008.py:96:45: B006 Do not use mutable data structures for argument defaults
|
95 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
96 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
| ^^^^^^^^^^^^^^^^^^^^^^^^ B006
96 | pass
97 | pass
|
B006_B008.py:99:33: B006 Do not use mutable data structures for argument defaults
B006_B008.py:100:33: B006 Do not use mutable data structures for argument defaults
|
99 | def kwonlyargs_mutable(*, value=[]):
100 | def kwonlyargs_mutable(*, value=[]):
| ^^ B006
100 | ...
101 | ...
|
B006_B008.py:192:20: B006 Do not use mutable data structures for argument defaults
B006_B008.py:218:20: B006 Do not use mutable data structures for argument defaults
|
192 | # B006 and B008
193 | # We should handle arbitrary nesting of these B008.
194 | def nested_combo(a=[float(3), dt.datetime.now()]):
218 | # B006 and B008
219 | # We should handle arbitrary nesting of these B008.
220 | def nested_combo(a=[float(3), dt.datetime.now()]):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B006
195 | pass
221 | pass
|
B006_B008.py:225:27: B006 Do not use mutable data structures for argument defaults
B006_B008.py:251:27: B006 Do not use mutable data structures for argument defaults
|
225 | def mutable_annotations(
226 | a: list[int] | None = [],
251 | def mutable_annotations(
252 | a: list[int] | None = [],
| ^^ B006
227 | b: Optional[Dict[int, int]] = {},
228 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
253 | b: Optional[Dict[int, int]] = {},
254 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
B006_B008.py:226:35: B006 Do not use mutable data structures for argument defaults
B006_B008.py:252:35: B006 Do not use mutable data structures for argument defaults
|
226 | def mutable_annotations(
227 | a: list[int] | None = [],
228 | b: Optional[Dict[int, int]] = {},
252 | def mutable_annotations(
253 | a: list[int] | None = [],
254 | b: Optional[Dict[int, int]] = {},
| ^^ B006
229 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
230 | ):
255 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
256 | ):
|
B006_B008.py:227:62: B006 Do not use mutable data structures for argument defaults
B006_B008.py:253:62: B006 Do not use mutable data structures for argument defaults
|
227 | a: list[int] | None = [],
228 | b: Optional[Dict[int, int]] = {},
229 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
253 | a: list[int] | None = [],
254 | b: Optional[Dict[int, int]] = {},
255 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
| ^^^^^ B006
230 | ):
231 | pass
256 | ):
257 | pass
|

View file

@ -1,114 +1,83 @@
---
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
---
B006_B008.py:87:61: B008 Do not perform function call `range` in argument defaults
B006_B008.py:88:61: B008 Do not perform function call `range` in argument defaults
|
87 | # N.B. we're also flagging the function call in the comprehension
88 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
88 | # N.B. we're also flagging the function call in the comprehension
89 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
| ^^^^^^^^ B008
89 | pass
90 | pass
|
B006_B008.py:91:64: B008 Do not perform function call `range` in argument defaults
B006_B008.py:92:64: B008 Do not perform function call `range` in argument defaults
|
91 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
92 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
| ^^^^^^^^ B008
92 | pass
93 | pass
|
B006_B008.py:95:60: B008 Do not perform function call `range` in argument defaults
B006_B008.py:96:60: B008 Do not perform function call `range` in argument defaults
|
95 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
96 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
| ^^^^^^^^ B008
96 | pass
97 | pass
|
B006_B008.py:111:39: B008 Do not perform function call `time.time` in argument defaults
B006_B008.py:112:39: B008 Do not perform function call `time.time` in argument defaults
|
111 | # B008
112 | # Flag function calls as default args (including if they are part of a sub-expression)
113 | def in_fact_all_calls_are_wrong(value=time.time()):
112 | # B008
113 | # Flag function calls as default args (including if they are part of a sub-expression)
114 | def in_fact_all_calls_are_wrong(value=time.time()):
| ^^^^^^^^^^^ B008
114 | ...
115 | ...
|
B006_B008.py:115:12: B008 Do not perform function call `dt.datetime.now` in argument defaults
B006_B008.py:116:12: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
115 | def f(when=dt.datetime.now() + dt.timedelta(days=7)):
116 | def f(when=dt.datetime.now() + dt.timedelta(days=7)):
| ^^^^^^^^^^^^^^^^^ B008
116 | pass
117 | pass
|
B006_B008.py:119:30: B008 Do not perform function call in argument defaults
B006_B008.py:120:30: B008 Do not perform function call in argument defaults
|
119 | def can_even_catch_lambdas(a=(lambda x: x)()):
120 | def can_even_catch_lambdas(a=(lambda x: x)()):
| ^^^^^^^^^^^^^^^ B008
120 | ...
121 | ...
|
B006_B008.py:157:34: B008 Do not perform function call `float` in argument defaults
B006_B008.py:218:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
157 | def float_infinity_literal(value=float("1e999")):
| ^^^^^^^^^^^^^^ B008
158 | pass
|
B006_B008.py:162:30: B008 Do not perform function call `float` in argument defaults
|
162 | # But don't allow standard floats
163 | def float_int_is_wrong(value=float(3)):
| ^^^^^^^^ B008
164 | pass
|
B006_B008.py:166:45: B008 Do not perform function call `float` in argument defaults
|
166 | def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
| ^^^^^^^^^^^^^ B008
167 | pass
|
B006_B008.py:192:21: B008 Do not perform function call `float` in argument defaults
|
192 | # B006 and B008
193 | # We should handle arbitrary nesting of these B008.
194 | def nested_combo(a=[float(3), dt.datetime.now()]):
| ^^^^^^^^ B008
195 | pass
|
B006_B008.py:192:31: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
192 | # B006 and B008
193 | # We should handle arbitrary nesting of these B008.
194 | def nested_combo(a=[float(3), dt.datetime.now()]):
218 | # B006 and B008
219 | # We should handle arbitrary nesting of these B008.
220 | def nested_combo(a=[float(3), dt.datetime.now()]):
| ^^^^^^^^^^^^^^^^^ B008
195 | pass
221 | pass
|
B006_B008.py:198:22: B008 Do not perform function call `map` in argument defaults
B006_B008.py:224:22: B008 Do not perform function call `map` in argument defaults
|
198 | # Don't flag nested B006 since we can't guarantee that
199 | # it isn't made mutable by the outer operation.
200 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
224 | # Don't flag nested B006 since we can't guarantee that
225 | # it isn't made mutable by the outer operation.
226 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
201 | pass
227 | pass
|
B006_B008.py:203:19: B008 Do not perform function call `random.randint` in argument defaults
B006_B008.py:229:19: B008 Do not perform function call `random.randint` in argument defaults
|
203 | # B008-ception.
204 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
229 | # B008-ception.
230 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008
205 | pass
231 | pass
|
B006_B008.py:203:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
B006_B008.py:229:37: B008 Do not perform function call `dt.datetime.now` in argument defaults
|
203 | # B008-ception.
204 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
229 | # B008-ception.
230 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
| ^^^^^^^^^^^^^^^^^ B008
205 | pass
231 | pass
|

View file

@ -11,34 +11,34 @@ RUF009.py:19:41: RUF009 Do not perform function call `default_function` in datac
23 | another_class_var: ClassVar[list[int]] = default_function()
|
RUF009.py:36:41: RUF009 Do not perform function call `default_function` in dataclass defaults
RUF009.py:41:41: RUF009 Do not perform function call `default_function` in dataclass defaults
|
36 | @dataclass
37 | class B:
38 | hidden_mutable_default: list[int] = default_function()
41 | @dataclass
42 | class B:
43 | hidden_mutable_default: list[int] = default_function()
| ^^^^^^^^^^^^^^^^^^ RUF009
39 | another_dataclass: A = A()
40 | not_optimal: ImmutableType = ImmutableType(20)
44 | another_dataclass: A = A()
45 | not_optimal: ImmutableType = ImmutableType(20)
|
RUF009.py:37:28: RUF009 Do not perform function call `A` in dataclass defaults
RUF009.py:42:28: RUF009 Do not perform function call `A` in dataclass defaults
|
37 | class B:
38 | hidden_mutable_default: list[int] = default_function()
39 | another_dataclass: A = A()
42 | class B:
43 | hidden_mutable_default: list[int] = default_function()
44 | another_dataclass: A = A()
| ^^^ RUF009
40 | not_optimal: ImmutableType = ImmutableType(20)
41 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
45 | not_optimal: ImmutableType = ImmutableType(20)
46 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
|
RUF009.py:38:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
RUF009.py:43:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
|
38 | hidden_mutable_default: list[int] = default_function()
39 | another_dataclass: A = A()
40 | not_optimal: ImmutableType = ImmutableType(20)
43 | hidden_mutable_default: list[int] = default_function()
44 | another_dataclass: A = A()
45 | not_optimal: ImmutableType = ImmutableType(20)
| ^^^^^^^^^^^^^^^^^ RUF009
41 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
42 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
46 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
47 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
|

View file

@ -201,12 +201,18 @@ pub fn is_immutable_annotation(model: &SemanticModel, expr: &Expr) -> bool {
}
const IMMUTABLE_FUNCS: &[&[&str]] = &[
&["", "tuple"],
&["", "bool"],
&["", "complex"],
&["", "float"],
&["", "frozenset"],
&["", "int"],
&["", "str"],
&["", "tuple"],
&["datetime", "date"],
&["datetime", "datetime"],
&["datetime", "timedelta"],
&["decimal", "Decimal"],
&["fractions", "Fraction"],
&["operator", "attrgetter"],
&["operator", "itemgetter"],
&["operator", "methodcaller"],