From 6d73b45bdd651658803fb3add5aee36c48746fe5 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 25 May 2023 17:11:27 +0200 Subject: [PATCH 1/4] Allow `float` for RUF009 --- .../resources/test/fixtures/ruff/RUF009.py | 1 + ...bugbear__tests__B008_B006_B008.py.snap.new | 84 +++++++++++++++++++ ...es__ruff__tests__RUF009_RUF009.py.snap.new | 45 ++++++++++ .../src/analyze/typing.rs | 1 + 4 files changed, 131 insertions(+) create mode 100644 crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new create mode 100644 crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF009.py b/crates/ruff/resources/test/fixtures/ruff/RUF009.py index 53c6a0598e1bc..9a40aa52d7229 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF009.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF009.py @@ -25,6 +25,7 @@ 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') DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40) diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new new file mode 100644 index 0000000000000..3fef0a0811081 --- /dev/null +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new @@ -0,0 +1,84 @@ +--- +source: crates/ruff/src/rules/flake8_bugbear/mod.rs +assertion_line: 57 +--- +B006_B008.py:87: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)]): + | ^^^^^^^^ B008 +89 | pass + | + +B006_B008.py:91: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)}): + | ^^^^^^^^ B008 +92 | pass + | + +B006_B008.py:95: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)}): + | ^^^^^^^^ B008 +96 | pass + | + +B006_B008.py:111: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()): + | ^^^^^^^^^^^ B008 +114 | ... + | + +B006_B008.py:115:12: B008 Do not perform function call `dt.datetime.now` in argument defaults + | +115 | def f(when=dt.datetime.now() + dt.timedelta(days=7)): + | ^^^^^^^^^^^^^^^^^ B008 +116 | pass + | + +B006_B008.py:119:30: B008 Do not perform function call in argument defaults + | +119 | def can_even_catch_lambdas(a=(lambda x: x)()): + | ^^^^^^^^^^^^^^^ B008 +120 | ... + | + +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()]): + | ^^^^^^^^^^^^^^^^^ B008 +195 | pass + | + +B006_B008.py:198: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"])): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008 +201 | pass + | + +B006_B008.py:203: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)): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008 +205 | pass + | + +B006_B008.py:203: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)): + | ^^^^^^^^^^^^^^^^^ B008 +205 | pass + | + + diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new new file mode 100644 index 0000000000000..f982c18bc3174 --- /dev/null +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new @@ -0,0 +1,45 @@ +--- +source: crates/ruff/src/rules/ruff/mod.rs +assertion_line: 174 +--- +RUF009.py:19:41: RUF009 Do not perform function call `default_function` in dataclass defaults + | +19 | @dataclass() +20 | class A: +21 | hidden_mutable_default: list[int] = default_function() + | ^^^^^^^^^^^^^^^^^^ RUF009 +22 | class_variable: typing.ClassVar[list[int]] = default_function() +23 | another_class_var: ClassVar[list[int]] = default_function() + | + +RUF009.py:37:41: RUF009 Do not perform function call `default_function` in dataclass defaults + | +37 | @dataclass +38 | class B: +39 | hidden_mutable_default: list[int] = default_function() + | ^^^^^^^^^^^^^^^^^^ RUF009 +40 | another_dataclass: A = A() +41 | not_optimal: ImmutableType = ImmutableType(20) + | + +RUF009.py:38:28: RUF009 Do not perform function call `A` in dataclass defaults + | +38 | class B: +39 | hidden_mutable_default: list[int] = default_function() +40 | another_dataclass: A = A() + | ^^^ RUF009 +41 | not_optimal: ImmutableType = ImmutableType(20) +42 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES + | + +RUF009.py:39:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults + | +39 | hidden_mutable_default: list[int] = default_function() +40 | another_dataclass: A = A() +41 | not_optimal: ImmutableType = ImmutableType(20) + | ^^^^^^^^^^^^^^^^^ RUF009 +42 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +43 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES + | + + diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index e9c6bf4a8f8ab..475ab34d05012 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -201,6 +201,7 @@ pub fn is_immutable_annotation(model: &SemanticModel, expr: &Expr) -> bool { } const IMMUTABLE_FUNCS: &[&[&str]] = &[ + &["", "float"], &["", "tuple"], &["", "frozenset"], &["datetime", "date"], From d7b812b4fdbc9d439f76adcb676424f6854b6d43 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Thu, 25 May 2023 18:36:03 +0200 Subject: [PATCH 2/4] Allow `int` and `complex` for RUF009 --- .../resources/test/fixtures/ruff/RUF009.py | 2 + ...ke8_bugbear__tests__B008_B006_B008.py.snap | 31 ------- ...bugbear__tests__B008_B006_B008.py.snap.new | 84 ------------------- ..._rules__ruff__tests__RUF009_RUF009.py.snap | 36 ++++---- ...es__ruff__tests__RUF009_RUF009.py.snap.new | 45 ---------- .../src/analyze/typing.rs | 2 + 6 files changed, 22 insertions(+), 178 deletions(-) delete mode 100644 crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new delete mode 100644 crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF009.py b/crates/ruff/resources/test/fixtures/ruff/RUF009.py index 9a40aa52d7229..7a9e14424fc0d 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF009.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF009.py @@ -26,6 +26,8 @@ class A: 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) DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40) diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap index 7ec11c3ab1900..cbe5a767c7da4 100644 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap @@ -46,37 +46,6 @@ B006_B008.py:119:30: B008 Do not perform function call in argument defaults 120 | ... | -B006_B008.py:157:34: B008 Do not perform function call `float` 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 diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new deleted file mode 100644 index 3fef0a0811081..0000000000000 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap.new +++ /dev/null @@ -1,84 +0,0 @@ ---- -source: crates/ruff/src/rules/flake8_bugbear/mod.rs -assertion_line: 57 ---- -B006_B008.py:87: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)]): - | ^^^^^^^^ B008 -89 | pass - | - -B006_B008.py:91: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)}): - | ^^^^^^^^ B008 -92 | pass - | - -B006_B008.py:95: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)}): - | ^^^^^^^^ B008 -96 | pass - | - -B006_B008.py:111: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()): - | ^^^^^^^^^^^ B008 -114 | ... - | - -B006_B008.py:115:12: B008 Do not perform function call `dt.datetime.now` in argument defaults - | -115 | def f(when=dt.datetime.now() + dt.timedelta(days=7)): - | ^^^^^^^^^^^^^^^^^ B008 -116 | pass - | - -B006_B008.py:119:30: B008 Do not perform function call in argument defaults - | -119 | def can_even_catch_lambdas(a=(lambda x: x)()): - | ^^^^^^^^^^^^^^^ B008 -120 | ... - | - -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()]): - | ^^^^^^^^^^^^^^^^^ B008 -195 | pass - | - -B006_B008.py:198: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"])): - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008 -201 | pass - | - -B006_B008.py:203: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)): - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B008 -205 | pass - | - -B006_B008.py:203: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)): - | ^^^^^^^^^^^^^^^^^ B008 -205 | pass - | - - diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap index 096591e7f9be4..2256025a164a8 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap @@ -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:39: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() +39 | @dataclass +40 | class B: +41 | hidden_mutable_default: list[int] = default_function() | ^^^^^^^^^^^^^^^^^^ RUF009 -39 | another_dataclass: A = A() -40 | not_optimal: ImmutableType = ImmutableType(20) +42 | another_dataclass: A = A() +43 | not_optimal: ImmutableType = ImmutableType(20) | -RUF009.py:37:28: RUF009 Do not perform function call `A` in dataclass defaults +RUF009.py:40: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() +40 | class B: +41 | hidden_mutable_default: list[int] = default_function() +42 | another_dataclass: A = A() | ^^^ RUF009 -40 | not_optimal: ImmutableType = ImmutableType(20) -41 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +43 | not_optimal: ImmutableType = ImmutableType(20) +44 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES | -RUF009.py:38:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults +RUF009.py:41: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) +41 | hidden_mutable_default: list[int] = default_function() +42 | another_dataclass: A = A() +43 | not_optimal: ImmutableType = ImmutableType(20) | ^^^^^^^^^^^^^^^^^ RUF009 -41 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES -42 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES +44 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES +45 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES | diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new deleted file mode 100644 index f982c18bc3174..0000000000000 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap.new +++ /dev/null @@ -1,45 +0,0 @@ ---- -source: crates/ruff/src/rules/ruff/mod.rs -assertion_line: 174 ---- -RUF009.py:19:41: RUF009 Do not perform function call `default_function` in dataclass defaults - | -19 | @dataclass() -20 | class A: -21 | hidden_mutable_default: list[int] = default_function() - | ^^^^^^^^^^^^^^^^^^ RUF009 -22 | class_variable: typing.ClassVar[list[int]] = default_function() -23 | another_class_var: ClassVar[list[int]] = default_function() - | - -RUF009.py:37:41: RUF009 Do not perform function call `default_function` in dataclass defaults - | -37 | @dataclass -38 | class B: -39 | hidden_mutable_default: list[int] = default_function() - | ^^^^^^^^^^^^^^^^^^ RUF009 -40 | another_dataclass: A = A() -41 | not_optimal: ImmutableType = ImmutableType(20) - | - -RUF009.py:38:28: RUF009 Do not perform function call `A` in dataclass defaults - | -38 | class B: -39 | hidden_mutable_default: list[int] = default_function() -40 | another_dataclass: A = A() - | ^^^ RUF009 -41 | not_optimal: ImmutableType = ImmutableType(20) -42 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES - | - -RUF009.py:39:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults - | -39 | hidden_mutable_default: list[int] = default_function() -40 | another_dataclass: A = A() -41 | not_optimal: ImmutableType = ImmutableType(20) - | ^^^^^^^^^^^^^^^^^ RUF009 -42 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES -43 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES - | - - diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 475ab34d05012..a2e7894d03f8f 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -202,6 +202,8 @@ pub fn is_immutable_annotation(model: &SemanticModel, expr: &Expr) -> bool { const IMMUTABLE_FUNCS: &[&[&str]] = &[ &["", "float"], + &["", "int"], + &["", "complex"], &["", "tuple"], &["", "frozenset"], &["datetime", "date"], From d394df27512e8d21e1e6c086664d6a18116eaac8 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Fri, 26 May 2023 20:57:29 +0200 Subject: [PATCH 3/4] Add `bool`, `int`, `str` and `Fraction` to IMMUTABLE_FUNCS adapt tests --- .../test/fixtures/flake8_bugbear/B006_B008.py | 32 +++++- .../resources/test/fixtures/ruff/RUF009.py | 6 +- ...ke8_bugbear__tests__B006_B006_B008.py.snap | 106 +++++++++--------- ...ke8_bugbear__tests__B008_B006_B008.py.snap | 78 ++++++------- ..._rules__ruff__tests__RUF009_RUF009.py.snap | 36 +++--- .../src/analyze/typing.rs | 7 +- 6 files changed, 148 insertions(+), 117 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_bugbear/B006_B008.py b/crates/ruff/resources/test/fixtures/flake8_bugbear/B006_B008.py index 0fa3d6dc5bc6f..297e3ca49e837 100644 --- a/crates/ruff/resources/test/fixtures/flake8_bugbear/B006_B008.py +++ b/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 @@ -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 diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF009.py b/crates/ruff/resources/test/fixtures/ruff/RUF009.py index 7a9e14424fc0d..3ba1aad6bed96 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF009.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF009.py @@ -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 [] @@ -28,7 +28,9 @@ class A: 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]) diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B006_B006_B008.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B006_B006_B008.py.snap index 2cb0b33275e85..ff021329cc4bf 100644 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B006_B006_B008.py.snap +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B006_B006_B008.py.snap @@ -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 | diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap index cbe5a767c7da4..514e2b2f56f0e 100644 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B008_B006_B008.py.snap @@ -1,83 +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:192:31: B008 Do not perform function call `dt.datetime.now` in argument defaults +B006_B008.py:218: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 | diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap index 2256025a164a8..48f097a86b620 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF009_RUF009.py.snap @@ -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:39: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 | -39 | @dataclass -40 | class B: -41 | hidden_mutable_default: list[int] = default_function() +41 | @dataclass +42 | class B: +43 | hidden_mutable_default: list[int] = default_function() | ^^^^^^^^^^^^^^^^^^ RUF009 -42 | another_dataclass: A = A() -43 | not_optimal: ImmutableType = ImmutableType(20) +44 | another_dataclass: A = A() +45 | not_optimal: ImmutableType = ImmutableType(20) | -RUF009.py:40: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 | -40 | class B: -41 | hidden_mutable_default: list[int] = default_function() -42 | another_dataclass: A = A() +42 | class B: +43 | hidden_mutable_default: list[int] = default_function() +44 | another_dataclass: A = A() | ^^^ RUF009 -43 | not_optimal: ImmutableType = ImmutableType(20) -44 | 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:41: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 | -41 | hidden_mutable_default: list[int] = default_function() -42 | another_dataclass: A = A() -43 | 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 -44 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES -45 | 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 | diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index a2e7894d03f8f..8bbde2990b101 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -201,15 +201,18 @@ pub fn is_immutable_annotation(model: &SemanticModel, expr: &Expr) -> bool { } const IMMUTABLE_FUNCS: &[&[&str]] = &[ + &["", "bool"], + &["", "complex"], &["", "float"], + &["", "frozenset"], &["", "int"], - &["", "complex"], + &["", "str"], &["", "tuple"], - &["", "frozenset"], &["datetime", "date"], &["datetime", "datetime"], &["datetime", "timedelta"], &["decimal", "Decimal"], + &["fractions", "Fraction"], &["operator", "attrgetter"], &["operator", "itemgetter"], &["operator", "methodcaller"], From 296c6bcac3b89618c68c747e59e2a086b6f41370 Mon Sep 17 00:00:00 2001 From: qdegraaf Date: Fri, 26 May 2023 21:09:01 +0200 Subject: [PATCH 4/4] Remove special infitinity/nan logic from flake8-bugbear --- .../rules/function_call_argument_default.rs | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/function_call_argument_default.rs b/crates/ruff/src/rules/flake8_bugbear/rules/function_call_argument_default.rs index 0d013ec7d3a8e..a36b571c12fad 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/function_call_argument_default.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/function_call_argument_default.rs @@ -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.