From 346016ba8fe44d6cb10f9a676ea3ed9e158c0415 Mon Sep 17 00:00:00 2001 From: janik Date: Thu, 23 Jul 2026 13:19:21 +0700 Subject: [PATCH] Fix the macOS crash at import: defer config.py annotations First real run on a Mac died before the dialog could open: config.py line 15: CELL_UM_OVERRIDE: float | None = None TypeError: unsupported operand type(s) for |: 'type' and 'NoneType' KiCad macOS bundles Python 3.9, where PEP 604 unions in annotations are evaluated at import time unless deferred - config.py was the one annotated module without the __future__ import (errors.py has no annotations, __init__.py is empty). A new tripwire test walks every shipped module with ast and fails if a file uses annotations without deferring them, so the next module added (progress.py was born only last week) cannot regress this silently while the Windows suite stays green. Verified for real this time, not audited: the full suite passes under CPython 3.9.25 with the exact stack a Mac venv resolves (numpy 2.0.2, scipy 1.13.1, matplotlib 3.9.4, PySide6 6.10.0, pyamg 5.2.1) - 137 passed. Co-Authored-By: Claude Fable 5 --- fill_resistance/config.py | 3 +++ tests/test_py39_compat.py | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/test_py39_compat.py diff --git a/fill_resistance/config.py b/fill_resistance/config.py index 43c8b07..8976f1b 100644 --- a/fill_resistance/config.py +++ b/fill_resistance/config.py @@ -2,6 +2,9 @@ A future version may read overrides from /fill_res_config.json. """ +from __future__ import annotations # KiCad's macOS Python is 3.9: without + # this, `float | None` annotations are + # evaluated at import and crash there # --- Grid sizing --- # Benchmarked on the VOUT+ plane (147x59 mm): R changes < 0.3% from diff --git a/tests/test_py39_compat.py b/tests/test_py39_compat.py new file mode 100644 index 0000000..eb9cd47 --- /dev/null +++ b/tests/test_py39_compat.py @@ -0,0 +1,51 @@ +"""Python 3.9 compatibility tripwire. + +KiCad's macOS builds bundle Python 3.9 and build the plugin venv with +it (README: Platform notes), while the dev environment runs a current +Python - so nothing else in the suite notices a construct that only +breaks on 3.9. The first real Mac run died at import: a module-level +`float | None` annotation in config.py, evaluated at runtime because +the file lacked the future import (PEP 604 unions need Python 3.10 +unless annotations are deferred). +""" +import ast +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SHIPPED = sorted((ROOT / "fill_resistance").glob("*.py")) +SHIPPED.append(ROOT / "fill_res_action.py") + + +def _has_future_annotations(tree: ast.Module) -> bool: + return any(isinstance(node, ast.ImportFrom) + and node.module == "__future__" + and any(alias.name == "annotations" for alias in node.names) + for node in tree.body) + + +def _uses_annotations(tree: ast.Module) -> bool: + for node in ast.walk(tree): + if isinstance(node, ast.AnnAssign): + return True + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + if node.returns is not None: + return True + a = node.args + args = (a.posonlyargs + a.args + a.kwonlyargs + + ([a.vararg] if a.vararg else []) + + ([a.kwarg] if a.kwarg else [])) + if any(arg.annotation is not None for arg in args): + return True + return False + + +def test_annotated_modules_defer_annotations(): + offenders = [] + for path in SHIPPED: + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + if _uses_annotations(tree) and not _has_future_annotations(tree): + offenders.append(path.name) + assert not offenders, ( + f"{offenders} use annotations without 'from __future__ import " + f"annotations': they are evaluated at import time and PEP 604 " + f"unions crash on KiCad's macOS Python 3.9.")