Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow more immutable funcs for RUF009 #4660

Merged
merged 4 commits into from May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 29 additions & 3 deletions crates/ruff/resources/test/fixtures/flake8_bugbear/B006_B008.py
@@ -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
Expand Down Expand Up @@ -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


Expand Down
9 changes: 7 additions & 2 deletions crates/ruff/resources/test/fixtures/ruff/RUF009.py
Expand Up @@ -2,10 +2,10 @@
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 []

Expand All @@ -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])
Expand Down
@@ -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};
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down
@@ -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
|