mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-28 02:39:59 +00:00 
			
		
		
		
	 793ff9bdbc
			
		
	
	
		793ff9bdbc
		
			
		
	
	
	
		
			
	
		
	
	
		
			Some checks are pending
		
		
	
	CI / Determine changes (push) Waiting to run
				
			CI / cargo fmt (push) Waiting to run
				
			CI / cargo clippy (push) Blocked by required conditions
				
			CI / cargo test (linux) (push) Blocked by required conditions
				
			CI / cargo test (linux, release) (push) Blocked by required conditions
				
			CI / cargo test (windows) (push) Blocked by required conditions
				
			CI / cargo test (wasm) (push) Blocked by required conditions
				
			CI / cargo build (release) (push) Waiting to run
				
			CI / cargo build (msrv) (push) Blocked by required conditions
				
			CI / cargo fuzz build (push) Blocked by required conditions
				
			CI / fuzz parser (push) Blocked by required conditions
				
			CI / test scripts (push) Blocked by required conditions
				
			CI / mkdocs (push) Waiting to run
				
			CI / ecosystem (push) Blocked by required conditions
				
			CI / Fuzz for new ty panics (push) Blocked by required conditions
				
			CI / cargo shear (push) Blocked by required conditions
				
			CI / python package (push) Waiting to run
				
			CI / pre-commit (push) Waiting to run
				
			CI / formatter instabilities and black similarity (push) Blocked by required conditions
				
			CI / test ruff-lsp (push) Blocked by required conditions
				
			CI / check playground (push) Blocked by required conditions
				
			CI / benchmarks (push) Blocked by required conditions
				
			[ty Playground] Release / publish (push) Waiting to run
				
			<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? --> ## Summary Fixes false positive in B909 (`loop-iterator-mutation`) where mutations inside return/break statements were incorrectly flagged as violations. The fix adds tracking for when mutations occur within return/break statements and excludes them from violation detection, as they don't cause the iteration issues B909 is designed to prevent. ## Test Plan - Added test cases covering the reported false positive scenarios to `B909.py` - Verified existing B909 tests continue to pass (no regressions) - Ran `cargo test -p ruff_linter --lib flake8_bugbear` successfully Fixes #18399
		
			
				
	
	
		
			195 lines
		
	
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Should emit:
 | |
| B909 - on lines 11, 25, 26, 40, 46
 | |
| """
 | |
| 
 | |
| # lists
 | |
| 
 | |
| some_list = [1, 2, 3]
 | |
| some_other_list = [1, 2, 3]
 | |
| for elem in some_list:
 | |
|     # errors
 | |
|     some_list.remove(0)
 | |
|     del some_list[2]
 | |
|     some_list.append(elem)
 | |
|     some_list.sort()
 | |
|     some_list.reverse()
 | |
|     some_list.clear()
 | |
|     some_list.extend([1, 2])
 | |
|     some_list.insert(1, 1)
 | |
|     some_list.pop(1)
 | |
|     some_list.pop()
 | |
| 
 | |
|     # conditional break should error
 | |
|     if elem == 2:
 | |
|         some_list.remove(0)
 | |
|         if elem == 3:
 | |
|             break
 | |
| 
 | |
|     # non-errors
 | |
|     some_other_list.remove(elem)
 | |
|     del some_list
 | |
|     del some_other_list
 | |
|     found_idx = some_list.index(elem)
 | |
|     some_list = 3
 | |
| 
 | |
|     # unconditional break should not error
 | |
|     if elem == 2:
 | |
|         some_list.remove(elem)
 | |
|         break
 | |
| 
 | |
| 
 | |
| # dicts
 | |
| mydicts = {"a": {"foo": 1, "bar": 2}}
 | |
| 
 | |
| for elem in mydicts:
 | |
|     # errors
 | |
|     mydicts.popitem()
 | |
|     mydicts.setdefault("foo", 1)
 | |
|     mydicts.update({"foo": "bar"})
 | |
| 
 | |
|     # no errors
 | |
|     elem.popitem()
 | |
|     elem.setdefault("foo", 1)
 | |
|     elem.update({"foo": "bar"})
 | |
| 
 | |
| # sets
 | |
| 
 | |
| myset = {1, 2, 3}
 | |
| 
 | |
| for _ in myset:
 | |
|     # errors
 | |
|     myset.update({4, 5})
 | |
|     myset.intersection_update({4, 5})
 | |
|     myset.difference_update({4, 5})
 | |
|     myset.symmetric_difference_update({4, 5})
 | |
|     myset.add(4)
 | |
|     myset.discard(3)
 | |
| 
 | |
|     # no errors
 | |
|     del myset
 | |
| 
 | |
| 
 | |
| # members
 | |
| class A:
 | |
|     some_list: list
 | |
| 
 | |
|     def __init__(self, ls):
 | |
|         self.some_list = list(ls)
 | |
| 
 | |
| 
 | |
| a = A((1, 2, 3))
 | |
| # ensure member accesses are handled as errors
 | |
| for elem in a.some_list:
 | |
|     a.some_list.remove(0)
 | |
|     del a.some_list[2]
 | |
| 
 | |
| 
 | |
| # Augassign should error
 | |
| 
 | |
| foo = [1, 2, 3]
 | |
| bar = [4, 5, 6]
 | |
| for _ in foo:
 | |
|     foo *= 2
 | |
|     foo += bar
 | |
|     foo[1] = 9
 | |
|     foo[1:2] = bar
 | |
|     foo[1:2:3] = bar
 | |
| 
 | |
| foo = {1, 2, 3}
 | |
| bar = {4, 5, 6}
 | |
| for _ in foo:  # should error
 | |
|     foo |= bar
 | |
|     foo &= bar
 | |
|     foo -= bar
 | |
|     foo ^= bar
 | |
| 
 | |
| 
 | |
| # more tests for unconditional breaks - should not error
 | |
| for _ in foo:
 | |
|     foo.remove(1)
 | |
|     for _ in bar:
 | |
|         bar.remove(1)
 | |
|         break
 | |
|     break
 | |
| 
 | |
| # should not error
 | |
| for _ in foo:
 | |
|     foo.remove(1)
 | |
|     for _ in bar:
 | |
|         ...
 | |
|     break
 | |
| 
 | |
| # should error (?)
 | |
| for _ in foo:
 | |
|     foo.remove(1)
 | |
|     if bar:
 | |
|         bar.remove(1)
 | |
|         break
 | |
|     break
 | |
| 
 | |
| # should error
 | |
| for _ in foo:
 | |
|     if bar:
 | |
|         pass
 | |
|     else:
 | |
|         foo.remove(1)
 | |
| 
 | |
| # should error
 | |
| for elem in some_list:
 | |
|     if some_list.pop() == 2:
 | |
|         pass
 | |
| 
 | |
| # should not error
 | |
| for elem in some_list:
 | |
|     if some_list.pop() == 2:
 | |
|         break
 | |
| 
 | |
| # should error
 | |
| for elem in some_list:
 | |
|     if some_list.pop() == 2:
 | |
|         pass
 | |
|     else:
 | |
|         break
 | |
| 
 | |
| # should error
 | |
| for elem in some_list:
 | |
|     del some_list[elem]
 | |
|     some_list.remove(elem)
 | |
|     some_list.discard(elem)
 | |
| 
 | |
| # should not error
 | |
| for elem in some_list:
 | |
|     some_list[elem] = 1
 | |
| 
 | |
| # should error
 | |
| for i, elem in enumerate(some_list):
 | |
|     some_list.pop(0)
 | |
| 
 | |
| # should not error (list)
 | |
| for i, elem in enumerate(some_list):
 | |
|     some_list[i] = 1
 | |
| 
 | |
| # should not error (dict)
 | |
| for i, elem in enumerate(some_list):
 | |
|     some_list[elem] = 1
 | |
| 
 | |
| # should not error
 | |
| def func():
 | |
|     for elem in some_list:
 | |
|         if some_list.pop() == 2:
 | |
|             return
 | |
| 
 | |
| # should not error - direct return with mutation (Issue #18399)
 | |
| def fail_map(mapping):
 | |
|     for key in mapping:
 | |
|         return mapping.pop(key)
 | |
| 
 | |
| def success_map(mapping):
 | |
|     for key in mapping:
 | |
|         ret = mapping.pop(key)  # should not error
 | |
|         return ret
 | |
| 
 | |
| def fail_list(seq):
 | |
|     for val in seq:
 | |
|         return seq.pop(4)
 |