Files
Dario Gabriel Lipicar 3043810ea2 fix(generator): no silent drops — unread STRUCTURE in a record is a build error
#127 made an unknown C++ TYPE a build error instead of a silent `any`. This is
the same hole one level down, in the record scanner: a line inside a `struct`
body that the scanner could not read as a field was `continue`d, and the struct
was published anyway — MINUS that field.

A field list is not a detail of a record, it IS the record: the promise every
other language binds to. A contract with two fields is exactly as well-formed as
one with three, so nothing downstream could tell. Measured on the generator built
from master:

  struct SplitRecord {           ->  type SplitRecord {     exit 0, no diagnostic
      std::string id;                  id: tstr
      std::vector<std::string>         n: uint
          tags;                      }
      uint64_t n;
  };                                   `tags` is simply GONE

  struct Outer {                 ->  type Outer { a: int }  exit 0
      struct Inner {                   the record is made of the INNER type's
          int64_t a;                   field and has none of its own
      };
      Inner inner;
      std::string label;
  };

  struct Pair {                  ->  method echoPair(v: Pair) with NO `type Pair`
      std::string first; std::string second;    emitted — a contract naming a
  };                                            type it never declares

  struct Defaulted {             ->  the brace-initialised field is dropped
      std::string id{"none"};        (only the `= v` form was recognised)
      int64_t n = 0;
  };

  struct Allman                  ->  not a record AT ALL, so every mention of it
  {                                  falls to the `any` fallback — since #127 an
      std::string id;                error whose hint says "declare a struct",
  };                                 which is the thing the author declared

The scanner now reads a body as DECLARATIONS rather than lines. Physical lines
are joined until the declaration is whole — a `;` at the struct's own brace
depth, or a `}` there, which is how a member function defined inline ends —
exactly as the caller already joins a method signature until its parentheses
balance. Where a brace sits, and where a line wraps, cannot decide what a header
means. Allman and base-clause openings are recognised for the same reason.

Whatever is left after that, and after the constructs that definitively are NOT
fields (member functions, `using`/`typedef`/`friend`/`static`, access
specifiers), is reported instead of skipped, naming the struct, the declaration
and the fix. Same withdrawal discipline as #127: the diagnostic is keyed on the
struct and tested against the API-REFERENCED set, so a helper struct the module
never publishes may be as unreadable as it likes — every real module carries one
(openmetrics' `ModuleSource`, the package manager's `PendingAction`).

Comments come off with a literal-aware strip. The old bare `indexOf("//")`
truncated `std::string url = "http://x";` inside the literal, which merely lost
the field before and would now reject valid code.

Blast radius, measured: all 27 universal-interface impl headers in the workspace
emit BYTE-IDENTICAL .lidl and stderr, with identical exit codes. All 6 structs
those headers declare are K&R with no unparsed body line, so nothing in tree
changes. logos-qt-generator, which compiles this same file, builds; and
test_fullapi_ext_cpp — the records-heavy module — builds end to end.

14 new tests. 10 of them fail on the unpatched parser; the rest are the "must
still work" controls, including one for an inline member-function body that an
earlier cut of this change would have broken.
2026-07-31 18:39:18 -03:00
..