| 240 | |
| 241 | |
| 242 | class OpChecker(OpVisitor[None]): |
| 243 | def __init__(self, parent_fn: FuncIR) -> None: |
| 244 | self.parent_fn = parent_fn |
| 245 | self.errors: list[FnError] = [] |
| 246 | |
| 247 | def fail(self, source: Op, desc: str) -> None: |
| 248 | self.errors.append(FnError(source=source, desc=desc)) |
| 249 | |
| 250 | def check_control_op_targets(self, op: ControlOp) -> None: |
| 251 | for target in op.targets(): |
| 252 | if target not in self.parent_fn.blocks: |
| 253 | self.fail(source=op, desc=f"Invalid control operation target: {target.label}") |
| 254 | |
| 255 | def check_type_coercion(self, op: Op, src: RType, dest: RType) -> None: |
| 256 | if not can_coerce_to(src, dest): |
| 257 | self.fail( |
| 258 | source=op, desc=f"Cannot coerce source type {src.name} to dest type {dest.name}" |
| 259 | ) |
| 260 | |
| 261 | def check_compatibility(self, op: Op, t: RType, s: RType) -> None: |
| 262 | if not can_coerce_to(t, s) or not can_coerce_to(s, t): |
| 263 | self.fail(source=op, desc=f"{t.name} and {s.name} are not compatible") |
| 264 | |
| 265 | def expect_float(self, op: Op, v: Value) -> None: |
| 266 | if not is_float_rprimitive(v.type): |
| 267 | self.fail(op, f"Float expected (actual type is {v.type})") |
| 268 | |
| 269 | def expect_non_float(self, op: Op, v: Value) -> None: |
| 270 | if is_float_rprimitive(v.type): |
| 271 | self.fail(op, "Float not expected") |
| 272 | |
| 273 | def expect_primitive_type(self, op: Op, v: Value) -> None: |
| 274 | if not isinstance(v.type, RPrimitive): |
| 275 | self.fail(op, f"RPrimitive expected, got {type(v.type).__name__}") |
| 276 | |
| 277 | def visit_goto(self, op: Goto) -> None: |
| 278 | self.check_control_op_targets(op) |
| 279 | |
| 280 | def visit_branch(self, op: Branch) -> None: |
| 281 | self.check_control_op_targets(op) |
| 282 | |
| 283 | def visit_return(self, op: Return) -> None: |
| 284 | self.check_type_coercion(op, op.value.type, self.parent_fn.decl.sig.ret_type) |
| 285 | |
| 286 | def visit_unreachable(self, op: Unreachable) -> None: |
| 287 | # Unreachables are checked at a higher level since validation |
| 288 | # requires access to the entire basic block. |
| 289 | pass |
| 290 | |
| 291 | def visit_assign(self, op: Assign) -> None: |
| 292 | self.check_type_coercion(op, op.src.type, op.dest.type) |
| 293 | |
| 294 | def visit_assign_multi(self, op: AssignMulti) -> None: |
| 295 | for src in op.src: |
| 296 | assert isinstance(op.dest.type, RArray) |
| 297 | self.check_type_coercion(op, src.type, op.dest.type.item_type) |
| 298 | |
| 299 | def visit_load_error_value(self, op: LoadErrorValue) -> None: |
no outgoing calls
no test coverage detected
searching dependent graphs…