diff --git a/cpp-generator/experimental/lidl_gen_client.cpp b/cpp-generator/experimental/lidl_gen_client.cpp index 5622ad4..8fb5a70 100644 --- a/cpp-generator/experimental/lidl_gen_client.cpp +++ b/cpp-generator/experimental/lidl_gen_client.cpp @@ -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") : QString("std::function"; @@ -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"; diff --git a/cpp-generator/experimental/lidl_gen_provider.cpp b/cpp-generator/experimental/lidl_gen_provider.cpp index bbf8637..11f0630 100644 --- a/cpp-generator/experimental/lidl_gen_provider.cpp +++ b/cpp-generator/experimental/lidl_gen_provider.cpp @@ -448,10 +448,9 @@ QString lidlMakeProviderDispatch(const ModuleDecl& module) s << "#include \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"; diff --git a/cpp-generator/legacy/generator_lib.cpp b/cpp-generator/legacy/generator_lib.cpp index ac0741a..73de3ca 100644 --- a/cpp-generator/legacy/generator_lib.cpp +++ b/cpp-generator/legacy/generator_lib.cpp @@ -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") @@ -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") { diff --git a/cpp-generator/legacy/main.cpp b/cpp-generator/legacy/main.cpp index da75c08..179d478 100644 --- a/cpp-generator/legacy/main.cpp +++ b/cpp-generator/legacy/main.cpp @@ -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"; diff --git a/flake.lock b/flake.lock index bb4f20d..5fde0b7 100644 --- a/flake.lock +++ b/flake.lock @@ -30,11 +30,11 @@ ] }, "locked": { - "lastModified": 1781266151, - "narHash": "sha256-RECe/lpZD7DsZEdwme13XXJEF7lxEqF2+Rjd4CUhW64=", + "lastModified": 1781267326, + "narHash": "sha256-n3CHoCFn16up4q3RieFrcS4GgneTO7wqTFjABo0AGKw=", "owner": "logos-co", "repo": "logos-protocol", - "rev": "176fbc80304b78c385f64eb78af9ec5470c29955", + "rev": "6492494a7c6ee1de531c25aeac86d0ea05f17d94", "type": "github" }, "original": { diff --git a/tests/generator/test_make_header.cpp b/tests/generator/test_make_header.cpp index 7f6a3f9..f9d39a5 100644 --- a/tests/generator/test_make_header.cpp +++ b/tests/generator/test_make_header.cpp @@ -94,8 +94,8 @@ TEST(MakeHeaderTest, ContainsMethodDeclarations) QJsonArray methods = makeTestMethods(); QString h = makeHeader("test_mod", "TestMod", methods); - EXPECT_TRUE(h.contains("int add(int a, int b)")); - EXPECT_TRUE(h.contains("void reset()")); + EXPECT_TRUE(h.contains("int add(int a, int b, logos::CallError* err = nullptr)")); + EXPECT_TRUE(h.contains("void reset(logos::CallError* err = nullptr)")); // Non-invokable should not appear EXPECT_FALSE(h.contains("internal")); } @@ -197,7 +197,7 @@ TEST(MakeHeaderTest, QVariantListAsyncOverload) } QString h = makeHeader("mod", "Mod", methods); - EXPECT_TRUE(h.contains("QVariantList getItems()")); + EXPECT_TRUE(h.contains("QVariantList getItems(logos::CallError* err = nullptr)")); EXPECT_TRUE(h.contains("getItemsAsync(")); EXPECT_TRUE(h.contains("std::function callback")); } @@ -215,7 +215,7 @@ TEST(MakeHeaderTest, QVariantMapAsyncOverload) } QString h = makeHeader("mod", "Mod", methods); - EXPECT_TRUE(h.contains("QVariantMap getData()")); + EXPECT_TRUE(h.contains("QVariantMap getData(logos::CallError* err = nullptr)")); EXPECT_TRUE(h.contains("getDataAsync(")); EXPECT_TRUE(h.contains("std::function callback")); } diff --git a/tests/generator/test_make_source.cpp b/tests/generator/test_make_source.cpp index 3229363..bea7fd1 100644 --- a/tests/generator/test_make_source.cpp +++ b/tests/generator/test_make_source.cpp @@ -44,7 +44,7 @@ TEST(MakeSourceTest, ZeroParams) QJsonArray methods; methods.append(makeMethod("doStuff", "int", 0)); QString src = makeSource("mod", "Mod", "mod.h", methods); - EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"doStuff\")")); + EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"doStuff\", QVariantList{}, Timeout(), &_err)")); EXPECT_TRUE(src.contains("return _result.toInt()")); } @@ -53,7 +53,7 @@ TEST(MakeSourceTest, OneParam) QJsonArray methods; methods.append(makeMethod("fn", "bool", 1)); QString src = makeSource("mod", "Mod", "mod.h", methods); - EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"fn\", p0)")); + EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"fn\", QVariantList{p0}, Timeout(), &_err)")); EXPECT_TRUE(src.contains("return _result.toBool()")); } @@ -62,7 +62,7 @@ TEST(MakeSourceTest, TwoParams) QJsonArray methods; methods.append(makeMethod("fn", "void", 2)); QString src = makeSource("mod", "Mod", "mod.h", methods); - EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"fn\", p0, p1)")); + EXPECT_TRUE(src.contains("m_client->invokeRemoteMethod(\"mod\", \"fn\", QVariantList{p0, p1}, Timeout(), &_err)")); } TEST(MakeSourceTest, ThreeParams)