Rework custom validators to work with generic rules#190
Merged
Conversation
GRef constructor6a06249 to
08e1745
Compare
b8b555e to
cb06c59
Compare
neilmayhew
approved these changes
May 6, 2026
Contributor
neilmayhew
left a comment
There was a problem hiding this comment.
Cool stuff!
The suggestions are all very minor, mostly stylistic things.
Contributor
|
I think the percentages in your final commit message are a little off. I think they should be 33% / 67%. (Did you use AI, by any chance? I've noticed that AI often isn't great at maths.) |
Contributor
Author
|
@neilmayhew I assume it picked these numbers from the specific sample it got from running the tests, still a bit odd to mention those numbers in the commit message instead of the actual probabilities. Seems like Claude is a hardcore empiricist 😅 |
ce56ca3 to
db663c4
Compare
Custom generators and validators attached to a generic Huddle rule can now look up the type bound to a generic parameter at the enclosing rule. Add `generateFromGRef` and `validateFromGRef` helpers, plus `validateFromName`. Custom closures attached to generic rules are wrapped at monomorphization time to install the active local bindings into `geLocal`/`veLocal` on `GenEnv`/`ValidateEnv`, which a new `lookupGRef` method on `MonadCddl` consults. `runCBORValidator` now takes a `CTreeRoot ValidatorPhase` directly. `GRef` moves to `Codec.CBOR.Cuddle.CDDL` so lower-level modules can use it; `Huddle` re-exports it without the constructor.
New Huddle example `tagRangeExample`: `foo<a> = #6.1280(a) / #6.1400(a)` used at `[foo<uint>, foo<nint>]`, with a custom generator that picks any tag in `1280..1400` (biased toward the edges via `frequency`) and a custom validator accepting the same range. Both delegate to the bound generic type via `generateFromGRef` / `validateFromGRef`. Wired into the `Generated value validates` round-trip suite, plus a separate property that classifies tag samples to surface edge coverage (33% edge / 67% middle).
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.
Summary
GRefresolves to the type bound at the enclosing rule.generateFromGRefandvalidateFromGRef(plusvalidateFromName) so closures attached to generic rules can delegate to whatever type the generic was instantiated with.GReffromCodec.CBOR.Cuddle.HuddletoCodec.CBOR.Cuddle.CDDLso the lookup helpers can live alongside their monads.Huddlere-exports it without the constructor.runCBORValidatornow takes aCTreeRoot ValidatorPhasedirectly instead of aValidateEnv.MonadCddltypeclass which has methods for looking up rules from the root CDDL.GenEnvandValidateEnvgaingeLocal/veLocalfields holding the active generic bindings; populated bysynthMonowrapping the captured custom generator/validator at monomorphization time.How it works
Monomorphization (
Resolve.synthMono) builds alocalBinds :: Map Name (CTree _)mapping each generic parameter (e.g.a0) to its bound CTree. Previously the body got substituted with these bindings but the opaque custom generator/validator closures didn't, so callinggenerateFromName "a0"from inside a custom closure failed withUnbound reference: a0.resolveGenericRefnow wraps the capturedg/vclosures withwithLocalGenBindings/withLocalValidateBindingsso the closure pushes its binding map ontogeLocal/veLocalfor the duration of its run. The newlookupGRefconsults that map;generateFromGRefandvalidateFromGRefbuild on top.