from __future__ import annotations import ast from dataclasses import dataclass from pathlib import Path from typing import Dict, List, Optional, Set @dataclass class HilInitIssue: file: str key: str message: str def _get_str_const(node: ast.AST) -> Optional[str]: return node.value if isinstance(node, ast.Constant) and isinstance(node.value, str) else None class _PhysicalValuesInitCollector(ast.NodeVisitor): """ Colleziona le chiavi inizializzate in vari modi: - physical_values["x"] = ... - physical_values["x"] += ... - physical_values.setdefault("x", ...) - physical_values.update({"x": ..., "y": ...}) """ def __init__(self) -> None: self.inits: Set[str] = set() def visit_Assign(self, node: ast.Assign) -> None: for tgt in node.targets: k = self._key_from_physical_values_subscript(tgt) if k: self.inits.add(k) self.generic_visit(node) def visit_AugAssign(self, node: ast.AugAssign) -> None: k = self._key_from_physical_values_subscript(node.target) if k: self.inits.add(k) self.generic_visit(node) def visit_Call(self, node: ast.Call) -> None: # physical_values.setdefault("x", ...) if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name): if node.func.value.id == "physical_values": if node.func.attr == "setdefault" and node.args: k = _get_str_const(node.args[0]) if k: self.inits.add(k) # physical_values.update({...}) if node.func.attr == "update" and node.args: arg0 = node.args[0] if isinstance(arg0, ast.Dict): for key_node in arg0.keys: k = _get_str_const(key_node) if k: self.inits.add(k) self.generic_visit(node) @staticmethod def _key_from_physical_values_subscript(node: ast.AST) -> Optional[str]: # physical_values["x"] -> Subscript(Name("physical_values"), Constant("x")) if not isinstance(node, ast.Subscript): return None if not (isinstance(node.value, ast.Name) and node.value.id == "physical_values"): return None return _get_str_const(node.slice) def validate_hil_initialization(hil_logic_file: str, required_keys: Set[str]) -> List[HilInitIssue]: """ Verifica che nel file HIL ci sia almeno un'inizializzazione per ciascuna key richiesta. Best-effort: guarda tutte le assegnazioni nel file (non solo dentro logic()). """ path = Path(hil_logic_file) text = path.read_text(encoding="utf-8", errors="replace") tree = ast.parse(text) collector = _PhysicalValuesInitCollector() collector.visit(tree) missing = sorted(required_keys - collector.inits) return [ HilInitIssue( file=str(path), key=k, message=f"physical_values['{k}'] non sembra inizializzato nel file HIL (manca un assegnamento/setdefault/update).", ) for k in missing ]