fix(server): a walker answers with reports only; report outside a walk is E1135 - #8950
Open
kashmithnisakya wants to merge 5 commits into
Open
fix(server): a walker answers with reports only; report outside a walk is E1135#8950kashmithnisakya wants to merge 5 commits into
kashmithnisakya wants to merge 5 commits into
Conversation
…ming function returns its generator
…alker/node/edge, stub defaults, one envelope decoder, gateway walker payload, W6010
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #8906.
The contract
A walker's
hasfields are its request andreportis its response. On the wire a walker is therefore its reports and nothing else: theresultslot of a walker spawn is{}, and a walker nested anywhere in a payload serialises to{}. A function answers with its return value inresult, and the only reports beside it are the ones produced by walkers it spawned.reportoutside a walker, node, or edge is now a compile-time error, E1135, so a function cannot report at all. Inside those three archetypes it is valid anywhere, including methods, nested defs, nested objs andimplbodies, since those all run inside a walk. A streaming function returns its generator (return stream();). Adef:pubwhose declared return type is a walker gets W6010, because its typed consumers receive an empty object.Before this change every walker response carried its report list twice, once at
data.reportsand once insidedata.resultthrough the walker's intrinsicreportsfield. A 300-item list walker measured 149,771 bytes, of which 74,782 were the copy. The echo also carried three marker keys and everyhasfield, none of which any client reads: the browser runtime returns the whole envelope and readsdata.reports, and the sv-to-sv stub overwroteinstance.reportsfrom the envelope after rebuilding the instance fromresult.Responses, old and new
All shapes are the
dataslot. The outer{"ok", "type", "data", "error"}envelope and the error envelope are unchanged.1. Walker that reports
POST /walker/Outer {"tag": "t1"}(spawnsNoDeclinside, which reports{"deleted": 5})Old:
{"result": {"_jac_type": "Outer", "_jac_id": "…", "_jac_archetype": "walker", "reports": [{"deleted": 5}, {"outer": "t1"}], "tag": "t1"}, "reports": [{"deleted": 5}, {"outer": "t1"}]}New:
{"result": {}, "reports": [{"deleted": 5}, {"outer": "t1"}]}2. Walker with no
hasfieldsPOST /walker/NoDecl {}Old:
{"result": {"_jac_type": "NoDecl", "_jac_id": "…", "_jac_archetype": "walker", "reports": [{"deleted": 5}]}, "reports": [{"deleted": 5}]}New:
{"result": {}, "reports": [{"deleted": 5}]}3. Walker that never reports
POST /walker/CountThings {"prefix": "t"}(accumulatestotalandnamesin fields)Old:
{"result": {"_jac_type": "CountThings", "_jac_id": "…", "_jac_archetype": "walker", "names": ["t0", "t1", "t2"], "prefix": "t", "reports": [], "total": 3}, "reports": []}New:
{"result": {}, "reports": []}4. Function with a return value
POST /function/list_things {"n": 2}Old and new, unchanged:
{"result": [{"name": "thing-0", "idx": 0}, {"name": "thing-1", "idx": 1}], "reports": []}5. Function that returns nothing
Old and new, unchanged:
{"result": null, "reports": []}6. Function that calls
reportOld:
{"result": {"returned": 2}, "reports": [{"reported": 2}]}New, at compile time:
7. Function that spawns a walker and returns its own value
POST /function/spawn_inside {"n": 1}(the walker doesreport itemswith a one-item list)Old, with the function's own
reportappended:{"result": {"count": 1}, "reports": [[{"name": "thing-0", "idx": 0, "payload": "…"}], {"from_function": true}]}New:
{"result": {"count": 1}, "reports": [[{"name": "thing-0", "idx": 0, "payload": "…"}]]}8. Function that returns the walker it spawned
POST /function/spawn_and_return_walker {"n": 1}Old:
{"result": {"_jac_type": "ListThings", "_jac_id": "…", "_jac_archetype": "walker", "n": 1, "reports": [[{"name": "thing-0", "idx": 0, "payload": "…"}]]}, "reports": [[{"name": "thing-0", "idx": 0, "payload": "…"}]]}New:
{"result": {}, "reports": [[{"name": "thing-0", "idx": 0, "payload": "…"}]]}9. sv-to-sv remote spawn
rg = Greet(name="ada")across a service cutOld: the consumer-side instance was rebuilt from the
resultecho, thenrg.reportswas overwritten from the envelope.New: the instance is built from the fields the caller passed (deep-copied), the provider's literal
hasdefaults for anything left out, and the provider'sreports.rg.name == "ada",rn.body == ""for a defaulted field, andrg.reports == ["hello, ada"]all hold. Field state the provider's walk mutated stays on the provider. With no stub class (the embedded gateway's walker proxy) the helper hands back the{result, reports}payload as is.What changed
jaclang/data/impl/serializer.impl.jac: inapi_modeaWalkerArchetypeserialises to{}. This is the envelope encoder's only caller, so it covers theresultslot, walkers nested in a return value, and walkers insidereports. The'walker'branch of_jac_archetypewas unreachable after that and is gone.diagnostics.jac,type_checker_pass.jac,type_checker_pass.impl.jac: E1135 is one lexical climb,_enclosing_walk_archetype, that stops at the nearest walker, node or edge and followsimpldeclarations on the way, so helpers, nested defs, nested objs and forward-declaredimplchains inside those archetypes stay valid. The old_enclosing_walker_report_elem_typesplit into that climb plus_walker_report_elem_type.boundary_analysis_pass,codeinfo.jac,compiler.impl.jac,jcir_gen_pass.impl.jac: walker bindings now carry the provider's literalhasdefaults, emitted on the stub as__jac_field_defaults__; import facts carry every provider walker name so adef:pubreturning one warns W6010 at the consumer.jaclang/server/sv_client.jac:unwrap_envelope,function_resultandhydrate_walker_envelopeare the one set of sv-to-sv envelope decoders. The core runtime andscale/runtime/rpc/rpc.jaccall them for both the function and walker paths; the three private copies of the unwrap logic are gone.scale/runtime/gateway/embedded_gateway.impl.jac: the walker proxy forwards the provider's{result, reports}payload instead of wrapping the walker underresult.microservices.md,jac-sv-endpoints.md,interop.md,jac-sv-streaming.md(a streamingdefreturns its generator), andjac-scale-http.md(plugin override guidance now names the shared decoders).Tests
tests/runtimelib/test_serve_walker_wire.jaccovers cases 1, 3, 4, 5, 7 and 8 against a live test client.tests/runtimelib/test_sv_walker_hydration.jacexercises the compiler-generated stub classes fromsv_relative_consumer.jac: caller fields, literal defaults, no aliasing of caller arguments, the no-stub payload, and the error envelopes of both decoders.tests/language/test_language.jac: E1135 for a function and for anobjmethod, plus a fixture provingreportreaches the walk from a node method, a nested def, a nested obj and animplchain.tests/compiler/test_typed_interop.jac: W6010 forlaunch -> Probe.scale/tests/microservices/test_microservice.jacreads a caller field, a defaulted field and a report off remote walkers end to end;test_embedded_gateway.jacpins the walker proxy payload.scale/tests/fixtures/cl_fullstack/test_echo.jacwas the one reader ofresult.reportsin the tree and now readsdata.reports.Compatibility
This is a wire-shape change and ships with a
breakingrelease note. Raw REST consumers that readhasfields or marker keys out ofdata.resulton a walker spawn must move toreport. Generated clients are unaffected: the browser runtime already readsdata.reports, and the sv-to-sv stub carries the caller's own arguments plus provider literal defaults. In-processspawnunderjac runis unchanged;w.totalstill works there. Adef:pubthat returns a walker keeps compiling but warns W6010, since typed consumers get an empty object.