codegen: CallError out-param instead of throwing wrappers

Per review, the generated sync wrappers expose the error channel as an
optional trailing parameter — add(a, b, &err) — rather than throwing:
explicit, stateless, works on temporaries, and existing call sites
compile unchanged (they keep default-on-failure, now with a qWarning so
failures are visible in the module log). The dispatch catch-all from the
previous commit stays: it contains author exceptions, it doesn't
introduce any.
This commit is contained in:
Dario Gabriel Lipicar
2026-06-12 09:32:07 -03:00
parent 8adb023863
commit bd24fb61ea
7 changed files with 48 additions and 33 deletions
+14 -6
View File
@@ -121,6 +121,7 @@ QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode)
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";
@@ -155,7 +156,10 @@ QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode)
emitParam(s, lidlTypeToQt(md.params[i].type), md.params[i].name);
if (i + 1 < md.params.size()) s << ", ";
}
s << ");\n";
// Optional error out-channel: pass a logos::CallError* to distinguish
// a failed remote call from a legitimately default-valued result.
if (!md.params.isEmpty()) s << ", ";
s << "logos::CallError* err = nullptr);\n";
QString asyncCb = (ret == "void")
? QString("std::function<void()>")
: QString("std::function<void(") + ret + ")>";
@@ -261,11 +265,13 @@ QString lidlMakeSource(const ModuleDecl& module, BindMode bindMode)
emitParam(s, lidlTypeToQt(md.params[i].type), md.params[i].name);
if (i + 1 < nParams) s << ", ";
}
s << ") {\n";
if (nParams > 0) s << ", ";
s << "logos::CallError* err) {\n";
// Call through the err-out overload: a failed call throws
// logos::LogosCallError instead of silently degrading to the return
// type's default value (see logos_call_error.h).
// Call through the err-out overload: with a logos::CallError* the
// caller can distinguish a failed remote call from a legitimately
// default-valued result; without it the historical default-on-failure
// behavior is kept, plus a warning in the module log.
s << " logos::CallError _err;\n";
if (ret != "void") s << " QVariant _result = ";
else s << " ";
@@ -276,7 +282,9 @@ QString lidlMakeSource(const ModuleDecl& module, BindMode bindMode)
if (i + 1 < nParams) s << ", ";
}
s << "}, Timeout(), &_err);\n";
s << " if (!_err.ok()) throw logos::LogosCallError(_err);\n";
s << " if (err) *err = _err;\n";
s << " else if (!_err.ok()) qWarning() << \"" << className << "::" << md.name
<< ": remote call failed:\" << QString::fromStdString(_err.message);\n";
if (ret != "void")
s << " " << returnConversion(ret) << "\n";
@@ -448,10 +448,9 @@ QString lidlMakeProviderDispatch(const ModuleDecl& module)
s << "#include <exception>\n\n";
// --- callMethod ---
// The dispatch body is wrapped in a catch-all: anything the author's code
// (or a generated typed wrapper — see logos::LogosCallError) lets escape
// becomes an ordinary method failure (invalid QVariant) instead of an
// exception unwinding through Qt event dispatch and killing the module
// The dispatch body is wrapped in a catch-all: any exception the author's
// code lets escape becomes an ordinary method failure (invalid QVariant)
// instead of unwinding through Qt event dispatch and killing the module
// process.
s << "QVariant " << providerObjectClass
<< "::callMethod(const QString& methodName, const QVariantList& args)\n{\n";
+18 -9
View File
@@ -213,6 +213,7 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
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.
@@ -230,6 +231,7 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
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";
@@ -318,7 +320,11 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
else s << pt << " " << pn;
if (i + 1 < params.size()) s << ", ";
}
s << ");\n";
// Optional error out-channel: pass a logos::CallError* to distinguish
// a failed remote call from a legitimately default-valued result.
// Existing call sites compile unchanged.
if (!params.isEmpty()) s << ", ";
s << "logos::CallError* err = nullptr);\n";
// Async overload: same params + callback + optional Timeout
QString asyncCallbackType = (ret == "void")
? QString("std::function<void()>")
@@ -581,14 +587,15 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
emitParam(params.at(i).toObject(), byRef);
if (i + 1 < params.size()) s << ", ";
}
s << ") {\n";
if (!params.isEmpty()) s << ", ";
s << "logos::CallError* err) {\n";
// Body: perform call through the err-out overload. A failed call
// (e.g. the bound module is missing) throws logos::LogosCallError
// instead of silently degrading to the return type's default value —
// a caller cannot otherwise tell failure from a legitimate 0 / "".
// Generated provider dispatch catches anything the author lets
// escape and converts it into an ordinary method failure.
// Body: perform call through the err-out overload. When the caller
// passes a logos::CallError* it can distinguish a failed remote call
// (e.g. the bound module is missing) from a legitimately
// default-valued result; without it the historical default-on-failure
// behavior is kept, now with a warning so failures are at least
// visible in the module log.
s << " logos::CallError _err;\n";
if (ret != "void") s << " QVariant _result = ";
else s << " ";
@@ -599,7 +606,9 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
if (i + 1 < params.size()) s << ", ";
}
s << "}, Timeout(), &_err);\n";
s << " if (!_err.ok()) throw logos::LogosCallError(_err);\n";
s << " if (err) *err = _err;\n";
s << " else if (!_err.ok()) qWarning() << \"" << className << "::" << name
<< ": remote call failed:\" << QString::fromStdString(_err.message);\n";
// Return conversion
if (ret == "void") {
+3 -4
View File
@@ -612,10 +612,9 @@ static int generateProviderDispatch(const QString& headerPath, const QString& ou
methodsByName[m.name].append(&m);
}
// The dispatch body is wrapped in a catch-all: anything the author's code
// (or a generated typed wrapper — see logos::LogosCallError) lets escape
// becomes an ordinary method failure (invalid QVariant) instead of an
// exception unwinding through Qt event dispatch and killing the module
// The dispatch body is wrapped in a catch-all: any exception the author's
// code lets escape becomes an ordinary method failure (invalid QVariant)
// instead of unwinding through Qt event dispatch and killing the module
// process.
s << "QVariant " << className << "::callMethod(const QString& methodName, const QVariantList& args)\n";
s << "{\n";