chore(generator): retire ApiStyle::Std

The Std surface — std-typed signatures over a QVariant + LogosAPIClient
body — no longer had a caller. `interface: "universal"` modules moved to
`lp` (std types over the Qt-free logos-protocol C ABI), and nothing else
ever selected it, so every Std branch was dead weight sitting in front of
the two live ones.

`--api-style=std` is now rejected with a message naming the retirement
rather than aliased to `qt`. A stale caller that still passes it wants
std signatures; handing it the Qt surface would fail later, further from
the cause.

The collapse is deliberate about the branches where Std was tested BEFORE
Qt, since a naive "delete the block containing ApiStyle::Std" changes Qt
output:

- makeHeader's include block tested Std first, so its `else` is the Qt
  include list — the Qt includes are kept and promoted, not deleted.
- recordToWireExpr / recordFromWireExpr returned the Qt map form from a
  guarded `if` and the Std form from the function's trailing `return`.
  The guard is dropped and the Qt form promoted to the tail; deleting
  only the trailing return would have left a path falling off the end.
- The private-member `else if (!events.isEmpty())` arm reads as an event
  test but was Std-only; the Qt arm (m_eventReplica + m_eventSource)
  survives, so setEventSource/trigger still have their storage.
- `if (apiStyle == Qt || !events.isEmpty())` is a disjunction, not an
  Std branch: it unwraps to an unconditional emit, keeping
  ensureReplica()'s declaration next to its definition.
- `isRec || style == Std` loses only the right disjunct — dropping
  `isRec ||` would double-wrap record fields in QVariant::fromValue.

mapParamTypeStd / mapReturnTypeStd / isStdRefType stay: they are the
shared std type table that ApiStyle::Lp reaches through the non-Qt arm of
paramTypeFor / returnTypeFor / byRefFor and directly from lpPushExpr /
lpFromJsonExpr.

Verified by output equivalence rather than by the build succeeding: the
generator was run over 13 fixture cases (the full_api contract as both a
bound interface and a baked dep, three record-bearing contracts incl.
map-of-record fields, the chat module's production contract, and a
no-events contract) for both qt and lp, before and after. `diff -r` over
the 194 resulting files reports no differences, and the experimental
--lidl backends are byte-identical too. Test suite: 180/180, unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-07-30 19:42:29 -03:00
co-authored by Claude Opus 5
parent 8e7ed6e0ec
commit 70586290d3
7 changed files with 199 additions and 349 deletions
+10 -5
View File
@@ -222,7 +222,7 @@ public:
LogosMap doWork(const std::string& input) {
// Cross-module call through the flat LogosModules aggregator.
// Because this module is `interface: "universal"`, mkLogosModule.nix
// passed -DLOGOS_API_STYLE=std to the codegen, so every <Dep>
// passed -DLOGOS_API_STYLE=lp to the codegen, so every <Dep>
// wrapper takes/returns std types — no Qt at the call site.
std::string reply = modules().some_dep.echo(input);
// ...
@@ -290,13 +290,18 @@ Each module's build picks **one** API style for the generated `<Module>` client
| `metadata.json#interface` | `LOGOS_API_STYLE` | Wrapper signatures |
|---|---|---|
| `"universal"` | `std` | `std::string`, `std::vector<std::string>`, `LogosMap`, `LogosList`, `int64_t`, `StdLogosResult` |
| `"universal"` / `"cdylib"` | `lp` | `std::string`, `std::vector<std::string>`, `LogosMap`, `LogosList`, `int64_t`, `StdLogosResult` |
| `"legacy"` / `"provider"` / absent | `qt` (default) | `QString`, `QStringList`, `QVariantList`, `QVariantMap`, `int`, `LogosResult` |
`mkLogosModule.nix` reads `interface` and threads `-DLOGOS_API_STYLE=std` through to the codegen for universal modules; everyone else defaults to Qt and stays bit-for-bit backward compatible. Inside the universal module's `.cpp`, the call site is:
A third value, `std`, used to name a std-typed surface whose body still went
through `QVariant` + `LogosAPIClient`. It was retired once universal modules
moved to `lp`; `--api-style=std` is now rejected outright rather than aliased,
so a stale build fails loudly instead of silently getting Qt signatures.
`mkLogosModule.nix` reads `interface` and threads `-DLOGOS_API_STYLE=lp` through to the codegen for universal modules; everyone else defaults to Qt and stays bit-for-bit backward compatible. Inside the universal module's `.cpp`, the call site is:
```cpp
// Universal module (api-style=std):
// Universal module (api-style=lp):
std::string reply = modules().some_dep.echo("hi");
```
@@ -307,7 +312,7 @@ std::string reply = modules().some_dep.echo("hi");
QString reply = modules().some_dep.echo(QString("hi"));
```
The wire is identical (`QVariant` under the hood); for std mode the Qt↔std conversion is inlined in the generated wrapper's `.cpp`, so the calling translation unit needs zero Qt headers.
The two carry the same values; the `lp` wrapper marshals them over the logos-protocol C ABI (`lp_*`) instead of `QVariant`, so the calling translation unit needs zero Qt headers and links no qt-sdk.
> **Migrating to std types**: The choice is driven entirely by `interface`. A handcrafted module that wants std types should switch to `interface: "universal"` — there's no per-flag override on `metadata.json`.
+10 -8
View File
@@ -92,7 +92,9 @@ The codegen exposes **one** wrapper class per module — `<Module>` — with sig
| `--api-style` | Wrapper signatures |
|---|---|
| `qt` (default) | `QString` / `QStringList` / `QVariantList` / `QVariantMap` / `int` / `LogosResult` |
| `std` | `std::string` / `std::vector<std::string>` / `LogosMap` / `LogosList` / `int64_t` / `StdLogosResult` |
| `lp` | `std::string` / `std::vector<std::string>` / `LogosMap` / `LogosList` / `int64_t` / `StdLogosResult`, over the Qt-free logos-protocol C ABI |
(A third value, `std` — std signatures over a `QVariant` / `LogosAPIClient` body — was retired; the generator now rejects `--api-style=std` instead of aliasing it.)
Both styles emit:
@@ -113,15 +115,15 @@ Only the modules explicitly listed as dependencies are exposed. The runtime's `c
`ApiStyle` enum + new helpers in `generator_lib`:
- `enum class ApiStyle { Qt, Std }` — passed to every wrapper-emitting function.
- File-local `mapParamTypeStd` / `mapReturnTypeStd` / `stdParamToQVariant` / `qVariantToStdReturn` — std-side type-mapping + Qt↔std conversion expressions. Hidden from `generator_lib.h` (not part of the public surface).
- `makeHeader(moduleName, className, methods, apiStyle, events)` / `makeSource(moduleName, className, headerBaseName, methods, apiStyle, events)` — single entry points that branch on `apiStyle` internally to emit the right include block, signature shape, and conversion bridges. `events` is loaded from a `<name>.lidl` sidecar via `--events-from`; when non-empty, the wrapper also gets one typed `on<EventName>(callback)` adapter per declared event (callback arg types follow `apiStyle`). The std-style wrapper grows the necessary `ensureReplica()` plumbing on demand.
- `enum class ApiStyle { Qt, Lp }` — passed to every wrapper-emitting function.
- File-local `mapParamTypeStd` / `mapReturnTypeStd` — the std-side type-mapping table the `lp` surface exposes. Hidden from `generator_lib.h` (not part of the public surface).
- `makeHeader(moduleName, className, methods, apiStyle, events)` / `makeSource(moduleName, className, headerBaseName, methods, apiStyle, events)` — single entry points that branch on `apiStyle` internally to emit the right include block, signature shape, and conversion bridges. `events` is loaded from a `<name>.lidl` sidecar via `--events-from`; when non-empty, the wrapper also gets one typed `on<EventName>(callback)` adapter per declared event (callback arg types follow `apiStyle`).
Flag plumbing:
1. `metadata.json#interface == "universal"``mkLogosModule.nix` adds `-DLOGOS_API_STYLE=std` to `extraCmakeFlags`. Anything else (`"legacy"`, `"provider"`, absent) leaves the default `qt`.
2. `LogosModule.cmake` reads `${LOGOS_API_STYLE}` (default `qt`) and forwards `--api-style=${LOGOS_API_STYLE}` to the `logos-cpp-generator --general-only` invocation that writes the umbrella. Each module's Nix build emits **two** header derivations (`<name>.headers-qt` and `<name>.headers-std`) via `buildHeaders.nix` — one `logos-cpp-generator --api-style=…` run per style, at the dep's build time. A consumer's `buildPlugin.nix` picks `dep.headers-${apiStyle}` and copies its `include/` straight into the build sandbox; no codegen runs at consume time. Nix's laziness means only the variant a downstream actually depends on is realised.
3. `legacy/main.cpp` parses `--api-style` once and threads the resulting `ApiStyle` through `generateFromPlugin`, `writeUmbrellaHeader{,FromDeps}`. No `_api_std.{h,cpp}` files are ever emitted; each module gets a single `<name>_api.h` + `<name>_api.cpp` pair regardless of style.
1. `metadata.json#interface == "universal"` (or `"cdylib"`)`mkLogosModule.nix` adds `-DLOGOS_API_STYLE=lp` to `extraCmakeFlags`. Anything else (`"legacy"`, `"provider"`, absent) leaves the default `qt`.
2. `LogosModule.cmake` reads `${LOGOS_API_STYLE}` (default `qt`) and forwards `--api-style=${LOGOS_API_STYLE}` to the `logos-cpp-generator --general-only` invocation that writes the umbrella. Each module's Nix build emits **two** header derivations (`<name>.headers-qt` and `<name>.headers-lp`) via `buildHeaders.nix` — one `logos-cpp-generator --api-style=…` run per style, at the dep's build time. A consumer's `buildPlugin.nix` picks `dep.headers-${apiStyle}` and copies its `include/` straight into the build sandbox; no codegen runs at consume time. Nix's laziness means only the variant a downstream actually depends on is realised.
3. `legacy/main.cpp` parses `--api-style` once (rejecting the retired `std`) and threads the resulting `ApiStyle` through `generateFromPlugin`, `writeUmbrellaHeader{,FromDeps}`. No per-style filenames are ever emitted; each module gets a single `<name>_api.h` + `<name>_api.cpp` pair regardless of style.
### Provider Generation (logos-qt-generator)
@@ -191,7 +193,7 @@ The `--events-from <path>` flag points the legacy `<plugin>.dylib --module-only`
```bash
logos-cpp-generator /path/to/plugin.dylib \
--module-only --api-style std \
--module-only --api-style lp \
--events-from /path/to/dep/share/logos/my_module.lidl \
--output-dir ./generated
```
+3 -1
View File
@@ -321,7 +321,9 @@ Generated from LIDL (not from `--from-header`). Each module gets **one** `<Modul
| `--api-style` | Wrapper signatures |
|---|---|
| `qt` (default) | QString / QStringList / QVariantList / QVariantMap / int / LogosResult |
| `std` | std::string / std::vector<std::string> / LogosMap / LogosList / int64_t / StdLogosResult |
| `lp` | std::string / std::vector<std::string> / LogosMap / LogosList / int64_t / StdLogosResult, over the Qt-free logos-protocol C ABI |
(`std` — the same signatures over a `QVariant` / `LogosAPIClient` body — was retired; `--api-style=std` is now an error.)
Both styles provide:
+128 -301
View File
@@ -101,16 +101,14 @@ QString toQVariantConversion(const QString& type, const QString& argExpr)
return argExpr + ".toString()";
}
// ─── Std (pure-C++) type-mapping helpers ─────────────────────────────────
// ─── std (pure-C++) type-mapping table ───────────────────────────────────
//
// File-local — not exposed in generator_lib.h. The single public entry
// point is makeHeader / makeSource taking an `ApiStyle` argument; when
// `apiStyle == Std`, those functions internally route through these
// helpers to pick the std type-mapping table. There's only one wrapper
// class per module — `<Module>` — whose signatures depend on apiStyle.
// The std-typed body still goes through the QVariant wire; the Qt↔std
// conversion is inlined at the call site, contained to the generated
// .cpp so callers never include Qt headers.
// File-local — not exposed in generator_lib.h. This is the type table the
// Qt-free surface (ApiStyle::Lp) exposes: the wrapper's signatures are std
// types so a universal / cdylib module's own translation units never name a
// Qt type. Reached through paramTypeFor / returnTypeFor / byRefFor below (the
// non-Qt arm of each) and directly from the Lp backend's lpPushExpr /
// lpFromJsonExpr.
static QString mapParamTypeStd(const QString& qtType)
{
@@ -146,81 +144,6 @@ static QString mapReturnTypeStd(const QString& qtType)
return base;
}
// Returns a C++ expression that lifts a std-typed parameter into a
// QVariant-side temporary suitable for invokeRemoteMethod. The
// temporaries are rvalues consumed inline at the call site.
static QString stdParamToQVariant(const QString& qtType, const QString& argName)
{
const QString base = mapParamType(qtType);
if (base == "QString")
return "QString::fromStdString(" + argName + ")";
if (base == "QStringList")
return "[&]{ QStringList _q; _q.reserve(static_cast<int>(" + argName +
".size())); for (const auto& _s : " + argName +
") _q.append(QString::fromStdString(_s)); return _q; }()";
if (base == "QByteArray")
return "QByteArray(reinterpret_cast<const char*>(" + argName +
".data()), static_cast<int>(" + argName + ".size()))";
if (base == "QJsonArray")
return "QJsonDocument::fromJson(QByteArray::fromStdString(" + argName +
".dump())).array()";
if (base == "QVariantList" || base == "QVariantMap" || base == "QVariant")
return "QJsonDocument::fromJson(QByteArray::fromStdString(" + argName +
".dump())).toVariant()" + (base == "QVariantList" ? ".toList()"
: base == "QVariantMap" ? ".toMap()"
: "");
if (base == "int")
// The std signature exposes `int64_t`; widen the QVariant
// payload to qlonglong so the wire carries the full 64-bit
// value instead of silently truncating to 32 bits on the way
// through `static_cast<int>`. (Reported by Copilot review on
// PR #61 — the std-typed surface and the wire payload were
// disagreeing for any value outside the int32 range.)
return "static_cast<qlonglong>(" + argName + ")";
return argName;
}
// Returns a C++ expression that converts a QVariant return value into
// the std-typed return type. `varExpr` is the source QVariant.
static QString qVariantToStdReturn(const QString& qtType, const QString& varExpr)
{
const QString base = mapReturnType(qtType);
if (base == "void")
return QString();
if (base == "bool")
return varExpr + ".toBool()";
if (base == "int")
return "static_cast<int64_t>(" + varExpr + ".toInt())";
if (base == "qlonglong")
return varExpr + ".toLongLong()";
if (base == "qulonglong")
return varExpr + ".toULongLong()";
if (base == "double" || base == "float")
return varExpr + ".toDouble()";
if (base == "QString")
return varExpr + ".toString().toStdString()";
if (base == "QStringList")
return "[&]{ std::vector<std::string> _v; const QStringList _q = " +
varExpr + ".toStringList(); _v.reserve(static_cast<size_t>(_q.size())); "
"for (const QString& _s : _q) _v.push_back(_s.toStdString()); return _v; }()";
if (base == "QByteArray")
return "[&]{ const QByteArray _b = " + varExpr +
".toByteArray(); return std::vector<uint8_t>(_b.begin(), _b.end()); }()";
if (base == "QJsonArray" || base == "QVariantList")
return "LogosList::parse(QJsonDocument(QJsonArray::fromVariantList(" +
varExpr + ".toList())).toJson(QJsonDocument::Compact).toStdString())";
if (base == "QVariantMap" || base == "QVariant")
return "LogosMap::parse(QJsonDocument(QJsonObject::fromVariantMap(" +
varExpr + ".toMap())).toJson(QJsonDocument::Compact).toStdString())";
if (base == "LogosResult")
return "[&]{ StdLogosResult _r; const LogosResult _q = " + varExpr +
".value<LogosResult>(); _r.success = _q.success; "
"if (_q.value.isValid()) _r.value = LogosMap::parse("
"QJsonDocument(QJsonObject::fromVariantMap(_q.value.toMap())).toJson(QJsonDocument::Compact).toStdString()); "
"_r.error = _q.error.toString().toStdString(); return _r; }()";
return varExpr + ".toString().toStdString()";
}
// ─── Records ─────────────────────────────────────────────────────────────
//
// A contract's `type Status { port: uint }` is a REAL C++ struct on the
@@ -343,12 +266,9 @@ static QString recordToWireExpr(const RecordSet& rs, const QString& t, ApiStyle
if (shape == RecordShape::List)
return "[&]{ QVariantList __acc; for (const auto& __e : " + expr
+ ") __acc.append(" + conv + "(__e)); return __acc; }()";
// Qt keys are QString, std keys std::string.
if (style == ApiStyle::Qt)
return "[&]{ QVariantMap __acc; for (auto __i = " + expr + ".cbegin(); __i != " + expr
+ ".cend(); ++__i) __acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
return "[&]{ QVariantMap __acc; for (const auto& __kv : " + expr
+ ") __acc.insert(QString::fromStdString(__kv.first), " + conv + "(__kv.second)); return __acc; }()";
// Map, Qt surface: the source container is a QMap, so keys are QString.
return "[&]{ QVariantMap __acc; for (auto __i = " + expr + ".cbegin(); __i != " + expr
+ ".cend(); ++__i) __acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
}
static QString recordFromWireExpr(const RecordSet& rs, const QString& t, ApiStyle style,
@@ -372,13 +292,10 @@ static QString recordFromWireExpr(const RecordSet& rs, const QString& t, ApiStyl
if (shape == RecordShape::List)
return "[&]{ " + cpp + " __acc; for (const QVariant& __e : (" + wire
+ ").toList()) __acc.push_back(" + conv + "(__e)); return __acc; }()";
if (style == ApiStyle::Qt)
return "[&]{ " + cpp + " __acc; const QVariantMap __src = (" + wire
+ ").toMap(); for (auto __i = __src.cbegin(); __i != __src.cend(); ++__i) "
"__acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
// Map, Qt surface: the destination container is a QMap, so keys are QString.
return "[&]{ " + cpp + " __acc; const QVariantMap __src = (" + wire
+ ").toMap(); for (auto __i = __src.cbegin(); __i != __src.cend(); ++__i) "
"__acc[__i.key().toStdString()] = " + conv + "(__i.value()); return __acc; }()";
"__acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
}
// Param-type predicate: passed by const-ref?
@@ -433,13 +350,12 @@ static bool byRefFor(const QString& qtType, const QString& cppType, ApiStyle sty
return (style == ApiStyle::Qt) ? isQtRefType(cppType) : isStdRefType(cppType);
}
// Typed value -> wire value (QVariant for Qt/Std, nlohmann::json for Lp).
// Typed value -> wire value (QVariant for Qt, nlohmann::json for Lp).
static QString toWireFor(const QString& qtType, ApiStyle style, const RecordSet& rs, const QString& expr)
{
const QString rec = recordToWireExpr(rs, qtType, style, expr);
if (!rec.isEmpty()) return rec;
if (style == ApiStyle::Lp) return lpPushExpr(qtType, expr);
if (style == ApiStyle::Std) return stdParamToQVariant(qtType, expr);
return expr; // Qt: the wrapper's own surface already IS the wire type
}
@@ -450,7 +366,6 @@ static QString fromWireFor(const QString& qtType, ApiStyle style, const RecordSe
const QString rec = recordFromWireExpr(rs, qtType, style, wire, qual);
if (!rec.isEmpty()) return rec;
if (style == ApiStyle::Lp) return lpFromJsonExpr(qtType, wire);
if (style == ApiStyle::Std) return qVariantToStdReturn(qtType, wire);
return toQVariantConversion(mapParamType(qtType), wire);
}
@@ -505,7 +420,7 @@ static void emitRecordConversions(QTextStream& s, const RecordSet& rs, ApiStyle
const QString v = toWireFor(f.type, style, rs, "v." + f.name);
const bool isRec = recordShape(rs, f.type, nullptr) != RecordShape::None;
s << " __m.insert(QStringLiteral(\"" << f.name << "\"), "
<< (isRec || style == ApiStyle::Std ? v : "QVariant::fromValue(" + v + ")")
<< (isRec ? v : "QVariant::fromValue(" + v + ")")
<< ");\n";
}
s << " return __m;\n";
@@ -545,43 +460,19 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
QString h;
QTextStream s(&h);
s << "#pragma once\n";
if (apiStyle == ApiStyle::Std) {
// Pure-C++ surface. Qt is still pulled in transitively by
// logos_api.h (LogosAPI is a QObject), but the wrapper's
// signatures are entirely std types so callers never have to
// type a Qt name themselves.
s << "#include <cstdint>\n";
s << "#include <string>\n";
s << "#include <vector>\n";
s << "#include <functional>\n";
s << "#include \"logos_types.h\"\n";
s << "#include \"logos_json.h\"\n";
s << "#include \"logos_result.h\"\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n";
s << "#include \"logos_call_error.h\"\n";
// Needed for the m_eventReplica member when the module declares
// any events. Cheap to include unconditionally — keeps the
// header symmetric with the Qt-style branch.
if (!events.isEmpty()) s << "#include \"logos_object.h\"\n";
// Record maps are std::map on the std surface.
if (!rs.isEmpty()) s << "#include <map>\n";
s << "\n";
} else {
s << "#include <QString>\n";
s << "#include <QVariant>\n";
s << "#include <QStringList>\n";
s << "#include <QJsonArray>\n";
s << "#include <QVariantList>\n";
s << "#include <QVariantMap>\n";
s << "#include <functional>\n";
s << "#include <utility>\n";
s << "#include \"logos_types.h\"\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n";
s << "#include \"logos_call_error.h\"\n";
s << "#include \"logos_object.h\"\n\n";
}
s << "#include <QString>\n";
s << "#include <QVariant>\n";
s << "#include <QStringList>\n";
s << "#include <QJsonArray>\n";
s << "#include <QVariantList>\n";
s << "#include <QVariantMap>\n";
s << "#include <functional>\n";
s << "#include <utility>\n";
s << "#include \"logos_types.h\"\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n";
s << "#include \"logos_call_error.h\"\n";
s << "#include \"logos_object.h\"\n\n";
s << "class " << className << " {\n";
s << "public:\n";
emitRecordStructs(s, rs, apiStyle);
@@ -591,31 +482,28 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
} else {
s << " explicit " << className << "(LogosAPI* api);\n\n";
}
if (apiStyle == ApiStyle::Qt) {
// Event subscription / trigger surface — Qt-typed.
s << " using RawEventCallback = std::function<void(const QString&, const QVariantList&)>;\n";
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
s << " bool on(const QString& eventName, EventCallback callback);\n";
s << " void setEventSource(LogosObject* source);\n";
s << " LogosObject* eventSource() const;\n";
s << " void trigger(const QString& eventName);\n";
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, Args&&... args) {\n";
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n";
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n\n";
}
// Event subscription / trigger surface — Qt-typed.
s << " using RawEventCallback = std::function<void(const QString&, const QVariantList&)>;\n";
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
s << " bool on(const QString& eventName, EventCallback callback);\n";
s << " void setEventSource(LogosObject* source);\n";
s << " LogosObject* eventSource() const;\n";
s << " void trigger(const QString& eventName);\n";
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, Args&&... args) {\n";
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n";
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n\n";
// Typed event subscribers — generated from the `.lidl` sidecar shipped
// with the dep's pre-built headers (via --events-from). One typed
// adapter per declared event, callback-arg types follow apiStyle.
// The generic `on(name, cb)` channel above stays available; for
// std-style consumers it's not exposed but the typed accessors are.
// The generic `on(name, cb)` channel above stays available alongside them.
for (const QJsonValue& ev : events) {
const QJsonObject eo = ev.toObject();
const QString evName = eo.value("name").toString();
@@ -690,34 +578,23 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
s << asyncCallbackType << " callback, Timeout timeout = Timeout());\n";
}
s << "\nprivate:\n";
// ensureReplica() is needed whenever the wrapper subscribes to
// events — in Qt mode that's always (the generic `on(...)` channel
// is exposed); in std mode it's gated on at least one declared
// event in the LIDL sidecar.
if (apiStyle == ApiStyle::Qt || !events.isEmpty()) {
s << " LogosObject* ensureReplica();\n";
}
if (apiStyle == ApiStyle::Qt) {
s << " template<typename... Args>\n";
s << " static QVariantList packVariantList(Args&&... args) {\n";
s << " QVariantList list;\n";
s << " list.reserve(sizeof...(Args));\n";
s << " using Expander = int[];\n";
s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward<Args>(args))), 0)...};\n";
s << " return list;\n";
s << " }\n";
}
// ensureReplica() is needed whenever the wrapper subscribes to events,
// which on the Qt surface is always: the generic `on(...)` channel is
// exposed even when the contract declares no typed events.
s << " LogosObject* ensureReplica();\n";
s << " template<typename... Args>\n";
s << " static QVariantList packVariantList(Args&&... args) {\n";
s << " QVariantList list;\n";
s << " list.reserve(sizeof...(Args));\n";
s << " using Expander = int[];\n";
s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward<Args>(args))), 0)...};\n";
s << " return list;\n";
s << " }\n";
s << " LogosAPI* m_api;\n";
s << " LogosAPIClient* m_client;\n";
s << " QString m_moduleName;\n";
if (apiStyle == ApiStyle::Qt) {
s << " LogosObject* m_eventReplica = nullptr;\n";
s << " LogosObject* m_eventSource = nullptr;\n";
} else if (!events.isEmpty()) {
// std-style consumer: only the receive-side replica is needed
// (no `trigger(...)` API on the std wrapper, so no eventSource).
s << " LogosObject* m_eventReplica = nullptr;\n";
}
s << " LogosObject* m_eventReplica = nullptr;\n";
s << " LogosObject* m_eventSource = nullptr;\n";
s << "};\n";
return h;
}
@@ -731,24 +608,8 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
QTextStream s(&c);
s << "#include \"" << headerBaseName << "\"\n\n";
s << "#include <QDebug>\n";
if (apiStyle == ApiStyle::Std) {
// Conversion bridges between std types and QVariant — confined
// to this .cpp so the caller's translation unit doesn't need
// any Qt headers itself.
s << "#include <QJsonDocument>\n";
s << "#include <QJsonArray>\n";
s << "#include <QJsonObject>\n";
s << "#include <QByteArray>\n";
s << "#include <QStringList>\n";
s << "#include <QVariantList>\n";
s << "#include <QVariantMap>\n";
// logos_object.h is the type of LogosObject* used by typed event
// accessors when the module declares events. Always include in
// std mode when events are present.
if (!events.isEmpty()) s << "#include \"logos_object.h\"\n";
}
if (apiStyle == ApiStyle::Qt && !rs.isEmpty()) {
// Record conversions build QVariantMaps regardless of api style.
if (!rs.isEmpty()) {
// Record conversions build QVariantMaps.
s << "#include <QVariantMap>\n";
}
s << "\n";
@@ -766,79 +627,61 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
s << className << "::" << className << "(LogosAPI* api) : m_api(api), m_client(api->getClient(\"" << moduleName << "\")), m_moduleName(QStringLiteral(\"" << moduleName << "\")) {}\n\n";
}
// ensureReplica() — generated for std mode too when events are
// declared. The body is identical to the Qt version; pulled up
// here so both branches share it.
if (apiStyle == ApiStyle::Std && !events.isEmpty()) {
s << "LogosObject* " << className << "::ensureReplica() {\n";
s << " if (!m_eventReplica) {\n";
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
s << " if (!replica) {\n";
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
s << " return nullptr;\n";
s << " }\n";
s << " m_eventReplica = replica;\n";
s << " }\n";
s << " return m_eventReplica;\n";
s << "}\n\n";
}
if (apiStyle == ApiStyle::Qt) {
s << "LogosObject* " << className << "::ensureReplica() {\n";
s << " if (!m_eventReplica) {\n";
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
s << " if (!replica) {\n";
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
s << " return nullptr;\n";
s << " }\n";
s << " m_eventReplica = replica;\n";
s << " }\n";
s << " return m_eventReplica;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " LogosObject* origin = ensureReplica();\n";
s << " if (!origin) {\n";
s << " return false;\n";
s << " }\n";
s << " m_client->onEvent(origin, eventName, callback);\n";
s << " return true;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n";
s << " callback(data);\n";
s << " });\n";
s << "}\n\n";
s << "void " << className << "::setEventSource(LogosObject* source) {\n";
s << " m_eventSource = source;\n";
s << "}\n\n";
s << "LogosObject* " << className << "::eventSource() const {\n";
s << " return m_eventSource;\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName) {\n";
s << " trigger(eventName, QVariantList{});\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, const QVariantList& data) {\n";
s << " if (!m_eventSource) {\n";
s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
s << " if (!source) {\n";
s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(source, eventName, data);\n";
s << "}\n\n";
}
s << "LogosObject* " << className << "::ensureReplica() {\n";
s << " if (!m_eventReplica) {\n";
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
s << " if (!replica) {\n";
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
s << " return nullptr;\n";
s << " }\n";
s << " m_eventReplica = replica;\n";
s << " }\n";
s << " return m_eventReplica;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " LogosObject* origin = ensureReplica();\n";
s << " if (!origin) {\n";
s << " return false;\n";
s << " }\n";
s << " m_client->onEvent(origin, eventName, callback);\n";
s << " return true;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n";
s << " callback(data);\n";
s << " });\n";
s << "}\n\n";
s << "void " << className << "::setEventSource(LogosObject* source) {\n";
s << " m_eventSource = source;\n";
s << "}\n\n";
s << "LogosObject* " << className << "::eventSource() const {\n";
s << " return m_eventSource;\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName) {\n";
s << " trigger(eventName, QVariantList{});\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, const QVariantList& data) {\n";
s << " if (!m_eventSource) {\n";
s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
s << " if (!source) {\n";
s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(source, eventName, data);\n";
s << "}\n\n";
// Typed event adapters — one per declared event. The callback type
// uses the apiStyle's type surface; the body unmarshals from the
@@ -911,9 +754,8 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
const QString retQual = returnTypeFor(qtRet, apiStyle, rs, className + "::");
QJsonArray params = o.value("parameters").toArray();
// Helper closures kept inline so the two branches don't get
// pulled apart visually — the per-arg / per-return shape is
// the only thing that varies between Qt and Std modes.
// Helper closures kept inline so the signature and the call that
// consumes it stay next to each other.
auto emitParam = [&](const QJsonObject& p, bool& byRefOut) {
QString qtPt = p.value("type").toString();
QString pt = paramTypeFor(qtPt, apiStyle, rs);
@@ -970,8 +812,6 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
// nothing
} else if (retIsRecord) {
s << " return " << fromWireFor(qtRet, apiStyle, rs, "_result") << ";\n";
} else if (apiStyle == ApiStyle::Std) {
s << " return " << qVariantToStdReturn(qtRet, "_result") << ";\n";
} else if (ret == "bool") {
s << " return _result.toBool();\n";
} else if (ret == "qlonglong") {
@@ -1036,22 +876,6 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
// A record decodes field by field; an invalid QVariant yields a
// default-constructed struct, matching the scalar paths.
s << " callback(" << fromWireFor(qtRet, apiStyle, rs, "v", className + "::") << ");\n";
} else if (apiStyle == ApiStyle::Std) {
// Default-construct on dispatch failure, matching the
// existing Qt code path which falls back to a zero / empty
// value when the QVariant is invalid.
QString defaultVal;
if (ret == "bool") defaultVal = "false";
else if (ret == "int64_t") defaultVal = "0";
else if (ret == "double") defaultVal = "0.0";
else if (ret == "std::string") defaultVal = "std::string()";
else if (ret.startsWith("std::vector")) defaultVal = ret + "()";
else if (ret == "LogosMap") defaultVal = "LogosMap::object()";
else if (ret == "LogosList") defaultVal = "LogosList::array()";
else if (ret == "StdLogosResult") defaultVal = "StdLogosResult{}";
else defaultVal = ret + "{}";
s << " if (!v.isValid()) { callback(" << defaultVal << "); return; }\n";
s << " callback(" << qVariantToStdReturn(qtRet, "v") << ");\n";
} else {
QString defaultVal;
if (ret == "bool") defaultVal = "false";
@@ -1187,12 +1011,15 @@ QVector<ParsedMethod> parseProviderHeader(const QString& headerPath, QTextStream
// ─── ApiStyle::Lp (Qt-free) wrapper emission ─────────────────────────────
//
// Same std-typed surface as ApiStyle::Std, but the generated body calls the
// logos-protocol C ABI through logos::LpClient instead of LogosAPIClient, so
// the wrapper's translation unit pulls in no Qt. Used for the cdylib outbound
// path (a Qt-free module calling its dependencies / subscribing to their
// events). The class still holds a single target; Static bakes it, Bound takes
// it at construction (interface dependencies).
// A std-typed surface (the mapParamTypeStd / mapReturnTypeStd table above)
// whose generated body calls the logos-protocol C ABI through logos::LpClient
// instead of LogosAPIClient, so the wrapper's translation unit pulls in no Qt.
// This is the only remaining std-typed flavour; the retired ApiStyle::Std
// exposed the same signatures over a QVariant + LogosAPIClient body.
//
// Used for the cdylib outbound path (a Qt-free module calling its dependencies
// / subscribing to their events). The class still holds a single target;
// Static bakes it, Bound takes it at construction (interface dependencies).
// std value -> nlohmann::json push expression. nlohmann handles
// string/int64/double/bool/vector<string>/json (LogosMap/LogosList) directly;
+17 -15
View File
@@ -17,18 +17,21 @@ struct ParsedMethod {
// Which type surface to expose on the generated per-module wrapper.
// Each module's build picks ONE — there's no composite output. Default
// is Qt for backward compatibility; `interface: "universal"` modules
// flip to Std via the -DLOGOS_API_STYLE=std CMake flag the module
// builder threads through.
// flip to Lp via the -DLOGOS_API_STYLE=lp CMake flag the module builder
// threads through.
// Qt — legacy Qt-typed surface (QString/QVariant…), body via LogosAPIClient.
// Std — std-typed surface, but the body still bridges through QVariant +
// LogosAPIClient (so the wrapper .cpp links qt-sdk).
// Lp — std-typed surface AND a Qt-free body: the wrapper calls the
// logos-protocol C ABI (lp_*) directly via logos::LpClient, so the
// module's translation units never include Qt or link qt-sdk. This is
// the path that lets a cdylib module do outbound typed calls/event
// subscriptions while staying Qt-free (Qt confined to the QRO transport
// inside logos-protocol + the generated plugin glue).
enum class ApiStyle { Qt, Std, Lp };
//
// A third flavour, Std, used to sit between the two: the std-typed surface
// with a body that still bridged through QVariant + LogosAPIClient. Nothing
// selected it any more (universal modules go straight to Lp), so it was
// retired; `--api-style=std` is now a hard error rather than a silent alias.
enum class ApiStyle { Qt, Lp };
// Whether the generated wrapper targets ONE fixed module (the historical
// behaviour) or binds to a module name chosen at runtime.
@@ -57,14 +60,12 @@ QString toProviderArgDecode(const QString& type, const QString& argExpr,
const QString& path);
// makeHeader / makeSource emit the single `<Class>` wrapper for a
// module. When `apiStyle == Std`, parameter / return types come from
// the std-typed mapping table (std::string / std::vector<std::string>
// / LogosMap / LogosList / int64_t / StdLogosResult) and the .cpp
// body wraps the QVariant wire with inline Qt↔std conversions —
// callers never include Qt headers. When `apiStyle == Qt`, the output
// matches the legacy Qt-typed surface (QString / QStringList /
// QVariantList / QVariantMap / int / LogosResult). The class name is
// always `<Module>` either way; the two styles are mutually exclusive.
// module. When `apiStyle == Qt`, the output is the legacy Qt-typed
// surface (QString / QStringList / QVariantList / QVariantMap / int /
// LogosResult) with a body that calls LogosAPIClient. When
// `apiStyle == Lp`, they delegate to makeHeaderLp / makeSourceLp below,
// which emit the std-typed, Qt-free surface. The class name is always
// `<Module>` either way; the two styles are mutually exclusive.
//
// `events` carries typed event prototypes loaded from a `.lidl`
// sidecar via --events-from. Each entry is
@@ -92,8 +93,9 @@ QString toProviderArgDecode(const QString& type, const QString& argExpr,
QString makeHeader(const QString& moduleName, const QString& className, const QJsonArray& methods, ApiStyle apiStyle = ApiStyle::Qt, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
QString makeSource(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods, ApiStyle apiStyle = ApiStyle::Qt, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
// Qt-free (ApiStyle::Lp) wrapper emission. Same std-typed surface as the Std
// flavor, but the generated body calls the logos-protocol C ABI through
// Qt-free (ApiStyle::Lp) wrapper emission. A std-typed surface
// (std::string / std::vector<std::string> / LogosMap / LogosList / int64_t /
// StdLogosResult) whose generated body calls the logos-protocol C ABI through
// logos::LpClient instead of LogosAPIClient — no Qt in the wrapper's TU.
// makeHeader/makeSource dispatch here when apiStyle == ApiStyle::Lp.
QString makeHeaderLp(const QString& moduleName, const QString& className, const QJsonArray& methods, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
+22 -10
View File
@@ -389,7 +389,7 @@ static bool writeUmbrellaHeader(const QString& genDirPath, QTextStream& err)
{
// Generate logos_sdk.h: include every per-module wrapper header in
// the gen dir and aggregate them into a flat `LogosModules` struct.
// The wrappers may be Qt-typed or Std-typed depending on the
// The wrappers may be Qt-typed or std-typed (lp) depending on the
// --api-style picked for this build; the umbrella shape doesn't
// change because either flavor produces the same accessor name
// (`<dep>`) on the same class name (`<Dep>`).
@@ -880,8 +880,9 @@ static int generateFromPlugin(const QString& pluginInputPath, const QString& out
// Single per-module wrapper file pair. apiStyle decides the
// signature shape: Qt-typed for legacy / handcrafted callers
// (default), std-typed when the consuming module's build passed
// --api-style=std (typically because it's `interface: "universal"`).
// (default), std-typed and Qt-free when the consuming module's build
// passed --api-style=lp (typically because it's `interface:
// "universal"` or `"cdylib"`).
// Both produce the same filename and class name, so the umbrella
// doesn't need to know which style was picked. `events` (loaded
// from a sibling `.lidl` sidecar via --events-from) adds typed
@@ -955,14 +956,19 @@ int legacy_main(int argc, char* argv[])
// Parse --general-only option
bool generalOnly = args.contains("--general-only");
// Parse --api-style option (qt | std). Picks which type surface
// Parse --api-style option (qt | lp). Picks which type surface
// the generated `<Module>` wrapper exposes. Default is qt for
// backward compatibility — every existing module that doesn't
// declare `interface: "universal"` in its metadata.json keeps
// its Qt-typed LogosModules surface. Universal modules get
// -DLOGOS_API_STYLE=std threaded through by mkLogosModule.nix /
// LogosModule.cmake, which becomes `--api-style=std` here.
// Both forms accepted: `--api-style std` and `--api-style=std`.
// its Qt-typed LogosModules surface. Universal / cdylib modules get
// -DLOGOS_API_STYLE=lp threaded through by mkLogosModule.nix /
// LogosModule.cmake, which becomes `--api-style=lp` here.
// Both forms accepted: `--api-style lp` and `--api-style=lp`.
//
// `std` was a third surface (std types over a QVariant/LogosAPIClient
// body). It is retired, and rejected LOUDLY rather than aliased to qt:
// a stale caller that still passes it wants std signatures, and silently
// handing it the Qt surface would only fail later, further from the cause.
ApiStyle apiStyle = ApiStyle::Qt;
{
QString apiVal;
@@ -977,11 +983,17 @@ int legacy_main(int argc, char* argv[])
break;
}
}
if (apiVal == "std") apiStyle = ApiStyle::Std;
if (apiVal == "std") {
err << "--api-style=std was retired: the Std surface (std types over a "
<< "QVariant/LogosAPIClient body) no longer exists.\n"
<< "Use 'lp' for the Qt-free std-typed surface, or 'qt' for the "
<< "Qt-typed one.\n";
return 1;
}
else if (apiVal == "lp") apiStyle = ApiStyle::Lp;
else if (!apiVal.isEmpty() && apiVal != "qt") {
err << "Unknown --api-style value: " << apiVal
<< " (expected 'qt', 'std', or 'lp')\n";
<< " (expected 'qt' or 'lp')\n";
return 1;
}
}
+9 -9
View File
@@ -2,7 +2,7 @@
// C++ module actually gets for its dependencies (`--dep <name>=<lidl>`).
//
// A contract's `type Status { ... }` used to reach every C++ consumer as an
// untyped bag: QVariant on the Qt surface, LogosMap on the std/lp one. The
// untyped bag: QVariant on the Qt surface, LogosMap on the lp one. The
// caller then had to know the field names AND, for a `bstr` field, that the
// value arrives as the canonical `{"_bytes": "..."}` envelope it must unwrap
// itself — while Rust and the client-stub backend hand back a real struct.
@@ -97,11 +97,11 @@ TEST(Records, QtWrapperExposesTheStruct)
EXPECT_FALSE(h.contains("describeStatus(QVariant"));
}
// The std / lp surfaces spell the same records in std types — a universal
// The lp surface spells the same records in std types — a universal
// (Qt-free) module never sees a Qt name.
TEST(Records, StdAndLpWrappersUseStdFieldTypes)
TEST(Records, LpWrapperUsesStdFieldTypes)
{
for (ApiStyle style : {ApiStyle::Std, ApiStyle::Lp}) {
for (ApiStyle style : {ApiStyle::Lp}) {
const QString h = makeHeader("info_module", "InfoModule", statusMethods(),
style, {}, BindMode::Static, statusRecords());
EXPECT_TRUE(h.contains(" uint64_t port{};")) << h.toStdString();
@@ -113,7 +113,7 @@ TEST(Records, StdAndLpWrappersUseStdFieldTypes)
EXPECT_FALSE(h.contains("LogosMap getStatus("));
}
// std::map needs its header on the std/lp surfaces.
// std::map needs its header on the lp surface.
const QString lp = makeHeader("info_module", "InfoModule", statusMethods(),
ApiStyle::Lp, {}, BindMode::Static, statusRecords());
EXPECT_TRUE(lp.contains("#include <map>"));
@@ -136,7 +136,7 @@ TEST(Records, BytesFieldsUseTheCanonicalEncoding)
EXPECT_TRUE(qt.contains("__out.blob = __m.value(QStringLiteral(\"blob\")).toByteArray();"));
}
// The conversions are file-local statics in the .cpp: a std/lp consumer's own
// The conversions are file-local statics in the .cpp: an lp consumer's own
// translation units must not need the wire type to include the header.
TEST(Records, ConversionsStayOutOfTheHeader)
{
@@ -172,11 +172,11 @@ TEST(Records, ReturnTypesAreQualifiedInTheDefinition)
// from its own uninitialized local — it compiled, with only a warning.
TEST(Records, ContainerLambdasDoNotShadowTheDecoderLocals)
{
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Std, ApiStyle::Lp}) {
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Lp}) {
const QString c = makeSource("info_module", "InfoModule", "info_module_api.h",
statusMethods(), style, {}, BindMode::Static,
statusRecords());
// The decoder's own map is `__m` (Qt/Std); a nested lambda must not
// The decoder's own map is `__m` (Qt); a nested lambda must not
// declare another one.
EXPECT_FALSE(c.contains("const QVariantMap __m = (__m.value")) << c.toStdString();
EXPECT_FALSE(c.contains("const nlohmann::json& __j = w.at")) << c.toStdString();
@@ -188,7 +188,7 @@ TEST(Records, ContainerLambdasDoNotShadowTheDecoderLocals)
TEST(Records, EmptyRecordSetChangesNothing)
{
const QJsonArray methods{method("ping", "QString", QJsonArray{param("msg", "QString")})};
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Std, ApiStyle::Lp}) {
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Lp}) {
EXPECT_EQ(makeHeader("m", "M", methods, style, {}, BindMode::Static, {}),
makeHeader("m", "M", methods, style, {}, BindMode::Static));
EXPECT_EQ(makeSource("m", "M", "m_api.h", methods, style, {}, BindMode::Static, {}),