fixhunt

ruff SIM910

labelled unknown
Runs / 2026-10-17T20-38-18-801Z-8fil-ruff-SIM910ruff 0.17.12026-10-17 20:38 UTC8 programs × 2 round(s)16 programs, 10 fired, 10 fixed, 0 findings, 1 rejectedmodel cost $0.0049

Analysis Nemotron 3 Super

Flags dict.get() calls where the second argument is explicitly None.

Fix: Removes the ', None' argument from dict.get() calls, leaving only the key argument.

8 hazards
  • If the call includes a trailing comment after the closing parenthesis, the comment may be lost during rewrite.
  • If the default argument is a complex expression with side effects (though None has none), removing it could alter evaluation order in hypothetical extended cases.
  • In code where None is used as a sentinel value distinct from the key's absence, removing it could change logic if the default were ever non-None (but rule only triggers on None).
  • If the get call is part of a larger expression where the parentheses affect grouping, removing arguments could alter precedence (unlikely but possible in nested calls).
  • If the dictionary get is overridden in a subclass where the default parameter has special meaning beyond the built-in, the fix assumes standard behavior.
  • If the code relies on the explicit None for documentation or clarity, removing it reduces expressiveness (semantic hazard).
  • If the line contains irrelevant whitespace formatting that the rewrite alters, it might affect tools sensitive to exact formatting.
  • If the get call is generated by a macro or codegen tool that expects the two-argument form, rewriting could break tooling expectations.

Programs

idresulttitlehazard
r1-p001fix okSIM910 Hazard 1: Sentinel Value ConfusionRemoving ', None' changes behavior when None is used as a sentinel to distinguish missing keys from actual None values.
r1-p002fix okSIM910 Hazard 2: Walrus and Comprehension ScopingIn a list comprehension with walrus operator, the get() call's second argument affects what value is captured and reused; removing ', None' alters the walrus assignment and thus the comprehension's ou
r1-p003silent prediction offSIM910 Hazard 3: Iterator Consumption in GeneratorWhen dict.get() is used inside a generator expression that is consumed multiple times, removing ', None' doesn't change the value but reveals that the generator was already exhausted — though here the
r1-p004silent prediction offSIM910 Hazard 4: Subclass Override with Special Default MeaningIf a subclass overrides dict.get() to give special meaning to the default argument (e.g., logging or transformation), removing ', None' breaks the intended behavior because the fix assumes built-in se
r1-p005fix okSIM910 Hazard 5: pathlib vs os.path BehaviorUsing dict.get() to cache path existence checks; removing ', None' doesn't change the value but the hazard is illustrated by showing how path resolution differs between os.path and pathlib — the get()
r1-p006invalidSIM910 Hazard 6: Side Effects in Default Argument (Hypothetical)Although None has no side effects, the rule encourages removing the argument; this program shows what *would* happen if the default had side effects (like a function call), proving that argument remov
r1-p007fix okSIM910 Hazard 7: String Formatting Edge CasesThe result of dict.get() is used in string formatting; removing ', None' doesn't change the value here, but the hazard is shown by formatting None vs missing key in different ways (%s, .format(), f-st
r1-p008silent prediction offSIM910 Hazard 8: Code-Generated Two-Argument Form ExpectationA code generation tool produces dict.get(key, None) calls expecting the two-argument form for AST parsing or templating; removing ', None' breaks the tool's expectation that the call has exactly two a
r2-p001silent prediction offSide Effect Evaluation OrderSIM910 Hazard 6: Side Effects in Default Argument (Hypothetical)
r2-p002fix okString Formatting Edge CasesSIM910 Hazard 7: String Formatting Edge Cases
r2-p003fix okExplicit None for Documentation ClaritySIM910 Hazard 3: Explicit None for Documentation or Clarity
r2-p004fix okFile Modes and Truncation with Non-Str DataSIM910 Hazard 4: File Modes and Truncation, Partial Writes, Non-Str Data
r2-p005fix okRuntime Type Annotations EvaluationSIM910 Hazard 5: Type Annotations Evaluated at Runtime, Typing-Only Imports
r2-p006silent prediction offEvaluation Order with Merged/Side-Effect ExpressionsSIM910 Hazard 6: Evaluation Order and Side Effects When Expressions Are Merged, Duplicated or Deleted
r2-p007fix okTruthiness Custom Bool Len Is Vs EqSIM910 Hazard 7: Truthiness of Custom __bool__/__len__, None vs Falsy, 'is' vs '=='
r2-p008fix okContext Managers Not Files Objects With Write But No CloseSIM910 Hazard 8: Context Managers That Are Not Files, Objects With write() But No close()

Sources

r1-p001 · SIM910 Hazard 1: Sentinel Value Confusion
1d = {'a': None, 'b': 1}
2result = []
3for k in ['a', 'b', 'c']:
4 v = d.get(k, None)
5 if v is None:
6 result.append((k, 'missing_or_none'))
7 else:
8 result.append((k, 'present'))
9print(result)
--- orig.py	2026-10-17 16:38:28.712341042 -0400
+++ fixed.py	2026-10-17 16:38:28.897350239 -0400
@@ -1,7 +1,7 @@
 d = {'a': None, 'b': 1}
 result = []
 for k in ['a', 'b', 'c']:
-    v = d.get(k, None)
+    v = d.get(k)
     if v is None:
         result.append((k, 'missing_or_none'))
     else:
[('a', 'missing_or_none'), ('b', 'present'), ('c', 'missing_or_none')]
r1-p002 · SIM910 Hazard 2: Walrus and Comprehension Scoping
1data = {'x': 10}
2result = [(v := data.get(k, None)) for k in ['x', 'y'] if v is not None]
3print(result)
--- orig.py	2026-10-17 16:38:29.005355607 -0400
+++ fixed.py	2026-10-17 16:38:29.399375163 -0400
@@ -1,3 +1,3 @@
 data = {'x': 10}
-result = [(v := data.get(k, None)) for k in ['x', 'y'] if v is not None]
+result = [(v := data.get(k)) for k in ['x', 'y'] if v is not None]
 print(result)
r1-p003 · SIM910 Hazard 3: Iterator Consumption in Generator
1def make_getter(d):
2 return lambda k: d.get(k, None)
3d = {'a': 1}
4getter = make_getter(d)
5vals = [getter(k) for k in ['a', 'b']]
6print(vals)
[1, None]
r1-p004 · SIM910 Hazard 4: Subclass Override with Special Default Meaning
1class CustomDict(dict):
2 def get(self, key, default):
3 if default is None:
4 return self.__missing__(key)
5 return super().get(key, default)
6 def __missing__(self, key):
7 return f'missing:{key}'
8
9cd = CustomDict({'a': 1})
10print(cd.get('b', None)) # Should log missing via __missing__
11cd['b'] = 2
12print(cd.get('b', None)) # Now returns 2
missing:b
missing:b
r1-p005 · SIM910 Hazard 5: pathlib vs os.path Behavior
1import os
2from pathlib import Path
3cache = {}
4def check_path(p):
5 if p not in cache:
6 cache[p] = Path(p).exists()
7 return cache.get(p, None)
8
9print(check_path('nonexistent.txt'))
10print(check_path('nonexistent.txt')) # Should be cached
--- orig.py	2026-10-17 16:38:29.886399334 -0400
+++ fixed.py	2026-10-17 16:38:30.085409204 -0400
@@ -4,7 +4,7 @@
 def check_path(p):
     if p not in cache:
         cache[p] = Path(p).exists()
-    return cache.get(p, None)
+    return cache.get(p)
 
 print(check_path('nonexistent.txt'))
 print(check_path('nonexistent.txt'))  # Should be cached
False
False
r1-p006 · SIM910 Hazard 6: Side Effects in Default Argument (Hypothetical) (generated program does not parse: Traceback (most recent call last):)
1counter = 0
2side_effect = lambda: nonlocal_counter := counter + 1 or exec('nonlocal_counter = 1')
3# Actually, simpler: use a function that prints
4def f():
5 print('side effect')
6 return 99
7
8d = {}
9# This would be dangerous if default had side effects, but we show the call shape
10v = d.get('missing', None) # Rule triggers
11print(f'got: {v}')
r1-p007 · SIM910 Hazard 7: String Formatting Edge Cases
1d = {'name': 'Alice'}
2for fmt in ['%s', '{}', f'{d.get("age", None)}']:
3 try:
4 if fmt == '%s':
5 res = fmt % d.get('age', None)
6 elif fmt == '{}':
7 res = fmt.format(d.get('age', None))
8 else:
9 res = eval(fmt) # f-string
10 print(repr(res))
11 except Exception as e:
12 print(f'{type(e).__name__}: {e}')
--- orig.py	2026-10-17 16:38:30.349422287 -0400
+++ fixed.py	2026-10-17 16:38:30.515430512 -0400
@@ -1,10 +1,10 @@
 d = {'name': 'Alice'}
-for fmt in ['%s', '{}', f'{d.get("age", None)}']:
+for fmt in ['%s', '{}', f'{d.get("age")}']:
     try:
         if fmt == '%s':
-            res = fmt % d.get('age', None)
+            res = fmt % d.get('age')
         elif fmt == '{}':
-            res = fmt.format(d.get('age', None))
+            res = fmt.format(d.get('age'))
         else:
             res = eval(fmt)  # f-string
         print(repr(res))
'None'
'None'
None
r1-p008 · SIM910 Hazard 8: Code-Generated Two-Argument Form Expectation
1import ast
2code = "d.get('x', None)"
3tree = ast.parse(code, mode='eval')
4call = tree.body
5if isinstance(call, ast.Call):
6 print(f'args: {len(call.args)}, keywords: {len(call.keywords)}')
7 print(f'default arg: {ast.unparse(call.args[1]) if len(call.args) > 1 else "missing"}')
args: 2, keywords: 0
default arg: None
r2-p001 · Side Effect Evaluation Order
1seq = []
2log = lambda x: (seq.append(x), x)[1]
3d = {'a': 1}
4def test(): return log('default')
5val = d.get('b', test())
6print('result:', val, 'log:', seq)
result: default log: ['default']
r2-p002 · String Formatting Edge Cases
1d = {}
2v = d.get('x', None)
3s = '%r %s' % (v, v)
4print(s)
5t = '{!r} {!s}'.format(v, v)
6print(t)
7f = f'{v!r} {v!s}'
8print(f)
--- orig.py	2026-10-17 16:38:42.984042302 -0400
+++ fixed.py	2026-10-17 16:38:43.150050362 -0400
@@ -1,5 +1,5 @@
 d = {}
-v = d.get('x', None)
+v = d.get('x')
 s = '%r %s' % (v, v)
 print(s)
 t = '{!r} {!s}'.format(v, v)
None None
None None
None None
r2-p003 · Explicit None for Documentation Clarity
1# Using .get(key, None) to signal intentional missing-key handling
2d = {'present': 42}
3v1 = d.get('present', None) # explicit None for clarity
4v2 = d.get('missing', None) # explicit None for clarity
5print('present:', v1, 'missing:', v2)
6# After fix: v1/v2 become d.get('present'), d.get('missing')
7# Behavior same here, but intent obscured in larger code
--- orig.py	2026-10-17 16:38:43.253055362 -0400
+++ fixed.py	2026-10-17 16:38:43.424063664 -0400
@@ -1,7 +1,7 @@
 # Using .get(key, None) to signal intentional missing-key handling
 d = {'present': 42}
-v1 = d.get('present', None)  # explicit None for clarity
-v2 = d.get('missing', None)  # explicit None for clarity
+v1 = d.get('present')  # explicit None for clarity
+v2 = d.get('missing')  # explicit None for clarity
 print('present:', v1, 'missing:', v2)
 # After fix: v1/v2 become d.get('present'), d.get('missing')
 # Behavior same here, but intent obscured in larger code
present: 42 missing: None
r2-p004 · File Modes and Truncation with Non-Str Data
1cache = {}
2def get_data(key):
3 return cache.get(key, None)
4for i in range(2):
5 data = get_data('x')
6 try:
7 with open('tmp.bin', 'wb') as f:
8 if data is None:
9 data = b''
10 f.write(data)
11 except Exception as e:
12 print(type(e).__name__, str(e))
13import os
14if os.path.exists('tmp.bin'):
15 os.remove('tmp.bin')
--- orig.py	2026-10-17 16:38:43.520068324 -0400
+++ fixed.py	2026-10-17 16:38:43.681076141 -0400
@@ -1,6 +1,6 @@
 cache = {}
 def get_data(key):
-    return cache.get(key, None)
+    return cache.get(key)
 for i in range(2):
     data = get_data('x')
     try:
r2-p005 · Runtime Type Annotations Evaluation
1from typing import Dict, Any
2def fetch(mapping: Dict[str, Any], key: str) -> Any:
3 return mapping.get(key, None)
4d: Dict[str, Any] = {}
5result = fetch(d, 'missing')
6print('result:', result, 'type:', type(result))
7# Rule triggers on mapping.get(key, None) -> removes None
8# Same behavior, but shows interaction with typed function
--- orig.py	2026-10-17 16:38:43.779080898 -0400
+++ fixed.py	2026-10-17 16:38:43.964089879 -0400
@@ -1,6 +1,6 @@
 from typing import Dict, Any
 def fetch(mapping: Dict[str, Any], key: str) -> Any:
-    return mapping.get(key, None)
+    return mapping.get(key)
 d: Dict[str, Any] = {}
 result = fetch(d, 'missing')
 print('result:', result, 'type:', type(result))
result: None type: <class 'NoneType'>
r2-p006 · Evaluation Order with Merged/Side-Effect Expressions
1ref = [0]
2def side():
3 ref[0] += 1
4 return ref[0]
5d = {}
6# Expression with side effect in default position
7val = d.get('missing', side())
8print('val:', val, 'ref:', ref[0])
9# Second call shows accumulated effect
10val2 = d.get('missing', side())
11print('val2:', val2, 'ref:', ref[0])
val: 1 ref: 1
val2: 2 ref: 2
r2-p007 · Truthiness Custom Bool Len Is Vs Eq
1class Obj:
2 def __bool__(self): return False
3 def __repr__(self): return 'Obj()'
4d = {'k': Obj()}
5v = d.get('k', None)
6print('v is None:', v is None, 'v == None:', v == None, 'bool(v):', bool(v))
7v2 = d.get('missing', None)
8print('v2 is None:', v2 is None, 'v2 == None:', v2 == None, 'bool(v2):', bool(v2))
--- orig.py	2026-10-17 16:38:44.202101418 -0400
+++ fixed.py	2026-10-17 16:38:44.372109658 -0400
@@ -2,7 +2,7 @@
     def __bool__(self): return False
     def __repr__(self): return 'Obj()'
 d = {'k': Obj()}
-v = d.get('k', None)
+v = d.get('k')
 print('v is None:', v is None, 'v == None:', v == None, 'bool(v):', bool(v))
-v2 = d.get('missing', None)
+v2 = d.get('missing')
 print('v2 is None:', v2 is None, 'v2 == None:', v2 == None, 'bool(v2):', bool(v2))
v is None: False v == None: False bool(v): False
v2 is None: True v2 == None: True bool(v2): False
r2-p008 · Context Managers Not Files Objects With Write But No Close
1class Logger:
2 def write(self, msg): print('LOG:', msg)
3 def __enter__(self): return self
4 def __exit__(self, *exc): pass
5cache = {}
6obj = Logger()
7with cache.get('logger', None) or Logger() as log:
8 log.write('test')
9print('done')
--- orig.py	2026-10-17 16:38:44.469114360 -0400
+++ fixed.py	2026-10-17 16:38:44.632122261 -0400
@@ -4,6 +4,6 @@
     def __exit__(self, *exc): pass
 cache = {}
 obj = Logger()
-with cache.get('logger', None) or Logger() as log:
+with cache.get('logger') or Logger() as log:
     log.write('test')
 print('done')
LOG: test
done