feat(arxjit): resolve a compilable signature from Python annotations - #103
Open
Jaskirat-s7 wants to merge 2 commits into
Open
feat(arxjit): resolve a compilable signature from Python annotations#103Jaskirat-s7 wants to merge 2 commits into
Jaskirat-s7 wants to merge 2 commits into
Conversation
Wiki Issue 2 leaves open how Python annotations interact with an explicit signature. New arxjit.reconcile decides the Signature a validated function will be compiled against: an explicit signature= wins outright and the annotations are not read at all, so a function may annotate freely without having to agree with it and there is deliberately no mismatch error; otherwise the signature is derived from the annotations; a function with neither is reported at WARNING severity and stays interpreted, while a function that annotates some things but not others is asking to be compiled, so every remaining gap is an ERROR. Annotations are read from the ast rather than from __annotations__. A module using "from __future__ import annotations" turns every annotation into a string at runtime, which would otherwise have to be evaluated back; reading the ast makes that irrelevant and lets each diagnostic point at the exact annotation that caused it. Only a bare int, float or bool is accepted, so a subscripted, dotted or quoted annotation is reported against its own location. Note this can never produce i32 or f32, since Python has no annotation that distinguishes widths: the narrow types stay explicit-signature only. Positional-only parameters are included in the derived signature. Validation permits them and they are ordinary positional arguments to a compiled function, so leaving them out would silently drop an argument. Move the diagnostic location helpers out of validation into arxjit.locations, now that a second stage builds diagnostics from ast nodes, so the two cannot drift on the column contract. resolve_signature is exported and tested but not yet consulted by @jit; wiring it into the decorator is the next change. 160 tests, arxjit coverage 100%.
The douki schema accepts a parameter entry as either a string or a mapping of type/description/optional/default/variadic. Three test helpers with deliberately unannotated parameters were left with a bare key, which parses as None and matches neither, so the douki-arxjit hook failed in CI while reporting the files as unchanged. Give those parameters a description; douki normalizes the string form to a description mapping and is then idempotent.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows #102. Closes the remaining open question in wiki Issue 2: "decide how Python annotations interact with explicit signatures."
The rules
New
arxjit.reconciledecides whichSignaturea validated function will be compiled against:signature=Because an explicit signature short-circuits, a function may annotate freely without having to agree with it — there is deliberately no mismatch error, per the decision in the thread. The last row is the judgement call worth flagging: once a function annotates anything, it is read as asking to be compiled, so a gap elsewhere is an error rather than a silent fallback.
Annotations are read from the ast, not
__annotations__A module using
from __future__ import annotationsturns every annotation into a string at runtime, which would otherwise have to be evaluated back witheval_str. Reading the ast makes that irrelevant, and lets each diagnostic point at the exact annotation that caused it rather than at the function.Only a bare
int,floatorboolis accepted; a subscripted (list[int]), dotted (np.float64) or quoted ("int") annotation is reported against its own location. One consequence worth naming: this can never producei32orf32, since Python has no annotation that distinguishes widths, so the narrow types stay explicit-signature only. That partly answers the "should we trim i32/f32?" question from #91 — they remain reachable, just not from annotations.Positional-only parameters are included in the derived signature. Validation permits them and they are ordinary positional arguments to a compiled function, so leaving them out would silently drop an argument.
Fail-closed on argument shapes
Signatureis a fixed list of positional argument types, so a variadic or keyword-only parameter has nowhere to go. Validation rejects those shapes first, which makes this unreachable through@jit— butresolve_signatureis exported, and without a check it would leave the parameters out and hand back a signature that is quietly wrong (i64()for a function taking*args). It now reports that it cannot derive one instead. Four regression tests cover*args,**kwargs, keyword-only, and the mixed positional-plus-variadic case.Location helpers moved
_char_columnand_diagnosticmove out ofvalidation.pyinto a newarxjit.locations, now that a second stage builds diagnostics from ast nodes. Single-sourcing them keeps the two stages from drifting on the column contract (one-based Unicode characters, converted from ast's zero-based UTF-8 byte offsets at the boundary). No behaviour change — a move, plus aseverityparameter so reconciliation can emit WARNING.Note on scope
resolve_signatureis exported and fully tested but not yet consulted by@jit—core.pyis untouched here. Wiring it into the decorator, along with thestrictflag and the warn-and-fall-back behaviour, is the next PR. Splitting it this way keeps the resolution rules reviewable on their own, separately from the decorator behaviour change.Verification
169 tests, arxjit coverage 100%. All gates green locally: pytest+cov, ruff format/check, mypy strict (
cd packages/arxjit && mypy src), bandit-iii -lll, vulture 80, mccabe 10,douki syncidempotent. Verified on CPython 3.10.19, 3.11.11 and 3.14.6.