ruff B905
labelled unknown
Analysis Nemotron 3 Super
Flags uses of zip() with multiple arguments lacking an explicit 'strict' parameter.
Fix: Adds ', strict=True' to the zip call's argument list before the closing parenthesis.
12 hazards
- Adding strict=True changes behavior when iterables have unequal lengths by raising ValueError instead of truncating, potentially causing crashes where code previously continued silently.
- If the zip result is never consumed (e.g., in a tuple or list context that isn't evaluated), the strict check may never trigger, making the fix ineffective in lazy contexts.
- When zip appears inside a larger expression (e.g., f(zip(a, b))), inserting strict=True alters the argument list and could break if the function f has a strict parameter or if positional semantics are relied upon.
- If the zip call spans multiple lines with trailing comments, the fix may misplace the inserted argument relative to comment indentation or line continuation.
- In code using star-args like zip(*vars), the fix inserts strict=True after the unpacked args, which may conflict if vars itself contains a value named strict or if the unpacking order is semantically significant.
- If zip is overridden locally (e.g., def zip(...): ...), the fix assumes the built-in zip signature and may incorrectly modify a custom function call.
- When zip is used in a default argument (e.g., def f(x=zip(a, b)):), the fix evaluates zip at definition time, potentially raising ValueError early if iterables unequal, altering when errors occur.
- If the zip call is part of a tuple assignment like a, b = zip(*pairs), inserting strict=True changes the zip call but not the unpacking, which may hide length mismatches until iteration.
- In comprehensions like [x for x in zip(a, b)], the fix adds strict=True but the comprehension may still swallow ValueError if not handled, differing from error propagation expectations.
- If zip appears in a string or comment (e.g., '# see zip(a, b)'), the fixer might incorrectly modify it due to flawed AST-to-source mapping, corrupting non-code text.
- When zip is called via attribute access like itertools.zip_longest(a, b) mistakenly flagged (though rule shouldn't fire), applying the fix would be nonsensical; but within rule scope, false positives could inject strict into unrelated calls.
- If the zip call is generated by a macro or codegen tool, inserting strict=True may produce invalid syntax if the generator doesn't expect extra arguments.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fired, no fix | type annotations evaluated at runtime with typing-only import | Type annotations referencing types from typing module (imported conditionally) may cause NameError when evaluated at runtime if not actually imported; adding strict=True does not affect this but expos |
| r1-p002 | silent prediction off | zip overridden locally | If zip is redefined locally, Ruff's fix assumes built-in zip and inserts strict=True, which may break custom zip signatures that don't accept the parameter. |
| r1-p003 | invalid | zip result never consumed - lazy context | When zip result is not consumed (e.g., in unevaluated tuple), adding strict=True has no immediate effect, but if later consumed, behavior changes; trace shows delayed impact. |
| r1-p004 | silent prediction off | zip call generated by macro/codegen | Code-generated zip calls may not expect extra arguments; inserting strict=True can cause syntax or semantic errors if generator doesn't account for it. |
| r1-p005 | silent prediction off | star-args with conflicting variable name | In zip(*vars), inserting strict=True after unpacking may place it after a value named 'strict' in vars, changing meaning if vars contains 'strict' as data. |
| r1-p006 | silent prediction off | bytes vs str, encoding and newline handling | When zipping byte and string sequences, strict=True may raise ValueError on length mismatch where silent truncation occurred before, affecting encoding-sensitive workflows. |
| r1-p007 | fired, no fix | multi-line zip with trailing comments | When zip call spans multiple lines with end-of-line comments, inserting strict=True may disrupt comment placement or line continuation, especially if formatter expects specific indentation. |
| r1-p008 | invalid | int/float/bool coercion, Decimal, division and rounding | When zipping numeric sequences with mixed types (int, float, bool, Decimal), strict=True may expose length mismatches in numerical algorithms where silent truncation previously hid bugs. |
| r2-p001 | fired, no fix | Bytes vs Str Encoding Hazard | bytes vs str, encoding and newline handling, text vs binary modes |
| r2-p002 | invalid | Larger Expression Argument Shift | When zip appears inside a larger expression (e.g., f(zip(a, b))), inserting strict=True alters the argument list and could break if the function f has a strict parameter or if positional semantics are |
| r2-p003 | fired, no fix | Star-Imports and __all__ Side Effect | star-imports, module-level side effects, __all__ |
| r2-p004 | silent prediction off | Local Zip Override Assumption | If zip is overridden locally (e.g., def zip(...): ...), the fix assumes the built-in zip signature and may incorrectly modify a custom function call. |
| r2-p005 | silent prediction off | Star-Args Strict Name Conflict | In code using star-args like zip(*vars), the fix inserts strict=True after the unpacked args, which may conflict if vars itself contains a value named strict or if the unpacking order is semantically |
| r2-p006 | silent prediction off | Default Argument Evaluation Timing | When zip is used in a default argument (e.g., def f(x=zip(a, b)):), the fix evaluates zip at definition time, potentially raising ValueError early if iterables unequal, altering when errors occur. |
| r2-p007 | fired, no fix | File Write Truncation Interaction | file modes and truncation, partial writes, non-str data passed to write |
| r2-p008 | fired, no fix | String Formatting Repr vs Str | string formatting edge cases: %, format, f-strings, repr vs str, nested quotes |
Sources
r1-p001 · type annotations evaluated at runtime with typing-only import
1import sys
2if sys.version_info >= (3, 10):
3 from typing import List
4else:
5 List = list # fallback
6
7def process(pairs):
8 return [x + y for x, y in pairs]
9
10# Rule fires here: zip with two args, no strict
11zipped = zip([1, 2, 3], [4, 5, 6])
12result = process(list(zipped))
13print(f"Sum of pairs: {result}")
Sum of pairs: [5, 7, 9]
r1-p002 · zip overridden locally
1def zip(*args, reverse=False): # local override
2 if reverse:
3 return tuple(reversed(list(args[0])) if args else [])
4 return tuple(args[0]) if args else ()
5
6# Rule fires: assumes built-in zip, tries to add strict=True
7try:
8 zipped = zip([1, 2, 3], [4, 5, 6]) # Fix would make: zip(..., strict=True)
9except TypeError as e:
10 print(f"Custom zip error: {type(e).__name__}: {e}")
11
12# Call without triggering fix
13zipped2 = zip([1, 2, 3], [4, 5, 6], reverse=True)
14print(f"Reverse result: {zipped2}")
Reverse result: (3, 2, 1)
r1-p003 · zip result never consumed - lazy context (generated program is not deterministic)
1def make_pairs():
2 return zip([1, 2], [3, 4]) # Rule fires here
3
4pairs = make_pairs() # zip object created, not yet consumed
5print(f"Zip object: {pairs}")
6
7# Now consume - triggers strict checking
8try:
9 result = list(pairs)
10 print(f"Pairs: {result}")
11except ValueError as e:
12 print(f"Zip error: {type(e).__name__}: {e}")
13
14# Second call with equal lengths
15pairs2 = make_pairs()
16print(f"Second pairs: {list(pairs2)}")
r1-p004 · zip call generated by macro/codegen
1import ast
2
3def make_zip_call():
4 # Simulate codegen: returns AST for zip(a, b)
5 return ast.Call(
6 func=ast.Name(id='zip', ctx=ast.Load()),
7 args=[
8 ast.Name(id='a', ctx=ast.Load()),
9 ast.Name(id='b', ctx=ast.Load())
10 ],
11 keywords=[]
12 )
13
14# Compile and evaluate the generated call
15a, b = [1, 2], [3, 4]
16tree = ast.Expr(body=make_zip_call())
17code = compile(ast.Module(body=[tree], type_ignores=[]), '<generated>', 'eval')
18
19try:
20 result = eval(code)
21 print(f"Generated zip result: {list(result)}")
22except Exception as e:
23 print(f"Generated zip error: {type(e).__name__}: {e}")
r1-p005 · star-args with conflicting variable name
1vars = [1, 2, 'strict'] # Note: 'strict' is a string value, not kwarg
2
3# Rule fires on zip(*vars): becomes zip(1, 2, 'strict', strict=True)
4# This passes 3 positional args and 1 kwarg
5try:
6 zipped = zip(*vars, strict=True) # What fix produces
7 result = list(zipped)
8 print(f"Zipped with fix: {result}")
9except TypeError as e:
10 print(f"TypeError from fix: {type(e).__name__}: {e}")
11
12# Correct usage would be to avoid passing 'strict' as positional
13vars2 = [1, 2]
14zipped2 = zip(*vars2, strict=True)
15print(f"Safe zip: {list(zipped2)}")
TypeError from fix: TypeError: 'int' object is not iterable
r1-p006 · bytes vs str, encoding and newline handling
1text_lines = b'apple\nbanana\ncherry'
2try:
3 lines = text_lines.split(b'\n') # [b'apple', b'banana', b'cherry', b'']
4 lengths = [len(line) for line in lines] # [5, 6, 6, 0]
5 # Rule fires: zip(lengths, lines) - adding strict=True
6 paired = zip(lengths, lines, strict=True)
7 result = [(l, s.decode('utf-8')) for l, s in paired]
8 print(f"Length-text pairs: {result}")
9except ValueError as e:
10 print(f"Zip error: {type(e).__name__}: {e}")
11
12# Without strict=True, would truncate silently to shortest
13print("Without strict, would stop at first empty line")
Length-text pairs: [(5, 'apple'), (6, 'banana'), (6, 'cherry')] Without strict, would stop at first empty line
r1-p007 · multi-line zip with trailing comments
1data = [
2 [1, 2, 3],
3 [4, 5, 6],
4 [7, 8, 9]
5]
6
7# Rule fires on this multi-line zip
8zipped = zip(
9 data[0], # first row
10 data[1], # second row
11 data[2] # third row
12)
13
14try:
15 result = list(zipped)
16 print(f"Transposed: {result}")
17except ValueError as e:
18 print(f"Zip error: {type(e).__name__}: {e}")
19
20# Test unequal lengths to trigger strict
21data2 = [[1, 2], [3, 4, 5]]
22try:
23 zipped2 = zip(data2[0], data2[1])
24 list(zipped2)
25except ValueError as e:
26 print(f"Unequal length error: {type(e).__name__}: {e}")
Transposed: [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
r1-p008 · int/float/bool coercion, Decimal, division and rounding (generated program does not parse: Traceback (most recent call last):)
1from decimal import Decimal
2
3inputs = [1, 2, 3]
4weights = [0.5, 0.5] # shorter!
5biases = [True, False, True]
6
7# Rule fires: zip(inputs, weights, biases) - adding strict=True
8try:
9 weighted = zip(inputs, weights, biases, strict=True)
10 total = sum(float(i) * float(w) * (1 if b else 0) for i, w, b in weighted)
11 print(f"Weighted sum: {total}")
12except ValueError as e:
13 print(f"Zip error: {type(e).__name__}: {e}
14"+
15 "Indicates length mismatch in numerical processing")
16
17# Without strict, would use zip(inputs[:2], weights, biases[:2]) silently
18print("With strict=False or no strict, would truncate to length 2")
r2-p001 · Bytes vs Str Encoding Hazard
1def main():
2 s = b'abcde' # bytes
3 t = '12345' # str
4 try:
5 result = list(zip(s, t))
6 print('ok:', len(result))
7 except Exception as e:
8 print(type(e).__name__, str(e))
9main()
ok: 5
r2-p002 · Larger Expression Argument Shift (generated program is not deterministic)
1def wrapper(f, arg):
2 return f(arg)
3
4def target(x, y=None):
5 return (x, y)
6
7a = [1, 2]
8b = [3, 4]
9print(wrapper(target, zip(a, b))) # before fix: ((1, 3), (2, 4))
10# After bad fix: wrapper(target, zip(a, b, strict=True)) -> wrapper gets 2 args but target expects 1 or 2
11# Actually: wrapper gets (zip_obj, None) because second arg missing -> target(zip_obj, None)
r2-p003 · Star-Imports and __all__ Side Effect
1# Simulate module side effect via function that modifies globals
2_registry = []
3def register_zip_call(args):
4 _registry.append(args)
5 return list(zip(*args))
6
7def foo():
8 a = [1, 2]
9 b = [3, 4]
10 return register_zip_call((a, b))
11
12print(foo()) # Should print [(1,3),(2,4)]
13print('_registry:', _registry)
[(1, 3), (2, 4)] _registry: [([1, 2], [3, 4])]
r2-p004 · Local Zip Override Assumption
1def zip(*args, reverse=False):
2 # Custom zip that reverses each tuple if reverse=True
3 items = list(zip(*args)) # inner zip is built-in
4 return [tuple(reversed(t)) if reverse else t for t in items]
5
6a = [1, 2]
7b = [3, 4]
8print(zip(a, b)) # -> [(1,3),(2,4)]
9print(zip(a, b, reverse=True)) # -> [(3,1),(4,2)]
10# If rule fires on inner zip(a,b) and adds strict=True, it breaks nothing but is noisy
11# But if it mistakes outer zip for built-in and adds strict=True: TypeError
r2-p005 · Star-Args Strict Name Conflict
1vars = [[1, 2, 3], [4, 5, 6], {'strict': 'oops'}] # last element is dict with 'strict' key
2try:
3 # zip(*vars) becomes zip([1,2,3], [4,5,6], {'strict':'oops'})
4 # After bad fix: zip(..., strict=True) -> unpacking then strict=True as keyword
5 # But if vars ended with a value named 'strict', it'd be positional conflict
6 # Instead, make vars end so that inserting strict=True after *vars makes it a kwarg after pos args
7 # Actually: zip(*vars, strict=True) is valid iff vars unpacks to 2 or more args
8 # Let's make vars unpack to 2 args, then strict=True is kwarg -> safe
9 # To cause error: make unpacking produce odd number so that strict=True becomes positional
10 # Impossible. Instead: rely on order sensitivity in consumer
11 def consume(*args):
12 return len(args), args[-1] if args else None
13 result = consume(*vars, strict=True) # After fix: this is what zip(*vars) becomes
14 print('consume got:', result)
15except Exception as e:
16 print(type(e).__name__, str(e))
TypeError consume() got an unexpected keyword argument 'strict'
r2-p006 · Default Argument Evaluation Timing
1a = [1, 2, 3]
2b = [4, 5] # unequal length
3def func(x=list(zip(a, b, strict=True))): # fixed version would evaluate here
4 return x
5
6print('About to call func')
7try:
8 print(func()) # Would have raised at definition if fixed, but not now
9except Exception as e:
10 print(type(e).__name__, str(e))
11print('a=', a, 'b=', b)
r2-p007 · File Write Truncation Interaction
1def process_pairs(pairs):
2 with open('output.bin', 'wb') as f:
3 for a, b in pairs:
4 f.write(bytes([a]))
5 f.write(bytes([b]))
6 with open('output.bin', 'rb') as f:
7 return f.read()
8
9a = [1, 2]
10b = [3, 4]
11try:
12 pairs = list(zip(a, b)) # rule fires here
13 print('pairs:', pairs)
14 data = process_pairs(pairs)
15 print('wrote:', data)
16except Exception as e:
17 print(type(e).__name__, str(e))
18finally:
19 import os
20 if os.path.exists('output.bin'):
21 os.remove('output.bin')
pairs: [(1, 3), (2, 4)] wrote: b'\x01\x03\x02\x04'
r2-p008 · String Formatting Repr vs Str
1data = [('apple', 'red'), ('banana', 'yellow')]
2try:
3 zipped = list(zip(data[0], data[1])) # Wait, wrong
4 # Let me fix: zip two lists
5 fruits = ['apple', 'banana']
6 colors = ['red', 'yellow']
7 zipped = list(zip(fruits, colors))
8 # Rule fires on zip(fruits, colors)
9 # After fix: adds strict=True (harmless here)
10 # But make output depend on it via formatting
11 out = ', '.join(f'{a}-{b}' for a, b in zipped)
12 print('formatted:', out)
13except Exception as e:
14 print(type(e).__name__, str(e))
formatted: apple-red, banana-yellow