Files
logos-cpp-sdk/tests/experimental
Dario Gabriel LipicarandClaude Opus 5 16144d4efa feat(parser): an unknown C++ spelling is a build error, not a silent any
cppTypeToLidl ended with `// Fallback: treat as opaque` -> `any`, and `any`
is ADMITTED by every backend gate. So a spelling nobody had written a branch
for was accepted in silence, published as `any`, and dispatched as a bare
`lidlImpl().f(args.at(0))` — no logos::fromJson<>, no check. That is the one
hole #113-#122 closed for every typed slot and left open for anything that
reached `any`.

An 11-method hostile probe was admitted wholesale: uint32_t, size_t, float
and uint8_t all typed as `any`; std::set, std::vector<std::pair<...>> and a
non-string-keyed map as `any`; std::vector<uint32_t> as `[any]`.

Now every spelling with no LIDL type is collected with the declaration that
carries it, and parseImplHeader turns the list into a parse error naming the
offending type and the fix:

  method 'send_generic_public_transaction': parameter 'instruction' declared
  `const std::vector<uint32_t>&`, whose element `uint32_t` has no LIDL type.
    LIDL numbers are 64-bit only. Declare it `uint64_t` (LIDL `uint`).
    Widening is source-compatible for every caller; a narrow type on the
    wire is not, which is why LIDL has none.

Numbers get that tailored hint (uint8_t its own — it means bytes here, and
only as std::vector<uint8_t>); sets, pairs/tuples, non-string map keys,
list/deque/array, Qt types and pointers each get theirs; anything else gets
the full table of recognised spellings. A hint that does not name a
replacement just moves the guesswork, so all of them do.

std::unordered_map<std::string, T> joins std::map as a spelling of
`{tstr: T}` — the codec has always handled both, and the previous commit made
the generated dispatch bind whichever the author declared. Two slots are the
exception and say so: a record FIELD and an event PARAMETER, where the
generator writes the spelling out into code the author's own declaration has
to match and can only pick one name.

Three properties keep this from breaking things it should not:

  * The MAPPING is unchanged. cppTypeToLidl still returns `any` for an
    unsupported spelling; only a diagnostic is recorded. A diagnostic that is
    later withdrawn therefore leaves output byte-identical.
  * Diagnostics are withdrawn for declarations that never reach the contract
    — a helper struct dropped by keepOnlyReferencedRecords, a reserved
    lifecycle hook (onContextReady and friends), a struct with no parsed
    fields. Publishing is what makes a type a promise.
  * An empty spelling is not a C++ type, it is this line-based parser failing
    to find one. It keeps the old behaviour rather than reporting `''`.

Also fixes a latent ordering bug found while threading the context through:
metadata.json's event parameters were typed BEFORE the header was read, i.e.
against whatever g_recordNames the previous module's parse had left behind.
They are now read there and typed after scanForRecords, in the same position
in module.events as before.

Verified by generating over every impl header and every .lidl in the
workspace: 31 derived contracts byte-identical, 368 consumer-umbrella files
byte-identical under both --api-style qt and --api-style lp, 46 generated
types headers byte-identical. Exactly two modules now fail, at exactly the
four slots a prior scan identified as silent admissions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:16:30 -03:00
..