fixhunt

ruff FURB156

labelled unknown
Runs / 2026-10-17T21-33-20-431Z-3mcg-ruff-FURB156ruff 0.17.12026-10-17 21:33 UTC8 programs × 2 round(s)16 programs, 11 fired, 11 fixed, 0 findings, 0 rejectedmodel cost $0.0031

Analysis Nemotron 3 Super

Flags hardcoded strings matching known charset literals like digits or ascii letters and suggests replacing them with corresponding `string` module constants.

Fix: Replaces the hardcoded string literal with an attribute access on the `string` module (e.g., `string.digits`, `string.ascii_lowercase`) and adds `import string` at the top of the file if not already present.

8 hazards
  • If the string literal appears inside a function or class scope, adding `import string` at the module level may cause unnecessary imports or circular import issues in rare cases.
  • If the string is used as a default argument value, the rewrite changes the timing of when the `string` module is accessed (at definition time vs call time), potentially affecting behavior if the module is modified later.
  • If the hardcoded string is part of a larger expression (e.g., concatenation or formatting), replacing only the literal may break the expression or produce a syntax error.
  • If the variable name `string` is already in use in the current scope, the import will shadow it, causing `string.digits` to fail with an AttributeError or refer to the wrong object.
  • If the code relies on the specific identity of the string literal (e.g., for interning or `is` comparisons), the rewrite may produce a different string object, breaking identity checks.
  • If the hardcoded string contains escaped characters (like `\t\n\v\f\r `) and is edited manually, the autofix might not recognize it as whitespace, leading to incomplete fixes.
  • If the string is used in a context where its exact byte representation matters (e.g., encoding, cryptography, or binary protocols), replacing it with a `string` constant could alter behavior if the constant differs in encoding or composition.
  • If the code is generated or templated and the string appears in a comment or docstring, the tool might incorrectly modify it, though the rule should avoid non-code contexts—however, flawed parsing could still lead to erroneous changes.

Programs

idresulttitlehazard
r1-p001fix okString Literal Identity CheckIf the code relies on the specific identity of the string literal (e.g., for interning or `is` comparisons), the rewrite may produce a different string object, breaking identity checks.
r1-p002fix okClass Attribute Descriptor Side Effectclass attributes vs instance attributes, descriptors, properties with side effects
r1-p003fix okFunction Scope Import Causing Circular RiskIf the string literal appears inside a function or class scope, adding `import string` at the module level may cause unnecessary imports or circular import issues in rare cases.
r1-p004fix okComment Attached to Removed Literalcomments and docstrings attached to the removed or moved code
r1-p005silent prediction offDefault Argument Evaluation Oncekeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r1-p006fix okGenerator Consumption Lazinessgenerators vs lists, iterators consumed once, lazy vs eager evaluation
r1-p007silentGenerated Code in Comment MisidentifiedIf the code is generated or templated and the string appears in a comment or docstring, the tool might incorrectly modify it, though the rule should avoid non-code contexts—however, flawed parsing cou
r1-p008fix okException Handling with Finallyexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r2-p001fix okException handling with finally and else clausesExceptions raised inside the rewritten code, finally/else clauses, exception chaining
r2-p002fix okFunction scope import causing circular riskIf the string literal appears inside a function or class scope, adding `import string` at the module level may cause unnecessary imports or circular import issues in rare cases.
r2-p003silent prediction offStar-imports and module-level side effectsstar-imports, module-level side effects, __all__
r2-p004silent prediction offEscaped whitespace in hardcoded stringIf the hardcoded string contains escaped characters (like `\t\n\v\f\r `) and is edited manually, the autofix might not recognize it as whitespace, leading to incomplete fixes.
r2-p005silent prediction offVariable name `string` already in useIf the variable name `string` is already in use in the current scope, the import will shadow it, causing `string.digits` to fail with an AttributeError or refer to the wrong object.
r2-p006fix okBytes vs str, encoding and newline handlingbytes vs str, encoding and newline handling, text vs binary modes
r2-p007fix okFile modes and truncation, partial writesfile modes and truncation, partial writes, non-str data passed to write
r2-p008fix okEvaluation order and side effects in merged expressionsevaluation order and side effects when expressions are merged, duplicated or deleted

Sources

r1-p001 · String Literal Identity Check
1s = "0123456789"
2t = "0123456789"
3print(s is t)
4s_replaced = "0123456789" # This line triggers FURB156
5t_replaced = "0123456789"
6print(s_replaced is t_replaced)
--- orig.py	2026-10-17 17:33:28.992365465 -0400
+++ fixed.py	2026-10-17 17:33:29.156363352 -0400
@@ -1,6 +1,7 @@
-s = "0123456789"
-t = "0123456789"
+import string
+s = string.digits
+t = string.digits
 print(s is t)
-s_replaced = "0123456789"  # This line triggers FURB156
-t_replaced = "0123456789"
+s_replaced = string.digits  # This line triggers FURB156
+t_replaced = string.digits
 print(s_replaced is t_replaced)
True
True
r1-p002 · Class Attribute Descriptor Side Effect
1class Meta(type):
2 def __setattr__(cls, name, value):
3 print(f"Setting {name}={value}")
4 super().__setattr__(name, value)
5
6class C(metaclass=Meta):
7 digits = "0123456789" # Triggers FURB156
8
9print(C.digits)
--- orig.py	2026-10-17 17:33:29.253362103 -0400
+++ fixed.py	2026-10-17 17:33:29.416360003 -0400
@@ -1,9 +1,10 @@
+import string
 class Meta(type):
     def __setattr__(cls, name, value):
         print(f"Setting {name}={value}")
         super().__setattr__(name, value)
 
 class C(metaclass=Meta):
-    digits = "0123456789"  # Triggers FURB156
+    digits = string.digits  # Triggers FURB156
 
 print(C.digits)
0123456789
r1-p003 · Function Scope Import Causing Circular Risk
1def f():
2 try:
3 import string # Local import to avoid circular
4 except ImportError:
5 pass
6 if '0' in "0123456789": # Triggers FURB156
7 return "found"
8 return "not found"
9
10print(f())
--- orig.py	2026-10-17 17:33:29.515358728 -0400
+++ fixed.py	2026-10-17 17:33:29.683356564 -0400
@@ -3,7 +3,7 @@
         import string  # Local import to avoid circular
     except ImportError:
         pass
-    if '0' in "0123456789":  # Triggers FURB156
+    if '0' in string.digits:  # Triggers FURB156
         return "found"
     return "not found"
 
found
r1-p004 · Comment Attached to Removed Literal
1# This comment describes the digit set
2DIGITS = "0123456789" # Triggers FURB156 - used for validation
3print(DIGITS)
--- orig.py	2026-10-17 17:33:29.783355276 -0400
+++ fixed.py	2026-10-17 17:33:29.944353202 -0400
@@ -1,3 +1,4 @@
 # This comment describes the digit set
-DIGITS = "0123456789"  # Triggers FURB156 - used for validation
+import string
+DIGITS = string.digits  # Triggers FURB156 - used for validation
 print(DIGITS)
0123456789
r1-p005 · Default Argument Evaluation Once
1def process(c=[]):
2 if 'a' in "0123456789abcdefghijklmnopqrstuvwxyz": # Triggers FURB156
3 c.append('matched')
4 return c
5
6print(process())
7print(process())
8print(process([]))
['matched']
['matched', 'matched']
['matched']
r1-p006 · Generator Consumption Laziness
1def char_gen():
2 for ch in "abcdefghijklmnopqrstuvwxyz": # Triggers FURB156
3 yield ch
4
5g = char_gen()
6print(list(g))
7print(list(g)) # Should be empty if exhausted
--- orig.py	2026-10-17 17:33:30.176350216 -0400
+++ fixed.py	2026-10-17 17:33:30.349347991 -0400
@@ -1,5 +1,6 @@
+import string
 def char_gen():
-    for ch in "abcdefghijklmnopqrstuvwxyz":  # Triggers FURB156
+    for ch in string.ascii_lowercase:  # Triggers FURB156
         yield ch
 
 g = char_gen()
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
[]
r1-p007 · Generated Code in Comment Misidentified
1## This is a generated template
2## digits = "0123456789" # Should NOT be touched
3## But if flawed parsing occurs, it might break
4print("safe")
safe
r1-p008 · Exception Handling with Finally
1try:
2 if 'x' in "abcdefghijklmnopqrstuvwxyz": # Triggers FURB156
3 raise ValueError("found x")
4except ValueError as e:
5 print(f"Caught: {e}")
6finally:
7 print("Finally block executed")
--- orig.py	2026-10-17 17:33:30.584344968 -0400
+++ fixed.py	2026-10-17 17:33:30.747342871 -0400
@@ -1,5 +1,6 @@
+import string
 try:
-    if 'x' in "abcdefghijklmnopqrstuvwxyz":  # Triggers FURB156
+    if 'x' in string.ascii_lowercase:  # Triggers FURB156
         raise ValueError("found x")
 except ValueError as e:
     print(f"Caught: {e}")
Caught: found x
Finally block executed
r2-p001 · Exception handling with finally and else clauses
1try:
2 if 'x' in '0123456789':
3 raise ValueError('found')
4except ValueError as e:
5 print('caught:', e)
6else:
7 print('no error')
8finally:
9 print('cleanup')
--- orig.py	2026-10-17 17:33:35.099287057 -0400
+++ fixed.py	2026-10-17 17:33:35.266284923 -0400
@@ -1,5 +1,6 @@
+import string
 try:
-    if 'x' in '0123456789':
+    if 'x' in string.digits:
         raise ValueError('found')
 except ValueError as e:
     print('caught:', e)
no error
cleanup
r2-p002 · Function scope import causing circular risk
1def f():
2 return 'a' in '0123456789'
3print(f())
--- orig.py	2026-10-17 17:33:35.367283632 -0400
+++ fixed.py	2026-10-17 17:33:35.526281599 -0400
@@ -1,3 +1,4 @@
+import string
 def f():
-    return 'a' in '0123456789'
+    return 'a' in string.digits
 print(f())
False
r2-p003 · Star-imports and module-level side effects
1from string import *
2print(digits)
3# Rule should not fire here because 'digits' is already imported via star
4# But if it did, it would break due to name conflict after fix attempt
0123456789
r2-p004 · Escaped whitespace in hardcoded string
1s = '\t\n\v\f\r '
2if '\t' in s:
3 print('tab found')
4else:
5 print('tab not found')
tab found
r2-p005 · Variable name `string` already in use
1string = 'overridden'
2try:
3 print(string.upper())
4except AttributeError as e:
5 print('Error:', e)
6# After fix: import string would shadow local var, but we simulate risk by showing what happens if name clashes
OVERRIDDEN
r2-p006 · Bytes vs str, encoding and newline handling
1data = b'0123456789'
2if b'5' in data:
3 print('byte found')
4text = '0123456789'
5if '5' in text:
6 print('char found')
--- orig.py	2026-10-17 17:33:36.009275426 -0400
+++ fixed.py	2026-10-17 17:33:36.179273256 -0400
@@ -1,6 +1,7 @@
+import string
 data = b'0123456789'
 if b'5' in data:
     print('byte found')
-text = '0123456789'
+text = string.digits
 if '5' in text:
     print('char found')
byte found
char found
r2-p007 · File modes and truncation, partial writes
1with open('test.txt', 'w') as f:
2 f.write('0123456789')
3with open('test.txt', 'r') as f:
4 content = f.read()
5 print('read:', repr(content))
6import os
7os.remove('test.txt')
--- orig.py	2026-10-17 17:33:36.277272005 -0400
+++ fixed.py	2026-10-17 17:33:36.437269962 -0400
@@ -1,5 +1,6 @@
+import string
 with open('test.txt', 'w') as f:
-    f.write('0123456789')
+    f.write(string.digits)
 with open('test.txt', 'r') as f:
     content = f.read()
     print('read:', repr(content))
read: '0123456789'
r2-p008 · Evaluation order and side effects in merged expressions
1side = []
2def get_digit_str():
3 side.append('called')
4 return '0123456789'
5if '5' in get_digit_str():
6 print('side effect log:', side)
--- orig.py	2026-10-17 17:33:36.535268711 -0400
+++ fixed.py	2026-10-17 17:33:36.702266580 -0400
@@ -1,6 +1,7 @@
+import string
 side = []
 def get_digit_str():
     side.append('called')
-    return '0123456789'
+    return string.digits
 if '5' in get_digit_str():
     print('side effect log:', side)
side effect log: ['called']