mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
test(doctests): the generator round-trip pins the lossless Qt spellings
`cpp-sdk-generator-roundtrip.test.yaml` is a CI gate
(.github/workflows/doctests.yml), and two of its `expect_contains` were
pinned to the type names the Qt consumer produced BEFORE the lossless
mapping:
QStringList labels(const QVariantList& ids
QVariant nearest(const Point& p, QVariant limit
The generator now emits `QList<qulonglong>` and
`std::optional<Point>` / `std::optional<qulonglong>` for those slots, so both
assertions failed. The `nearest` step's `run` grep was pinned the same way
(`QVariant nearest`), so the line it was supposed to assert on was not even
in the output being searched.
Verified by running the spec's own steps against the generator built from
this commit: 10 run-steps, 0 failures. The `[uint]` -> QList<qulonglong> and
`?T` -> std::optional<T> lines were read out of the real
`consumer/sensor_module_api.h` and `geometry/geometry_module_api.h`, not
written from the mapping table.
Prose too, in three places that described the old table: the Flow-3 type
mapping ("other arrays -> QVariantList"), the composite-types intro
("optionals ... stay QVariantMap / QVariant"), and the composite-signature
step. They now say what the mapping actually is — one LIDL type, one C++
spelling, with `any` the single deliberate exception — and `nearest` is
called out as the one signature carrying both halves of the optional
mapping.
`doctests/outputs/cpp-sdk-generator-roundtrip.md` carries the same prose
corrections. That tree is hand-pinned and CI never diffs it, which is
exactly why it must be corrected by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
db0263ae5e
commit
b72d9bb6b1
@@ -274,10 +274,14 @@ sections:
|
||||
variant that hands it a `logos::AsyncResult<T>` (`{value, error}`) so a
|
||||
failed call is distinguishable from a legitimately default-valued one —
|
||||
and each `event` an `on(...)` subscription. The type mapping is the Qt
|
||||
caller style:
|
||||
caller style, and it is **lossless** — one LIDL type, one C++ spelling:
|
||||
`float64`→`double`, `tstr`→`QString`, `int`→`qlonglong`, `uint`→
|
||||
`qulonglong`, `bstr`→`QByteArray`, `[tstr]`→`QStringList`, other
|
||||
arrays→`QVariantList`, and `result`→`LogosResult`.
|
||||
`qulonglong`, `bstr`→`QByteArray`, `result`→`LogosResult`, and each
|
||||
container carries its element type through — `[tstr]`→`QStringList`,
|
||||
every other `[T]`→`QList<T>` (so `[uint]` is `QList<qulonglong>`).
|
||||
Only `any` stays `QVariant`: it is the one Qt type that holds bytes *and*
|
||||
an exact `uint64` *and* arbitrary nesting, so narrowing it would lose what
|
||||
it was chosen to carry.
|
||||
|
||||
The integer spellings are 64-bit, and unsigned stays unsigned: LIDL
|
||||
`int`/`uint` are `int64_t`/`uint64_t` in every other binding, so spelling
|
||||
@@ -302,7 +306,7 @@ sections:
|
||||
- "double temperature("
|
||||
- "qlonglong record(qulonglong id, double value, const QString& note, bool valid"
|
||||
- "QByteArray firmware(const QByteArray& image"
|
||||
- "QStringList labels(const QVariantList& ids"
|
||||
- "QStringList labels(const QList<qulonglong>& ids"
|
||||
- "LogosResult reset(const QString& id"
|
||||
- title: "The three call surfaces per method"
|
||||
text: |
|
||||
@@ -334,10 +338,14 @@ sections:
|
||||
generates `struct Point` plus the conversions, so a caller writes
|
||||
`Point p = client.translate(q, 1, 2)` instead of digging fields out of a
|
||||
`QVariantMap`. `[Point]` is a `QList<Point>` and `{tstr: Point}` a
|
||||
`QMap<QString, Point>`. Maps of `any`, optionals (`?T`) and `any` itself
|
||||
still cross as untyped JSON and stay `QVariantMap` / `QVariant` — a record
|
||||
has a declared shape, those do not. Here is a contract that uses all of
|
||||
them, taken straight to a consumer header.
|
||||
`QMap<QString, Point>`. An **optional** `?T` is a
|
||||
`std::optional<T>` — two-state in the type system, so an absent value is
|
||||
not spelled the same way as a present default, and `?uint` is
|
||||
distinguishable from `?tstr`. Only `any` — and any container whose element
|
||||
type bottoms out at `any`, such as `{tstr: any}` — stays untyped, as
|
||||
`QVariant` / `QVariantMap`: a record has a declared shape and an `any` does
|
||||
not. Here is a contract that uses all of them, taken straight to a consumer
|
||||
header.
|
||||
steps:
|
||||
- title: "geometry_module.lidl"
|
||||
file:
|
||||
@@ -374,10 +382,15 @@ sections:
|
||||
text: |
|
||||
`Point` is generated as a struct, so a record parameter is taken by
|
||||
const-ref and a record return comes back typed. An array-of-records is a
|
||||
`QList<Point>`. A map of `any`, an optional and a bare `any` stay
|
||||
`QVariantMap` / `QVariant` — the untyped carriers for JSON whose shape
|
||||
the contract does not declare.
|
||||
run: "grep -E 'class GeometryModule|struct Point|Point translate|QList<Point>|QVariantMap attributes|QVariant nearest' geometry/geometry_module_api.h"
|
||||
`QList<Point>`. `nearest` shows both halves of the optional mapping in
|
||||
one signature — `?uint` in, `?Point` out — as
|
||||
`std::optional<qulonglong>` and `std::optional<Point>`: the caller can
|
||||
ask `limit.has_value()` and the return can be *nothing* without
|
||||
colliding with a legitimate `Point{0, 0}`. Only `attributes` and
|
||||
`describe` stay `QVariantMap` / `QVariant`: `{tstr: any}` and `any` are
|
||||
the untyped carriers for JSON whose shape the contract does not
|
||||
declare.
|
||||
run: "grep -E 'class GeometryModule|struct Point|Point translate|QList<Point>|QVariantMap attributes|std::optional<Point> nearest' geometry/geometry_module_api.h"
|
||||
code_block: |
|
||||
grep -E 'class|translate|bounds|attributes|nearest' geometry/geometry_module_api.h
|
||||
expect_contains:
|
||||
@@ -386,4 +399,4 @@ sections:
|
||||
- "Point translate(const Point& p, double dx, double dy"
|
||||
- "Point bounds(const QList<Point>& points"
|
||||
- "QVariantMap attributes(const QVariantMap& tags"
|
||||
- "QVariant nearest(const Point& p, QVariant limit"
|
||||
- "std::optional<Point> nearest(const Point& p, const std::optional<qulonglong>& limit"
|
||||
|
||||
@@ -258,10 +258,15 @@ cat provider/sensor_module_events_cdylib.cpp
|
||||
The consumer side. From the same contract, `--module-only` emits the typed
|
||||
wrapper a *consumer* compiles against to call `sensor_module`. Each LIDL
|
||||
`method` becomes a synchronous caller plus an `…Async` variant, and each
|
||||
`event` an `on(...)` subscription. The type mapping is the Qt caller style:
|
||||
`event` an `on(...)` subscription. The type mapping is the Qt caller style,
|
||||
and it is **lossless** — one LIDL type, one C++ spelling:
|
||||
`float64`→`double`, `tstr`→`QString`, `int`→`qlonglong`, `uint`→
|
||||
`qulonglong`, `bstr`→`QByteArray`, `[tstr]`→`QStringList`, other
|
||||
arrays→`QVariantList`, and `result`→`LogosResult`.
|
||||
`qulonglong`, `bstr`→`QByteArray`, `result`→`LogosResult`, and each
|
||||
container carries its element type through — `[tstr]`→`QStringList`,
|
||||
every other `[T]`→`QList<T>` (so `[uint]` is `QList<qulonglong>`).
|
||||
Only `any` stays `QVariant`: it is the one Qt type that holds bytes *and*
|
||||
an exact `uint64` *and* arbitrary nesting, so narrowing it would lose what
|
||||
it was chosen to carry.
|
||||
|
||||
The integer spellings are 64-bit, and unsigned stays unsigned: LIDL
|
||||
`int`/`uint` are `int64_t`/`uint64_t` in every other binding, so spelling
|
||||
@@ -293,10 +298,14 @@ A **record becomes a real C++ struct**: a `type Point { … }` in the contract
|
||||
generates `struct Point` plus the conversions, so a caller writes
|
||||
`Point p = client.translate(q, 1, 2)` instead of digging fields out of a
|
||||
`QVariantMap`. `[Point]` is a `QList<Point>` and `{tstr: Point}` a
|
||||
`QMap<QString, Point>`. Maps of `any`, optionals (`?T`) and `any` itself
|
||||
still cross as untyped JSON and stay `QVariantMap` / `QVariant` — a record
|
||||
has a declared shape, those do not. Here is a contract that uses all of
|
||||
them, taken straight to a consumer header.
|
||||
`QMap<QString, Point>`. An **optional** `?T` is a
|
||||
`std::optional<T>` — two-state in the type system, so an absent value is
|
||||
not spelled the same way as a present default, and `?uint` is
|
||||
distinguishable from `?tstr`. Only `any` — and any container whose element
|
||||
type bottoms out at `any`, such as `{tstr: any}` — stays untyped, as
|
||||
`QVariant` / `QVariantMap`: a record has a declared shape and an `any` does
|
||||
not. Here is a contract that uses all of them, taken straight to a consumer
|
||||
header.
|
||||
|
||||
### 6.1 geometry_module.lidl
|
||||
|
||||
@@ -331,9 +340,14 @@ logos-cpp-generator --lidl geometry_module.lidl \
|
||||
|
||||
`Point` is generated as a struct, so a record parameter is taken by
|
||||
const-ref and a record return comes back typed. An array-of-records is a
|
||||
`QList<Point>`. A map of `any`, an optional and a bare `any` stay
|
||||
`QVariantMap` / `QVariant` — the untyped carriers for JSON whose shape
|
||||
the contract does not declare.
|
||||
`QList<Point>`. `nearest` shows both halves of the optional mapping in
|
||||
one signature — `?uint` in, `?Point` out — as
|
||||
`std::optional<qulonglong>` and `std::optional<Point>`: the caller can
|
||||
ask `limit.has_value()` and the return can be *nothing* without
|
||||
colliding with a legitimate `Point{0, 0}`. Only `attributes` and
|
||||
`describe` stay `QVariantMap` / `QVariant`: `{tstr: any}` and `any` are
|
||||
the untyped carriers for JSON whose shape the contract does not
|
||||
declare.
|
||||
|
||||
```bash
|
||||
grep -E 'class|translate|bounds|attributes|nearest' geometry/geometry_module_api.h
|
||||
|
||||
Reference in New Issue
Block a user