Files
Dario LipicarandClaude Opus 5 5d0a99a182 chore(generator): retire ApiStyle::Std (#122)
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>
2026-07-30 21:41:52 -03:00

106 lines
6.1 KiB
C++

#ifndef GENERATOR_LIB_H
#define GENERATOR_LIB_H
#include <QString>
#include <QJsonArray>
#include <QTextStream>
#include <QVector>
#include <QPair>
struct ParsedMethod {
QString returnType;
QString name;
QVector<QPair<QString, QString>> params; // (type, name)
QString description; // doc comment adjacent to the LOGOS_METHOD declaration
};
// 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 Lp via the -DLOGOS_API_STYLE=lp CMake flag the module builder
// threads through.
// Qt — legacy Qt-typed surface (QString/QVariant…), body via LogosAPIClient.
// 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).
//
// 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.
// Static — the module name is baked into the ctor + every remote call,
// so `<Class>(LogosAPI*)` always talks to that one module.
// This is what name-baked dependency wrappers use.
// Bound — the ctor takes `(LogosAPI*, const QString& moduleName)` and
// stores it in `m_moduleName`; every remote call routes through
// that member. This is what *interface* wrappers use: one
// interface, bound to a concrete module name at runtime.
// Default is Static so existing callers and their generated output are
// byte-for-byte unchanged.
enum class BindMode { Static, Bound };
QString toPascalCase(const QString& name);
QString normalizeType(QString t);
QString mapParamType(const QString& qtType);
QString mapReturnType(const QString& qtType);
QString toQVariantConversion(const QString& type, const QString& argExpr);
// Incoming provider argument -> the declared type, via the canonical codec
// (logos::qtArgFromVariant). `path` names the slot in the diagnostic a
// rejection carries, e.g. "arg0". See the definition for why this is separate
// from toQVariantConversion.
QString toProviderArgDecode(const QString& type, const QString& argExpr,
const QString& path);
// makeHeader / makeSource emit the single `<Class>` wrapper for a
// 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
// { "name": "<event>", "params": [ { "name": "...", "type": "<QtTypeName>" } ] }
// (Qt-typed names — same surface methods come through). When non-empty,
// the wrapper also gets one `on<EventName>(callback)` accessor per
// event next to the existing generic `onEvent(name, callback)` channel.
// The accessor signature uses the apiStyle's type surface for the
// callback's argument types.
//
// `bindMode` selects a fixed-module wrapper (Static, default) or a
// runtime-bound interface wrapper (Bound) — see BindMode above. In Bound
// mode `moduleName` is used only for the class/file naming the caller
// already decided; the emitted code never bakes it into a call.
//
// `records` carries the contract's `type Foo { ... }` declarations, as
// [ { "name": "Foo", "fields": [ { "name": "...", "type": "<QtTypeName>" } ] } ]
// Each becomes a struct NESTED in the wrapper class (`<Class>::Foo`, so two
// deps may both declare a `Status`), and every method / event that mentions
// one is typed with it instead of falling back to QVariant / LogosMap. Field
// types use the same Qt type-name spelling as methods, so a field can name
// another record, `QList<Record>`, or `QMap<QString, Record>`. Empty (the
// default, and what the metaobject-introspection path passes) leaves the
// generated output exactly as it was.
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. 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 = {});
QString makeSourceLp(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
QVector<ParsedMethod> parseProviderHeader(const QString& headerPath, QTextStream& err);
#endif // GENERATOR_LIB_H