Skip to content

bloqade.gemini.common.validation.call_site

call_site

bloqade.gemini.common.validation.call_site

Attribute validation errors back to the call that dragged them in.

Inlining is lossy in exactly the way that matters for diagnostics. InlinePass splices a callee’s body into the caller and the invoke disappears, but the spliced statements keep the callee’s source info. If one of them then fails validation, the error names a file the user never opened::

Gemini Logical Validation:

  • Non-constant iterable in for loop is not supported … File ”…/gemini/logical/stdlib/post_processing.py”, line 12, col 65 │ return logical.default_post_processing(qbs)

and the excerpt is a chimera. ValidationError.attach overwrites source.lineno_begin with the entry method’s and resolves the source lines against the entry’s py_func, so the reader gets the callee’s filename against the caller’s line numbers and the caller’s code — line 12 above is in the caller, not in post_processing.py. When the callee’s line number falls past the end of the caller, which is the usual case, the excerpt comes out empty and the diagnostic is a bare filename. That is bloqade-internal#449.

The lowering is not wrong to reject the program: default_post_processing loops over range(1, len(register)), which only aggressive_unroll=True can flatten. What is missing is which call introduced the offending code and what to do about it. So InlineOrigins records the call sites before inlining destroys them, and afterwards folds them back into the messages and re-points the excerpt at the kernel the statement actually came from.

A statement is identified with the kernel it was written in by (source.file, source.lineno_begin) — filename plus that kernel’s offset into it. File alone is not enough: a helper defined a few lines above the kernel that calls it is the common case, and it shares the caller’s filename while needing exactly the same attribution.

Which call to blame comes from the static call graph: every method reachable from the entry inherits the call site of the first hop that reaches it, breadth-first so the shallowest — the one written in the user’s own kernel — wins. That is coarse enough to survive nested inlining and precise enough to name the one call the user can change.