From b274e2303d5c0e38ee91978ec81a5b3cfa30b788 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 6 Nov 2026 03:12:15 +0100 Subject: [PATCH] fix: annotate the five affinity sort keys --- scripts/common.py | 2 +- tests/test_audit_regressions.py | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/scripts/common.py b/scripts/common.py index a1f057f23..21ef30930 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -1317,7 +1317,7 @@ def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str owners = _owner_tokens(file_entry) sized = any(file_entry.get(k) for k in ("size", "min_size", "max_size")) - def score(path: str) -> tuple[int, bool, bool, int]: + def score(path: str) -> tuple[int, bool, bool, bool, int]: fits = 0 if sized and os.path.exists(path): fits = int(size_fits(file_entry, os.path.getsize(path))) diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index d697062c4..267e4b28c 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -7138,5 +7138,47 @@ class OpenMsxSystemRomsAreFoundBySha1(unittest.TestCase): self.assertEqual(named, []) +class TupleAnnotationsMatchTheirReturns(unittest.TestCase): + """`_by_affinity.score` gained a fifth sort key while its annotation kept + four, so the signature misread the order that sends C-Dogs' + gamecontrollerdb.txt behind a neutral copy.""" + + @staticmethod + def _own_returns(fn: ast.AST): + stack = list(ast.iter_child_nodes(fn)) + while stack: + node = stack.pop() + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)): + continue + if isinstance(node, ast.Return): + yield node + stack.extend(ast.iter_child_nodes(node)) + + def test_a_fixed_tuple_annotation_counts_its_returned_values(self): + sources = sorted((ROOT / "scripts").rglob("*.py")) + [ROOT / "install.py"] + mismatched = [] + for path in sources: + for fn in ast.walk(ast.parse(path.read_text(encoding="utf-8"))): + if not isinstance(fn, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + hint = fn.returns + if not (isinstance(hint, ast.Subscript) and isinstance(hint.value, ast.Name) + and hint.value.id == "tuple"): + continue + declared = hint.slice.elts if isinstance(hint.slice, ast.Tuple) else [hint.slice] + if any(isinstance(e, ast.Constant) and e.value is Ellipsis for e in declared): + continue + for ret in self._own_returns(fn): + value = ret.value + if (isinstance(value, ast.Tuple) + and not any(isinstance(e, ast.Starred) for e in value.elts) + and len(value.elts) != len(declared)): + mismatched.append( + f"{path.relative_to(ROOT)}:{ret.lineno} " + f"{len(value.elts)} returned, {len(declared)} declared" + ) + self.assertEqual(mismatched, []) + + if __name__ == "__main__": unittest.main()