mirror of
https://github.com/logos-co/logos-test-modules.git
synced 2026-08-31 04:01:16 +00:00
One contract had two providers answering different SHAPES. test_fullapi_ext_rust.lidl declared `echoOptional(v: ?tstr) -> result`; test_fullapi_ext_cpp is header-first and kept deriving `-> ?tstr` from `std::optional<std::string> echoOptional(...)`. Carried as known-ext.json's `ext-optional-return-changed-on-one-provider`, and visible on a second surface as `ext-optional-return-shape-through-the-qt-proxy`. THE WORKAROUND OUTLIVED ITS CAUSE. `-> result` was chosen because `logos-qt-generator --backend consumer` REFUSED `-> ?T` -- null was also how a failed call reported itself on that path, so an empty ?T and a failure were one wire value. That gate is retired: `lidlTypeToQt` maps `?T` to std::optional<T>, so the empty answer is std::nullopt, an inhabitant of its own, and failure travels on logos::CallError, decided by the C ABI return code and never by the value's null-ness. So `-> ?tstr` is emittable today, it is what the C++ provider already derives, and it is what the case table already expects. Reverting the Rust side is what makes the two agree; the alternative (moving C++ to `-> result`) would have meant changing the expectations to match a workaround for a constraint that no longer exists. Changed together, because a proxy that forwards a shape must declare the same one: the Rust LIDL and its impl (`Option<String>` in and out, no success flag standing in for presence), the qtproxy's own interfaces/full_api_ext.lidl, and the qtproxy impl's echoOptional -- which now forwards through sOptStr rather than sResult, and renders with rOpt rather than rResult in the exercise-all and async blocks. Both registry entries are retired. The proxy one had a SECOND, independent half -- a Qt wrapper that could not decode a reply returned a default-constructed LogosResult, and that default is byte-for-byte a provider refusal -- which is fixed in logos-qt-sdk#44 and logos-cpp-sdk#150 and stands whichever shape the contract had chosen. Recorded in the `fixed` entry, because retiring it here without naming that half would credit the shape change with more than it did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
4.0 KiB
Plaintext
90 lines
4.0 KiB
Plaintext
module test_fullapi_ext_rust {
|
|
version "1.0.0"
|
|
description "The composite tail of the conformance matrix: records, bytes at depth, typed maps and nested composites. Deliberately SEPARATE from full_api — several of these types are not yet expressible on the C++ cdylib provider (its typeSupported() gate rejects records and [bstr] by name), and adding them to full_api would break test_fullapi_cpp's build rather than add a test."
|
|
category "testing"
|
|
depends []
|
|
|
|
type Blob {
|
|
id: tstr
|
|
n: uint
|
|
payload: bstr
|
|
}
|
|
|
|
type Wrapper {
|
|
inner: Blob
|
|
tags: [tstr]
|
|
blobs: [Blob]
|
|
}
|
|
|
|
; Optionality. `?T` is TWO-state — a value of T, or empty — never three-state:
|
|
; one LIDL type maps to one type per language, and every target has exactly
|
|
; one empty inhabitant (None / nullopt / invalid QVariant / undefined).
|
|
;
|
|
; BOTH SPELLINGS ON PURPOSE. `? maybe: tstr` (the field flag) and `?tstr` (the
|
|
; type kind, in echoOptional below) are bound to the same meaning by
|
|
; logos-lidl/docs/spec.md:179,184-185 — they must emit byte-identical code,
|
|
; and nothing tested that until these cells existed. They did NOT agree when
|
|
; these cells were written: the Rust dispatch descriptor typed a flag-optional
|
|
; field by its BASE type (so an absent `maybe` was rejected as `expected string
|
|
; at arg0.maybe, got null`) while `?tstr` fell to the catch-all `Ty::Any` (so
|
|
; anything at all was accepted). Same declared meaning, opposite behaviour —
|
|
; which is what these cells were written to catch. They agree now; the cells
|
|
; stay as the guard.
|
|
;
|
|
; `required` is here to hold the other half of the contract down: leniency
|
|
; applies in an OPTIONAL slot only. A required field must still reject both
|
|
; absence and null.
|
|
type Opt {
|
|
required: tstr
|
|
? maybe: tstr
|
|
? count: uint
|
|
? blob: bstr
|
|
}
|
|
|
|
method whoAmI() -> tstr
|
|
|
|
method echoBlob(v: Blob) -> Blob
|
|
method echoWrapper(v: Wrapper) -> Wrapper
|
|
method echoBlobList(v: [Blob]) -> [Blob]
|
|
method echoBlobMap(v: {tstr: Blob}) -> {tstr: Blob}
|
|
|
|
method echoBytesList(v: [bstr]) -> [bstr]
|
|
method echoBytesMap(v: {tstr: bstr}) -> {tstr: bstr}
|
|
method echoIntMap(v: {tstr: int}) -> {tstr: int}
|
|
method echoStringMap(v: {tstr: tstr}) -> {tstr: tstr}
|
|
method echoNestedInts(v: [[int]]) -> [[int]]
|
|
method echoMapOfBytesLists(v: {tstr: [bstr]}) -> {tstr: [bstr]}
|
|
|
|
; Optionality in a record, in a LIST of records (per-element presence must be
|
|
; independent), and as a bare positional slot. The last one is where the wire
|
|
; rule bites: a record field is NAMED, so empty is spelled by omitting the
|
|
; key; a method ARG is POSITIONAL and has no key to omit, so empty is spelled
|
|
; null and the arity never changes.
|
|
;
|
|
; A ?T RETURN was spelled `-> result` here for a while, and this comment used
|
|
; to explain why: `logos-qt-generator --backend consumer` REFUSED `-> ?T`,
|
|
; because null was also how a failed call reported itself on that path, so an
|
|
; empty ?T and a failure were one wire value for every non-Rust caller.
|
|
;
|
|
; THAT GATE IS RETIRED and the reason no longer holds. `lidlTypeToQt` maps
|
|
; `?T` to std::optional<T>, so the empty answer is std::nullopt — a distinct
|
|
; inhabitant, not a null that collides with anything — and the Qt consumer
|
|
; headers this module could not emit before now emit. Failure travels on
|
|
; `logos::CallError`, decided by the C ABI return code and never by the
|
|
; value's null-ness.
|
|
;
|
|
; The workaround outlived its cause, and while it did it cost more than it
|
|
; bought: test_fullapi_ext_cpp is header-first and kept deriving `-> ?tstr`,
|
|
; so ONE contract had two providers answering different SHAPES, which
|
|
; known-ext.json carried as `ext-optional-return-changed-on-one-provider`.
|
|
; `?tstr` is what the other provider already sends and what the case table
|
|
; already expects, so it is the shape both agree on.
|
|
method echoOpt(v: Opt) -> Opt
|
|
method echoOptList(v: [Opt]) -> [Opt]
|
|
method echoOptional(v: ?tstr) -> ?tstr
|
|
|
|
method fireBlobEvent(v: Blob) -> bool
|
|
|
|
event blobEvent(v: Blob)
|
|
}
|