def crop(self, frame, box): # error: Unsupported operand types for + ("int" and "None")
Swept all 6 src modules, 8 test files, and 2 helper scripts to mypy . → 0 errors. Fixes per pattern:
1. Misplaced type: ignore on closing paren (sprites.py)
Mypy reports the error on the line where the operation happens (box.x + box.w), not on the closing paren — so the ignore there both fails to suppress and trips warn_unused_ignores:
# BEFORE — ignore on closing paren: no effect
def _crop(self, frame, box): # error: Unsupported operand types for + ("int" and "None")
return frame.crop(
(box.x, box.y, box.x + box.w, box.y + box.h)
) # type: ignore[arg-type] # error: Unused "type: ignore" comment
Best fix is to fix the underlying type (make w a required int and expose a typed rectangle) so no ignore is needed; the relocation alternative (verified working) is to put the ignore on the operator line, keeping the ignore as the entire comment — mypy 2.x rejects # type: ignore[code] trailing text:
@dataclass
class SpriteBox:
x: int; y: int; w: int; h: int
def crop_box(self) -> tuple[int, int, int, int]:
return (self.x, self.y, self.x + self.w, self.y + self.h)
def _crop(self, frame: Image.Image, box: SpriteBox) -> Image.Image:
return frame.crop(box.crop_box()) # clean — no ignore
2. Called-but-undefined methods (sprites.py) — recognize() calls self._extract_enemy_sprite_region(...) / _extract_player_sprite_region(...) which did not exist (attr-defined + latent AttributeError). Implemented them (they crop ENEMY_BOX from the playfield corner and PLAYER_BOX from the HUD strip):
def _extract_enemy_sprite_region(self, frame: Image.Image) -> Image.Image:
"""Crop the enemy sprite out of the playfield corner of the frame."""
return self._crop(frame, self.ENEMY_BOX)
def _extract_player_sprite_region(self, frame: Image.Image) -> Image.Image:
"""Crop the player sprite out of the HUD strip of the frame."""
return self._crop(frame, self.PLAYER_BOX)
3. Test mocks / None into typed params (tests/)
Fixtures typed -> object can't flow into _MapDB parameters; fetch_map() returns Optional[dict] and was passed to attach(entry: dict) unguarded:
# conftest.py — typed fixtures (object -> _MapDB / dict)
@pytest.fixture
def mapdb() -> _MapDB:
return _MapDB(":memory:")
@pytest.fixture
def entry() -> dict[str, Any]:
return {"name": "Pallet", "origin_x": 0, "origin_y": 0, "width": 16, "height": 16}
# test_mapdb.py — narrow Optional before passing to a typed param
def test_roundtrip(mapdb: _MapDB) -> None:
rowid = mapdb.insert_map("Viridian", 0, 0, 16, 16)
assert rowid is not None
entry = mapdb.fetch_map(rowid)
assert entry is not None # <-- the required guard
mapdb.attach(entry)
assert entry["name"] == "Viridian"
Also annotated tmp_path: Path in the tests that used it (no-untyped-def).
4. Image.NEAREST → Image.Resampling.NEAREST (sprites.py, scan_assets.py)
Pillow 12 stubs no longer expose the module-level constant (Module has no attribute "NEAREST" [attr-defined]):
tileset = tileset.resize((tileset.width * 2, tileset.height * 2), Image.Resampling.NEAREST)
5. json.loads returns Any (config.py, scan_assets.py) — no-any-return under warn_return_any:
return cast(dict[str, Any], json.loads(fh.read()))
6. session_id = None infers None (session.py) — later assignment of lastrowid (Optional[int]) became an incompatible-assignment error:
self.session_id: Optional[int] = None # matches _MapDB.insert_map -> Optional[int]
Reproduced the sweep on a faithful 18-file project (6 src modules + 8 test files + 2 scripts) under the guard config (`mypy .`, `warn_return_any`, `warn_unused_ignores`, `disallow_untyped_defs`, `no_implicit_optional`), mypy 2.3.0 + Pillow 12.3.0: - **Before:** 16 errors in 9 files — every one of the 6 patterns present (NEAREST ×3, no-any-return ×2, object-typed fixtures ×3, missing methods ×2, operator-on-`None` + unused-ignore ×2, `None`-typed `session_id`, untyped test params ×2). - **After:** `mypy .` → `Success: no issues found in 18 source files`, exit code 0. No leftover ignores (would trip `warn_unused_ignores`). - **Runtime:** `pytest` → 9 passed, 9 collected. This proves the new `_extract_*_sprite_region` methods actually work (`recognize()` returns `"tie"` for identical black crops; `load_tileset` caches a 2× upscaled image; DB round-trip; `session.session_id == lastrowid`; config load). - **Scripts execute:** `scan_assets.py` upscaled 8×8 tiles to 16×16 and emitted a JSON index; `export_db.py` inserted and dumped the map row (`id: 1`). - **Edge cases tested:** (a) pattern-1 relocation verified in isolation — ignore on the operator line suppresses, ignore on the closing paren does not (error remains + `unused-ignore`), and mypy 2.x rejects `# type: ignore[code]` with trailing prose; (b) `fetch_map(9999) is None` path still covered; (c) `session_id is None` before `create()`, equals `lastrowid` after; (d) both src and script call sites of the resample filter fixed.
{"model": "deepseek-v4-flash", "problem_class": "python-mypy-static-analysis-sweep", "result": "passed", "tests": 9}