behavior-contracts 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/behavior.d.ts +47 -0
  2. package/dist/behavior.d.ts.map +1 -1
  3. package/dist/behavior.js.map +1 -1
  4. package/dist/generator/core.d.ts +1 -1
  5. package/dist/generator/core.d.ts.map +1 -1
  6. package/dist/generator/core.js.map +1 -1
  7. package/dist/generator/emit-straightline-go.d.ts +77 -0
  8. package/dist/generator/emit-straightline-go.d.ts.map +1 -0
  9. package/dist/generator/emit-straightline-go.js +592 -0
  10. package/dist/generator/emit-straightline-go.js.map +1 -0
  11. package/dist/generator/emit-straightline-php.d.ts +46 -0
  12. package/dist/generator/emit-straightline-php.d.ts.map +1 -0
  13. package/dist/generator/emit-straightline-php.js +501 -0
  14. package/dist/generator/emit-straightline-php.js.map +1 -0
  15. package/dist/generator/emit-straightline-python.d.ts +36 -0
  16. package/dist/generator/emit-straightline-python.d.ts.map +1 -0
  17. package/dist/generator/emit-straightline-python.js +441 -0
  18. package/dist/generator/emit-straightline-python.js.map +1 -0
  19. package/dist/generator/emit-straightline-rust.d.ts +78 -0
  20. package/dist/generator/emit-straightline-rust.d.ts.map +1 -0
  21. package/dist/generator/emit-straightline-rust.js +615 -0
  22. package/dist/generator/emit-straightline-rust.js.map +1 -0
  23. package/dist/generator/emit-straightline-typed-go.d.ts +39 -0
  24. package/dist/generator/emit-straightline-typed-go.d.ts.map +1 -0
  25. package/dist/generator/emit-straightline-typed-go.js +828 -0
  26. package/dist/generator/emit-straightline-typed-go.js.map +1 -0
  27. package/dist/generator/emit-straightline-typed-rust.d.ts +48 -0
  28. package/dist/generator/emit-straightline-typed-rust.d.ts.map +1 -0
  29. package/dist/generator/emit-straightline-typed-rust.js +823 -0
  30. package/dist/generator/emit-straightline-typed-rust.js.map +1 -0
  31. package/dist/generator/emit-straightline-typed-typescript.d.ts +49 -0
  32. package/dist/generator/emit-straightline-typed-typescript.d.ts.map +1 -0
  33. package/dist/generator/emit-straightline-typed-typescript.js +314 -0
  34. package/dist/generator/emit-straightline-typed-typescript.js.map +1 -0
  35. package/dist/generator/emit-straightline-typescript.d.ts +36 -0
  36. package/dist/generator/emit-straightline-typescript.d.ts.map +1 -0
  37. package/dist/generator/emit-straightline-typescript.js +397 -0
  38. package/dist/generator/emit-straightline-typescript.js.map +1 -0
  39. package/dist/generator/index.d.ts +13 -0
  40. package/dist/generator/index.d.ts.map +1 -1
  41. package/dist/generator/index.js +39 -0
  42. package/dist/generator/index.js.map +1 -1
  43. package/dist/generator/literal.d.ts +7 -0
  44. package/dist/generator/literal.d.ts.map +1 -1
  45. package/dist/generator/literal.js +41 -0
  46. package/dist/generator/literal.js.map +1 -1
  47. package/dist/generator/straightline.d.ts +147 -0
  48. package/dist/generator/straightline.d.ts.map +1 -0
  49. package/dist/generator/straightline.js +122 -0
  50. package/dist/generator/straightline.js.map +1 -0
  51. package/dist/generator/typed.d.ts +156 -0
  52. package/dist/generator/typed.d.ts.map +1 -0
  53. package/dist/generator/typed.js +108 -0
  54. package/dist/generator/typed.js.map +1 -0
  55. package/dist/guard.d.ts +18 -0
  56. package/dist/guard.d.ts.map +1 -1
  57. package/dist/guard.js +56 -0
  58. package/dist/guard.js.map +1 -1
  59. package/dist/index.d.ts +7 -4
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +10 -2
  62. package/dist/index.js.map +1 -1
  63. package/dist/primitives.d.ts +112 -0
  64. package/dist/primitives.d.ts.map +1 -0
  65. package/dist/primitives.js +206 -0
  66. package/dist/primitives.js.map +1 -0
  67. package/package.json +1 -1
@@ -0,0 +1,828 @@
1
+ import { emitStraightlineModule } from "./straightline.js";
2
+ import { buildTypePlan, deriveTypeRef, findDecl, } from "./typed.js";
3
+ import { goStraightlineEmitter, goStraightlineInternals } from "./emit-straightline-go.js";
4
+ import { buildComponentPlan } from "./straightline.js";
5
+ import { GeneratorFailure } from "./core.js";
6
+ /** runtime package alias(生成コード内)— straight-line と同じ `dslcontracts`。 */
7
+ const PKG = "dslcontracts";
8
+ // ── Go typed materializer(TypedMaterializer seam の Go 具体化 — 拡張 hook 実装)────
9
+ /** 可搬スカラ → Go の具体型名(de-box 後の struct field 型)。int/float は int64/float64 で区別。 */
10
+ function goScalar(scalar) {
11
+ switch (scalar) {
12
+ case "string":
13
+ return "string";
14
+ case "int":
15
+ return "int64";
16
+ case "float":
17
+ return "float64";
18
+ case "bool":
19
+ return "bool";
20
+ case "null":
21
+ // null 単独型は Go に無いので Value(nil のみ格納)。opt でラップされて現れるのが通常。
22
+ return `${PKG}.Value`;
23
+ }
24
+ }
25
+ /** TypeRef を Go の具体型名へ(named→struct 名、arr→slice、opt→ポインタ、scalar→具体型)。 */
26
+ function renderTypeRef(ref) {
27
+ switch (ref.kind) {
28
+ case "scalar":
29
+ return goScalar(ref.scalar);
30
+ case "opt":
31
+ // nullable は Go ポインタで表す(nil = null)。scalar/struct いずれも `*T`。
32
+ return `*${renderTypeRef(ref.inner)}`;
33
+ case "arr":
34
+ return `[]${renderTypeRef(ref.elem)}`;
35
+ case "named":
36
+ return ref.name;
37
+ }
38
+ }
39
+ /** 型プランの named obj を Go struct 宣言へ(decls 順=決定的)。field は宣言順・export 名。gofmt と同じ
40
+ * 列アラインで綴じる(field 名列・型列を最大幅に揃える — 生成物が gofmt clean になるよう)。 */
41
+ function emitTypeDecls(plan) {
42
+ if (plan.decls.length === 0)
43
+ return "";
44
+ return plan.decls
45
+ .map((d) => {
46
+ const names = d.fields.map((f) => goFieldName(f.name));
47
+ const types = d.fields.map((f) => renderTypeRef(f.type));
48
+ const nameW = Math.max(0, ...names.map((n) => n.length));
49
+ const typeW = Math.max(0, ...types.map((t) => t.length));
50
+ const fields = d.fields
51
+ .map((f, i) => `\t${names[i].padEnd(nameW)} ${types[i].padEnd(typeW)} // ${JSON.stringify(f.name)}`)
52
+ .join("\n");
53
+ return `type ${d.name} struct {\n${fields}\n}`;
54
+ })
55
+ .join("\n\n");
56
+ }
57
+ /** wire field 名 → Go の export struct field 名(決定的・一意)。元名は struct コメント/marshaller で保持。 */
58
+ function goFieldName(wire) {
59
+ // 先頭を大文字化して export(Go の struct field 参照可視性)。非識別子文字は `_`。衝突は元名一意で回避
60
+ // されないので、決定的に元名を base64url なしの安全化+先頭大文字で綴じ、marshaller が wire キーで対応づける。
61
+ const safe = wire.replace(/[^A-Za-z0-9_]/g, "_");
62
+ const head = safe.charAt(0);
63
+ const capitalized = /[a-z]/.test(head) ? head.toUpperCase() + safe.slice(1) : "F_" + safe;
64
+ return capitalized;
65
+ }
66
+ /**
67
+ * emitMarshallers — 各 named struct の raw→typed marshaller(§4.4)。動的 Value(`*Obj` 期待)を受け、
68
+ * 各 field を typed に読み出して具体 struct を組む。generic Value 走査を exec 経路から除去する de-box の実体。
69
+ * field 型ごとに再帰: named→marshal_T*、arr→要素ループ、opt→null 分岐、scalar→型 assert。
70
+ */
71
+ function emitMarshallers(plan) {
72
+ if (plan.decls.length === 0)
73
+ return "";
74
+ return plan.decls.map((d) => emitOneMarshaller(d, plan)).join("\n\n");
75
+ }
76
+ function emitOneMarshaller(d, plan) {
77
+ const lines = [];
78
+ lines.push(`func marshal_${d.name}(raw ${PKG}.Value) (${d.name}, error) {`);
79
+ lines.push(`\tvar out ${d.name}`);
80
+ lines.push(`\to, ok := raw.(*${PKG}.Obj)`);
81
+ lines.push(`\tif !ok {`);
82
+ lines.push(`\t\treturn out, ${PKG}.NewExprFailure("TYPE_MISMATCH", "typed marshal ${d.name}: expected obj, got "+${PKG}.TypeName(raw))`);
83
+ lines.push(`\t}`);
84
+ for (const f of d.fields) {
85
+ const goName = goFieldName(f.name);
86
+ const keyLit = JSON.stringify(f.name);
87
+ lines.push(`\t{`);
88
+ lines.push(`\t\tfv, present := o.Get(${keyLit})`);
89
+ lines.push(`\t\tif !present {`);
90
+ lines.push(`\t\t\treturn out, ${PKG}.NewExprFailure("MISSING_PROP", "typed marshal ${d.name}: missing property ."+${keyLit})`);
91
+ lines.push(`\t\t}`);
92
+ lines.push(emitFieldMaterialize(`out.${goName}`, "fv", f.type, plan, 2));
93
+ lines.push(`\t}`);
94
+ }
95
+ lines.push(`\treturn out, nil`);
96
+ lines.push(`}`);
97
+ return lines.join("\n");
98
+ }
99
+ /**
100
+ * emitFieldMaterialize — `target` (typed lvalue) に `src` (Value 式) を型 `ref` で materialize して代入する
101
+ * Go 文ブロックを返す(indent = タブ数)。scalar は型 assert、named は marshal_T* 呼び、arr は要素ループ、
102
+ * opt は null 分岐(ポインタ)。すべて具体 Go 型で組む(de-box)。fail は expr.go と同じ Failure code。
103
+ */
104
+ function emitFieldMaterialize(target, src, ref, plan, indent) {
105
+ const t = "\t".repeat(indent);
106
+ switch (ref.kind) {
107
+ case "scalar": {
108
+ if (ref.scalar === "null") {
109
+ return `${t}${target} = ${src}`;
110
+ }
111
+ const gt = goScalar(ref.scalar);
112
+ const assertType = ref.scalar === "int" ? "int64" : ref.scalar === "float" ? "float64" : gt;
113
+ return [
114
+ `${t}sv, sok := ${src}.(${assertType})`,
115
+ `${t}if !sok {`,
116
+ `${t}\treturn out, ${PKG}.NewExprFailure("TYPE_MISMATCH", "typed marshal: expected ${ref.scalar}, got "+${PKG}.TypeName(${src}))`,
117
+ `${t}}`,
118
+ `${t}${target} = sv`,
119
+ ].join("\n");
120
+ }
121
+ case "named": {
122
+ return [
123
+ `${t}mv, merr := marshal_${ref.name}(${src})`,
124
+ `${t}if merr != nil {`,
125
+ `${t}\treturn out, merr`,
126
+ `${t}}`,
127
+ `${t}${target} = mv`,
128
+ ].join("\n");
129
+ }
130
+ case "arr": {
131
+ const elemGo = renderTypeRef(ref.elem);
132
+ const inner = emitFieldMaterialize("elemOut", "elemRaw", ref.elem, plan, indent + 1);
133
+ return [
134
+ `${t}arr, aok := ${src}.([]${PKG}.Value)`,
135
+ `${t}if !aok {`,
136
+ `${t}\treturn out, ${PKG}.NewExprFailure("TYPE_MISMATCH", "typed marshal: expected arr, got "+${PKG}.TypeName(${src}))`,
137
+ `${t}}`,
138
+ `${t}slice := make([]${elemGo}, 0, len(arr))`,
139
+ `${t}for _, elemRaw := range arr {`,
140
+ `${t}\tvar elemOut ${elemGo}`,
141
+ inner,
142
+ `${t}\tslice = append(slice, elemOut)`,
143
+ `${t}}`,
144
+ `${t}${target} = slice`,
145
+ ].join("\n");
146
+ }
147
+ case "opt": {
148
+ const innerGo = renderTypeRef(ref.inner);
149
+ const inner = emitFieldMaterialize("optOut", src, ref.inner, plan, indent + 1);
150
+ return [
151
+ `${t}if ${src} == nil {`,
152
+ `${t}\t${target} = nil`,
153
+ `${t}} else {`,
154
+ `${t}\tvar optOut ${innerGo}`,
155
+ inner,
156
+ `${t}\t${target} = &optOut`,
157
+ `${t}}`,
158
+ ].join("\n");
159
+ }
160
+ }
161
+ }
162
+ /**
163
+ * materializeExpr — raw な Value 式 `rawExpr` を型 `ref` の具体 typed 値へ変換する **単一式**(exec 経路の
164
+ * de-box)。named→`mustMarshal_T*`(marshaller 経由で具体 struct へ)、scalar→型 assert helper、arr/opt→
165
+ * 具体 slice/pointer helper。呼び側(typed view の output assembly / node materialize)が typed ローカルへ束ねる。
166
+ * これが seam の runtime-materialization hook の Go 実装本体(rust #48 は同型の hook を返す)。
167
+ */
168
+ function materializeExpr(rawExpr, ref, _plan) {
169
+ switch (ref.kind) {
170
+ case "named":
171
+ return `mustMarshal_${ref.name}(${rawExpr})`;
172
+ case "scalar":
173
+ return `mustScalar_${ref.scalar}(${rawExpr})`;
174
+ case "arr":
175
+ return `mustArr_${arrHelperKey(ref)}(${rawExpr})`;
176
+ case "opt":
177
+ return `mustOpt_${arrHelperKey(ref)}(${rawExpr})`;
178
+ }
179
+ }
180
+ /**
181
+ * typedFieldAccess — typed base 式 `baseExpr`(型 `baseRef`)に field path を **struct field 直参照**で辿る。
182
+ * map lookup ではなく `.FieldName` 連結(=de-box されている証拠)。辿り着いた TypeRef も返す。
183
+ */
184
+ function typedFieldAccess(baseExpr, baseRef, fields, plan) {
185
+ let expr = baseExpr;
186
+ let ref = baseRef;
187
+ for (const f of fields) {
188
+ if (ref.kind !== "named") {
189
+ throw new Error(`typed go: field access '.${f}' on non-struct TypeRef ${JSON.stringify(ref)}`);
190
+ }
191
+ const decl = findDecl(plan, ref.name);
192
+ const fld = decl?.fields.find((x) => x.name === f);
193
+ if (!fld)
194
+ throw new Error(`typed go: field '${f}' not found on struct ${ref.name}`);
195
+ expr = `${expr}.${goFieldName(f)}`; // struct field 直参照(map lookup でない)
196
+ ref = fld.type;
197
+ }
198
+ return { expr, ref };
199
+ }
200
+ /**
201
+ * serializeTyped — 具体 typed 値式を canonical 直列化用の動的 Value へ戻す式(観測等価 pin)。exec は
202
+ * typed のまま進み、component 境界でだけ Value 化して golden と突き合わせる。named→ser_T*、scalar は
203
+ * そのまま Value、arr→要素 ser ループ helper、opt→null 分岐 helper。式一発で返せるよう helper を通す。
204
+ */
205
+ function serializeTyped(typedExpr, ref, _plan) {
206
+ switch (ref.kind) {
207
+ case "named":
208
+ return `ser_${ref.name}(${typedExpr})`;
209
+ case "scalar":
210
+ // 具体スカラは Value に代入可能(string/int64/float64/bool は Value=interface{})。
211
+ return `${PKG}.Value(${typedExpr})`;
212
+ case "arr":
213
+ return `serArr(${typedExpr}, func(e ${renderTypeRef(ref.elem)}) ${PKG}.Value { return ${serializeTyped("e", ref.elem, _plan)} })`;
214
+ case "opt":
215
+ return `serOpt(${typedExpr} == nil, func() ${PKG}.Value { return ${serializeTypedOptInner(ref.inner, typedExpr, _plan)} })`;
216
+ }
217
+ }
218
+ /** opt の非 nil 分岐: `*ptr` を deref して inner を serialize。 */
219
+ function serializeTypedOptInner(inner, ptrExpr, plan) {
220
+ return serializeTyped(`(*${ptrExpr})`, inner, plan);
221
+ }
222
+ export const goTypedMaterializer = {
223
+ emitTypeDecls,
224
+ renderTypeRef,
225
+ emitMarshallers,
226
+ materializeExpr,
227
+ typedFieldAccess,
228
+ serializeTyped,
229
+ };
230
+ // ── serializer helper(named struct → Value)を型プランから emit ────────────────────
231
+ /** 各 named struct を canonical Value(*Obj, field は宣言順)へ戻す `ser_T*` + 汎用 arr/opt helper を emit。 */
232
+ function emitSerializers(plan) {
233
+ const perStruct = plan.decls.map((d) => {
234
+ const sets = d.fields
235
+ .map((f) => {
236
+ const val = serializeTyped(`v.${goFieldName(f.name)}`, f.type, plan);
237
+ return `\to.Set(${JSON.stringify(f.name)}, ${val})`;
238
+ })
239
+ .join("\n");
240
+ return `func ser_${d.name}(v ${d.name}) ${PKG}.Value {\n\to := ${PKG}.NewObj()\n${sets}\n\treturn o\n}`;
241
+ });
242
+ // 汎用 arr/opt serialize helper(型パラメタで要素 serializer を受ける)。
243
+ const generic = `func serArr[T any](xs []T, ser func(T) ${PKG}.Value) ${PKG}.Value {
244
+ \tout := make([]${PKG}.Value, 0, len(xs))
245
+ \tfor _, x := range xs {
246
+ \t\tout = append(out, ser(x))
247
+ \t}
248
+ \treturn out
249
+ }
250
+
251
+ func serOpt(isNil bool, ser func() ${PKG}.Value) ${PKG}.Value {
252
+ \tif isNil {
253
+ \t\treturn nil
254
+ \t}
255
+ \treturn ser()
256
+ }`;
257
+ return perStruct.length === 0 ? generic : `${generic}\n\n${perStruct.join("\n\n")}`;
258
+ }
259
+ // ── typed view 関数(実 exec 経路 — 実 de-box)─────────────────────────────────────
260
+ /** component 名 → Go 関数名(straight-line と同規律)。 */
261
+ function sanitize(name) {
262
+ return name.replace(/[^A-Za-z0-9_]/g, "_");
263
+ }
264
+ /** 生成コード内の typed ローカル変数名(materialize 済み struct)。 */
265
+ function typedLocal(nodeId) {
266
+ return `t_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
267
+ }
268
+ /**
269
+ * nodeMaterializerName — scalar/arr/opt を outType に持つ typed node の **fail-closed** 材化関数名。
270
+ * component 名で namespace して module 全体で一意にする(node id は component 間で衝突しうる)。
271
+ */
272
+ function nodeMaterializerName(compName, nodeId) {
273
+ return `materializeNode_${sanitize(compName)}_${nodeId.replace(/[^A-Za-z0-9_]/g, "_")}`;
274
+ }
275
+ /** `ref` の path 配列(Expression IR)を取り出す。単純 ref ノードでなければ undefined。 */
276
+ function refPathOf(node) {
277
+ if (node === null || typeof node !== "object" || Array.isArray(node))
278
+ return undefined;
279
+ const keys = Object.keys(node);
280
+ if (keys.length !== 1 || keys[0] !== "ref")
281
+ return undefined;
282
+ const path = node.ref;
283
+ if (!Array.isArray(path) || !path.every((p) => typeof p === "string"))
284
+ return undefined;
285
+ return path;
286
+ }
287
+ /**
288
+ * emitTypedValue — output(Φ 合流式)IR を、期待型 `expected` の **具体 Go typed 値**式へ再帰 lower し、
289
+ * `{ expr, ref }` を返す。これが AC2 の「exec が具体 struct を保持」の実体:
290
+ * - `{ref:[typedNode, …fields]}` → typed struct field 直参照(typedFieldAccess — map lookup でない)。
291
+ * - `{ref:[input/非 typed node]}` → 型付け不能な生 Value。expected があれば materialize(de-box)。
292
+ * - `{obj:{…}}` → 具体 struct literal(expected が named のとき各 field を期待型で lower)。
293
+ * - `{arr:[…]}` → 具体 slice literal(要素は要素型で lower)。
294
+ * - scalar literal → 具体 Go 値。
295
+ * - 演算子(concat/…)→ straight-line 生値を expected へ materialize(意味論は SSoT が持つ)。
296
+ */
297
+ function emitTypedValue(node, expected, typedNodes, plan) {
298
+ const path = refPathOf(node);
299
+ if (path && path.length >= 1) {
300
+ const head = path[0];
301
+ if (typedNodes.has(head)) {
302
+ // typed node への参照: struct field 直参照で辿る(de-box された struct access)。
303
+ const baseRef = typedNodes.get(head);
304
+ const { expr, ref } = typedFieldAccess(typedLocal(head), baseRef, path.slice(1), plan);
305
+ return { expr, ref };
306
+ }
307
+ // input param 参照(非 typed node): input Value(portScope)を局所的に読み、expected へ materialize
308
+ // (de-box)。これは動的 Value tree の再構築ではなく input 境界値の 1 leaf 読み(許容される局所変換)。
309
+ // bc typed corpus の output は input を直参照しないので通常到達しないが、健全に処理する。
310
+ if (expected)
311
+ return { expr: materializeExpr(scopeRead(path), expected, plan), ref: expected };
312
+ return { expr: scopeRead(path), ref: undefined };
313
+ }
314
+ if (node !== null && typeof node === "object" && !Array.isArray(node)) {
315
+ const keys = Object.keys(node);
316
+ if (keys.length === 1 && keys[0] === "obj" && expected && expected.kind === "named") {
317
+ // 具体 struct literal を組む(de-box した output assembly)。
318
+ const decl = findDecl(plan, expected.name);
319
+ const fields = node.obj;
320
+ const inits = decl.fields.map((f) => {
321
+ const sub = emitTypedValue(fields[f.name], f.type, typedNodes, plan);
322
+ return `${goFieldName(f.name)}: ${sub.expr}`;
323
+ });
324
+ return { expr: `${expected.name}{${inits.join(", ")}}`, ref: expected };
325
+ }
326
+ if (keys.length === 1 && keys[0] === "arr" && expected && expected.kind === "arr") {
327
+ const elems = node.arr;
328
+ const items = elems.map((e) => emitTypedValue(e, expected.elem, typedNodes, plan).expr);
329
+ return { expr: `[]${renderTypeRef(expected.elem)}{${items.join(", ")}}`, ref: expected };
330
+ }
331
+ // concat 演算子: operand を **de-box された typed 値**(typed field access なら concrete string)で
332
+ // 読み、native string 連結する(go-straightline の slConcat と同じ native 化パターン — 意味論保存)。
333
+ // これで scalar-output-via-typed-field-access vector も **具体 struct field を経由した de-box** になる。
334
+ if (keys.length === 1 && keys[0] === "concat" && expected && expected.kind === "scalar" && expected.scalar === "string") {
335
+ const parts = node.concat;
336
+ const partExprs = parts.map((p) => {
337
+ const sub = emitTypedValue(p, { kind: "scalar", scalar: "string" }, typedNodes, plan);
338
+ return sub.expr;
339
+ });
340
+ return { expr: partExprs.join(" + "), ref: expected };
341
+ }
342
+ // その他単一キー = 別演算子(add/eq/…): struct-native exec では動的 Value output tree を組まない方針
343
+ // ゆえ、A0 primitive SSoT を呼ぶ経路(operand の局所 Value 化)を要する。bc typed corpus には output
344
+ // 位置の非 concat 演算子が無いので、ここは **LOUD fail-closed**(silent な Value フォールバックを禁ずる
345
+ // 監査指示に従う)。graphddb #60 / follow-up で primitive-at-boundary 経路を足す。
346
+ if (keys.length === 1) {
347
+ throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go-typed struct-native output: operator '${keys[0]}' at the output position is not supported in the struct-native de-box path yet (typed corpus covers only ref / obj / arr / concat outputs). Do not fall back to building the dynamic Value output tree.`);
348
+ }
349
+ }
350
+ // scalar literal(typed operand として現れる bare リテラル "!"): 具体 Go スカラ。
351
+ if (expected && expected.kind === "scalar") {
352
+ return { expr: litScalar(node, expected.scalar), ref: expected };
353
+ }
354
+ return { expr: litScalarUnknown(node), ref: undefined };
355
+ }
356
+ /** input/非 typed node の scope 読み(動的 Value 式)。slRef 相当のネスト walk を helper で。 */
357
+ function scopeRead(path) {
358
+ // portScope() = input params only(node 結果 *Obj scope は struct-native では存在しない)。
359
+ return `mustInputRef([]string{${path.map((p) => JSON.stringify(p)).join(", ")}}, portScope())`;
360
+ }
361
+ /** 期待スカラの Go リテラル(string/int/float/bool/null)。 */
362
+ function litScalar(node, scalar) {
363
+ if (scalar === "string" && typeof node === "string")
364
+ return JSON.stringify(node);
365
+ if (scalar === "bool" && typeof node === "boolean")
366
+ return node ? "true" : "false";
367
+ if (scalar === "int") {
368
+ if (node !== null && typeof node === "object" && typeof node.int === "string")
369
+ return `int64(${node.int})`;
370
+ if (typeof node === "number")
371
+ return `int64(${node})`;
372
+ }
373
+ if (scalar === "float") {
374
+ if (node !== null && typeof node === "object" && typeof node.float === "number")
375
+ return `float64(${node.float})`;
376
+ if (typeof node === "number")
377
+ return `float64(${node})`;
378
+ }
379
+ if (scalar === "null")
380
+ return "nil";
381
+ // 型が食い違う literal は fail-closed(型プランと不整合)。
382
+ throw new Error(`typed go: scalar literal ${JSON.stringify(node)} does not match expected ${scalar}`);
383
+ }
384
+ function litScalarUnknown(node) {
385
+ if (node === null)
386
+ return "nil";
387
+ if (typeof node === "boolean")
388
+ return node ? "true" : "false";
389
+ if (typeof node === "string")
390
+ return JSON.stringify(node);
391
+ return "nil";
392
+ }
393
+ const PORT_SCOPE = "portScope()";
394
+ /**
395
+ * emitTypedRunner — 1 typed component の **struct-native exec**(bc#47 rework: LAYERED でなく実 de-box)。
396
+ *
397
+ * 監査で不合格だった旧設計(Value-building `run_*` + `scopeCapture` で全ノードの生 Value を `*Obj` に
398
+ * 蓄え、さらに straight-line が `PrimObj(...)` で動的 Value output tree を組み、その後 struct へ変換)を
399
+ * 廃し、**結果を最初から struct 空間で持つ** exec を生成する:
400
+ *
401
+ * - plan/handler-dispatch の SSoT は再利用する(`RunPlan` primitive + `ExecHandler`。意味論は二重実装
402
+ * しない)。ただし RunPlan の exec thunk は、handler 結果(生 Value mock;graphddb #60 では raw)を
403
+ * **その場で outType struct へ marshal**(raw→typed 境界)し、struct を typed scope へ格納する。
404
+ * 生 Value を `*Obj` results scope へ入れることは**しない**。
405
+ * - port 式は従来どおり native/primitive(`slRef`/`slConcat`/`Prim*`)で評価するが、読む scope は
406
+ * **input params だけを持つ portScope()**(bc の port は input のみ参照。上流 typed node を参照する
407
+ * port が来た場合はその struct を局所的に Value 化して読む=operand の局所変換のみ・許容)。
408
+ * - component `output` は typed node struct への **struct field 直参照** + struct/slice literal で
409
+ * 組み、最終 return 境界で **1 回だけ** Value へ serialize する(動的 Value output tree は組まない)。
410
+ * - overflow/短絡 operator は A0 primitive SSoT のまま(意味論非再実装)。ここで struct 空間へ移すのは
411
+ * **結果 materialization(handler 結果・scope・output)だけ**。
412
+ *
413
+ * map を含む typed component は struct-native exec のスコープ外(LOUD fail-closed。graphddb #60 / follow-up)。
414
+ */
415
+ function emitTypedRunner(comp, plan) {
416
+ const fn = sanitize(comp.name);
417
+ // struct-native scope を組むため、port 式は portScope()(input params のみ)で emit する。
418
+ const cplan = buildComponentPlan(comp, goStraightlineInternals.dialect, PORT_SCOPE);
419
+ // typed node(componentRef で outType を持つもの)を index 化。
420
+ const typedNodes = new Map();
421
+ for (const n of comp.body) {
422
+ const ot = n.outType;
423
+ if (ot !== undefined && "component" in n) {
424
+ typedNodes.set(n.id, deriveTypeRef(ot, plan));
425
+ }
426
+ }
427
+ // 各 typed node の struct ローカル宣言(zero 値・producedフラグ)。RunPlan の exec が marshal して埋める。
428
+ const decls = [...typedNodes.entries()]
429
+ .map(([id, ref]) => `\tvar ${typedLocal(id)} ${renderTypeRef(ref)}`)
430
+ .join("\n");
431
+ const arms = cplan.ops.map((op) => emitTypedExecArm(op, comp.name, typedNodes, plan)).join("\n");
432
+ const planLiteral = comp.plan
433
+ ? goStraightlineInternals.emitBehaviorPlanLiteral(comp.plan)
434
+ : `(*${PKG}.ExecutionPlanSpec)(nil)`;
435
+ // output を struct で組み立てて 1 回だけ serialize(動的 Value output tree は組まない)。
436
+ const outRef = comp.outputType !== undefined ? deriveTypeRef(comp.outputType, plan) : undefined;
437
+ const out = emitTypedValue(comp.output, outRef, typedNodes, plan);
438
+ const outRefFinal = out.ref ?? outRef;
439
+ const serialized = outRefFinal ? serializeTyped("__typed_out", outRefFinal, plan) : "__typed_out";
440
+ return `// run_typed_${fn} — STRUCT-NATIVE exec (bc#47 de-box): the plan is driven by the RunPlan primitive
441
+ // (plan SSoT) with per-node thunks that marshal the handler result DIRECTLY into the node's outType
442
+ // struct at the boundary and store the STRUCT in a typed scope — the dynamic Value node-result scope
443
+ // and the PrimObj(...) output tree are NEVER built. Downstream ref/field access reads the structs;
444
+ // the output is assembled as a struct and serialized to a Value ONLY at the final return boundary.
445
+ // Called by Bind for '${comp.name}'. (bc handlers return a Value mock = the raw->typed boundary; in
446
+ // graphddb #60 the handler returns raw and the same marshal-at-boundary keeps it struct-native.)
447
+ func run_typed_${fn}(handlers ${PKG}.ComponentExec, input *${PKG}.Obj) (${PKG}.Value, error) {
448
+ \tops := ${goStraightlineInternals.emitOpsLiteral(cplan)}
449
+ \tif input == nil {
450
+ \t\tinput = ${PKG}.NewObj()
451
+ \t}
452
+ \t// portScope exposes ONLY the input params (a genuine boundary Value). No node-result *Obj scope is
453
+ \t// built: typed node results live in the struct locals below, read via struct field references.
454
+ \tportScope := func() *${PKG}.Obj {
455
+ \t\ts := ${PKG}.NewObj()
456
+ \t\tfor _, k := range input.Keys {
457
+ \t\t\ts.Set(k, input.Vals[k])
458
+ \t\t}
459
+ \t\treturn s
460
+ \t}
461
+ \t_ = portScope
462
+ ${decls ? decls + "\n" : ""}\tvar scopeMu sync.Mutex
463
+ \t_ = scopeMu.Unlock
464
+ \texecErrs := make([]error, len(ops))
465
+ \tabort := func(idx int, err error) ${PKG}.ExecOutcome {
466
+ \t\texecErrs[idx] = err
467
+ \t\treturn ${PKG}.ErrOutcome("aborted")
468
+ \t}
469
+ \texec := func(op ${PKG}.OpSpec, _ ${PKG}.Value) ${PKG}.ExecOutcome {
470
+ \t\tidx := -1
471
+ \t\tfor k := range ops {
472
+ \t\t\tif ops[k].ID == op.ID {
473
+ \t\t\t\tidx = k
474
+ \t\t\t\tbreak
475
+ \t\t\t}
476
+ \t\t}
477
+ \t\ti := idx
478
+ ${arms}
479
+ \t\treturn abort(idx, ${PKG}.NewBehaviorFailure("UNKNOWN_NODE_KIND", "op '"+op.ID+"' has no generated typed exec arm (fail-closed)"))
480
+ \t}
481
+ \tplan := ${planLiteral}
482
+ \trun, err := ${PKG}.RunPlan(plan, ops, exec)
483
+ \tif firstErr := ${PKG}.FirstExecErrInVisitOrder(execErrs, plan, len(ops), err); firstErr != nil {
484
+ \t\treturn nil, firstErr
485
+ \t}
486
+ \tif err != nil {
487
+ \t\treturn nil, err
488
+ \t}
489
+ \t_ = run
490
+ \t__typed_out := ${out.expr}
491
+ \t_ = __typed_out
492
+ \treturn ${serialized}, nil
493
+ }`;
494
+ }
495
+ /**
496
+ * emitTypedExecArm — 1 ノードの struct-native exec 分岐。componentRef: ports を native/primitive で評価
497
+ * (portScope 読み)→ ExecHandler → 結果を **outType struct へ marshal**(raw→typed 境界)→ struct scope に
498
+ * 格納。map/cond は struct-native では未対応(typed corpus は componentRef のみ)— fail-closed。
499
+ */
500
+ function emitTypedExecArm(op, compName, typedNodes, plan) {
501
+ if (op.kind !== "componentRef") {
502
+ // struct-native exec は typed corpus(componentRef のみ)に閉じる。map/cond typed は follow-up(#60)。
503
+ throw new GeneratorFailure("UNSUPPORTED_NODE_STRAIGHTLINE", `go-typed struct-native exec: node kind '${op.kind}' is not supported in a typed component yet (typed corpus is componentRef-only; map/cond typed de-box is deferred to a follow-up). Do not silently fall back to a Value-building path.`);
504
+ }
505
+ const gp = goStraightlineInternals.goPortVar;
506
+ const portEvals = op.ports
507
+ .map((p) => `\t\t\t${gp(p.name)}, err := ${p.expr}
508
+ \t\t\tif err != nil {
509
+ \t\t\t\treturn abort(idx, err)
510
+ \t\t\t}
511
+ \t\t\tports.Set(${JSON.stringify(p.name)}, ${gp(p.name)})`)
512
+ .join("\n");
513
+ const body = op.ports.length === 0 ? "" : `\n${portEvals}`;
514
+ const ref = typedNodes.get(op.id);
515
+ // 結果 materialization: handler 結果を outType struct へ marshal して struct scope へ格納(de-box 境界)。
516
+ // outType 無しの typed component node(純 output-typed)は struct にせず、struct 空間の観点では格納不要。
517
+ let materialize;
518
+ if (ref === undefined) {
519
+ // outType 無し node(typed component だが個々の node には注記が無い): 結果は使われない(output が
520
+ // input/他 node 由来)。struct 化する型が無いので何もしない(Value を scope へ入れることもしない)。
521
+ materialize = "\t\t\t_ = outcome.Ok";
522
+ }
523
+ else if (ref.kind === "named") {
524
+ materialize = `\t\t\tst, merr := marshal_${ref.name}(outcome.Ok)
525
+ \t\t\tif merr != nil {
526
+ \t\t\t\treturn abort(idx, merr)
527
+ \t\t\t}
528
+ \t\t\tscopeMu.Lock()
529
+ \t\t\t${typedLocal(op.id)} = st
530
+ \t\t\tscopeMu.Unlock()`;
531
+ }
532
+ else {
533
+ // scalar/arr/opt outType node: **fail-closed** 材化関数で具体 typed 値へ(marshal と同型の de-box)。
534
+ // 旧実装は must*(型 assert を捨てて zero 値へ silent coerce)だったが、handler が outType 非適合の
535
+ // 値を返した場合に沈黙で潰れる(TYPE_MISMATCH を握り潰す)ため fail-closed に差し替えた(bc#49 B3)。
536
+ // materializeNode_* は emitFieldMaterialize と同じ TYPE_MISMATCH/MISSING_PROP を返す。
537
+ const mfn = nodeMaterializerName(compName, op.id);
538
+ materialize = `\t\t\ttv, merr := ${mfn}(outcome.Ok)
539
+ \t\t\tif merr != nil {
540
+ \t\t\t\treturn abort(idx, merr)
541
+ \t\t\t}
542
+ \t\t\tscopeMu.Lock()
543
+ \t\t\t${typedLocal(op.id)} = tv
544
+ \t\t\tscopeMu.Unlock()`;
545
+ }
546
+ return `\t\tif i == ${op.index} {
547
+ \t\t\tports := ${PKG}.NewObj()${body}
548
+ \t\t\toutcome, resolved := ${PKG}.ExecHandler(handlers, ${JSON.stringify(op.id)}, ${JSON.stringify(op.component)}, ports, nil)
549
+ \t\t\tif !resolved {
550
+ \t\t\t\treturn abort(idx, ${PKG}.NewBehaviorFailure("UNKNOWN_COMPONENT", "component '${op.component}' has no handler (fail-closed)"))
551
+ \t\t\t}
552
+ \t\t\tif outcome.IsError {
553
+ \t\t\t\treturn outcome
554
+ \t\t\t}
555
+ ${materialize}
556
+ \t\t\treturn ${PKG}.OkOutcome(nil)
557
+ \t\t}`;
558
+ }
559
+ /** arr/opt helper 名の決定的キー(要素型で一意化)。 */
560
+ function arrHelperKey(ref) {
561
+ const flat = (r) => {
562
+ switch (r.kind) {
563
+ case "scalar":
564
+ return r.scalar;
565
+ case "named":
566
+ return r.name;
567
+ case "arr":
568
+ return `arr_${flat(r.elem)}`;
569
+ case "opt":
570
+ return `opt_${flat(r.inner)}`;
571
+ }
572
+ };
573
+ return flat(ref);
574
+ }
575
+ // ── module 綴じ(straight-line 本体を scope 露出版で流用 + typed 層)──────────────────
576
+ function hasAnyTypeAnnotation(ctx) {
577
+ for (const comp of ctx.ir.components) {
578
+ if (comp.outputType !== undefined)
579
+ return true;
580
+ for (const n of comp.body)
581
+ if (n.outType !== undefined)
582
+ return true;
583
+ }
584
+ return false;
585
+ }
586
+ /** typed component(typedView 経路で駆動するもの)か。 */
587
+ function isTypedComponent(comp) {
588
+ if (comp.outputType !== undefined)
589
+ return true;
590
+ return comp.body.some((n) => n.outType !== undefined);
591
+ }
592
+ /** must 系 helper(struct-native exec の scalar/arr/opt/named de-box を単一式で使う)を emit。 */
593
+ function emitMustHelpers(plan) {
594
+ const parts = [];
595
+ // mustInputRef — output が input param を直参照する稀な場合に、input Value(portScope)から 1 leaf を
596
+ // 読む(動的 Value tree の再構築ではなく境界値の局所読み)。エラーは型プランが保証するので best-effort。
597
+ parts.push(`func mustInputRef(path []string, scope *${PKG}.Obj) ${PKG}.Value {
598
+ \tif len(path) == 0 {
599
+ \t\treturn nil
600
+ \t}
601
+ \tcur, ok := scope.Get(path[0])
602
+ \tif !ok {
603
+ \t\treturn nil
604
+ \t}
605
+ \tfor _, seg := range path[1:] {
606
+ \t\to, isObj := cur.(*${PKG}.Obj)
607
+ \t\tif !isObj {
608
+ \t\t\treturn nil
609
+ \t\t}
610
+ \t\tnext, present := o.Get(seg)
611
+ \t\tif !present {
612
+ \t\t\treturn nil
613
+ \t\t}
614
+ \t\tcur = next
615
+ \t}
616
+ \treturn cur
617
+ }`);
618
+ // scalar must helper(型 assert → 具体 Go 値。失敗は panic せず sentinel; 実際には型プランが保証)。
619
+ for (const scalar of ["string", "int", "float", "bool", "null"]) {
620
+ if (scalar === "null") {
621
+ parts.push(`func mustScalar_null(v ${PKG}.Value) ${PKG}.Value { return v }`);
622
+ continue;
623
+ }
624
+ const gt = goScalar(scalar);
625
+ const at = scalar === "int" ? "int64" : scalar === "float" ? "float64" : gt;
626
+ parts.push(`func mustScalar_${scalar}(v ${PKG}.Value) ${gt} {
627
+ \ts, _ := v.(${at})
628
+ \treturn s
629
+ }`);
630
+ }
631
+ // named must-marshal(単一式で使う: err は typed view の named node materialize が別途チェックするので
632
+ // ここでは best-effort。exec 経路の de-box は marshal_T* が担う)。
633
+ for (const d of plan.decls) {
634
+ parts.push(`func mustMarshal_${d.name}(v ${PKG}.Value) ${d.name} {
635
+ \tout, _ := marshal_${d.name}(v)
636
+ \treturn out
637
+ }`);
638
+ }
639
+ return parts.join("\n\n");
640
+ }
641
+ /**
642
+ * emitArrOptNodeHelpers — arr/opt を outType に持つ **node**(named でない node)用の materialize helper
643
+ * `mustArr_*` / `mustOpt_*` を、IR に現れる arr/opt node outType 集合から決定的に emit する。要素/inner が
644
+ * named なら marshaller を、scalar なら型 assert を、ネスト arr/opt なら再帰 helper を呼ぶ(具体 slice/ptr へ de-box)。
645
+ */
646
+ function emitArrOptNodeHelpers(ctx, plan) {
647
+ const seen = new Set();
648
+ const parts = [];
649
+ const need = (ref) => {
650
+ const key = arrHelperKey(ref);
651
+ if (ref.kind === "arr") {
652
+ // 要素が arr/opt なら先に要素 helper を用意する(ネスト de-box)。
653
+ if (ref.elem.kind === "arr" || ref.elem.kind === "opt")
654
+ need(ref.elem);
655
+ if (seen.has(`arr_${key}`))
656
+ return;
657
+ seen.add(`arr_${key}`);
658
+ const elemGo = renderTypeRef(ref.elem);
659
+ parts.push(`func mustArr_${key}(v ${PKG}.Value) []${elemGo} {
660
+ \tarr, _ := v.([]${PKG}.Value)
661
+ \tout := make([]${elemGo}, 0, len(arr))
662
+ \tfor _, e := range arr {
663
+ \t\tout = append(out, ${elemMaterializeExpr(ref.elem)})
664
+ \t}
665
+ \treturn out
666
+ }`);
667
+ }
668
+ else if (ref.kind === "opt") {
669
+ if (ref.inner.kind === "arr" || ref.inner.kind === "opt")
670
+ need(ref.inner);
671
+ if (seen.has(`opt_${key}`))
672
+ return;
673
+ seen.add(`opt_${key}`);
674
+ const innerGo = renderTypeRef(ref.inner);
675
+ parts.push(`func mustOpt_${key}(v ${PKG}.Value) *${innerGo} {
676
+ \tif v == nil {
677
+ \t\treturn nil
678
+ \t}
679
+ \tinner := ${elemMaterializeExpr(ref.inner, "v")}
680
+ \treturn &inner
681
+ }`);
682
+ }
683
+ };
684
+ for (const comp of ctx.ir.components) {
685
+ for (const n of comp.body) {
686
+ const ot = n.outType;
687
+ if (ot === undefined || !("component" in n))
688
+ continue;
689
+ const ref = deriveTypeRef(ot, plan);
690
+ if (ref.kind === "arr" || ref.kind === "opt")
691
+ need(ref);
692
+ }
693
+ }
694
+ return parts.join("\n\n");
695
+ }
696
+ /**
697
+ * emitNodeMaterializers — scalar/arr/opt を outType に持つ typed **node** 用の **fail-closed** 材化関数
698
+ * `materializeNode_<comp>_<id>(raw Value) (T, error)` を emit する(named node は marshal_T* を直接使うので不要)。
699
+ * emitFieldMaterialize を再利用し、marshaller と同型の TYPE_MISMATCH/MISSING_PROP で loud reject する
700
+ * (handler が outType 非適合の値を返しても silent-coerce しない — bc#49 B3 の fail-closed 修正の実体)。
701
+ */
702
+ function emitNodeMaterializers(comp, typedNodes, plan) {
703
+ const parts = [];
704
+ for (const [id, ref] of typedNodes.entries()) {
705
+ if (ref.kind === "named")
706
+ continue; // named は marshal_T* が担う
707
+ const ty = renderTypeRef(ref);
708
+ const fn = nodeMaterializerName(comp.name, id);
709
+ const body = emitFieldMaterialize("out", "raw", ref, plan, 1);
710
+ parts.push(`func ${fn}(raw ${PKG}.Value) (${ty}, error) {\n\tvar out ${ty}\n${body}\n\treturn out, nil\n}`);
711
+ }
712
+ return parts.join("\n\n");
713
+ }
714
+ /** arr/opt 要素の materialize 式(named→mustMarshal, scalar→mustScalar, arr/opt→ネスト helper)。 */
715
+ function elemMaterializeExpr(ref, varName = "e") {
716
+ switch (ref.kind) {
717
+ case "named":
718
+ return `mustMarshal_${ref.name}(${varName})`;
719
+ case "scalar":
720
+ return `mustScalar_${ref.scalar}(${varName})`;
721
+ case "arr":
722
+ case "opt":
723
+ return `mustArr_${arrHelperKey(ref)}(${varName})`.replace("mustArr_", ref.kind === "opt" ? "mustOpt_" : "mustArr_");
724
+ }
725
+ }
726
+ function emit(ctx) {
727
+ // go-straightline のモジュール全文を同梱する(untyped component の run_*/Bind = A2 SSoT。byte 不変で
728
+ // 後方互換)。typed component は **struct-native な run_typed_* へ差し替える**(Value-building run_* も
729
+ // scopeCapture も使わない — 監査 rework)。dispatch の先頭に typedDispatch 相談を注入する。
730
+ const straightlineCode = goStraightlineEmitter.emit(ctx);
731
+ if (!hasAnyTypeAnnotation(ctx))
732
+ return straightlineCode;
733
+ const patchedStraightline = injectTypedDispatch(straightlineCode);
734
+ const plan = buildTypePlan(ctx.ir);
735
+ const decls = goTypedMaterializer.emitTypeDecls(plan);
736
+ const marshallers = goTypedMaterializer.emitMarshallers(plan);
737
+ const serializers = emitSerializers(plan);
738
+ const mustHelpers = emitMustHelpers(plan);
739
+ const arrOptNodeHelpers = emitArrOptNodeHelpers(ctx, plan);
740
+ // fail-closed な scalar/arr/opt node 材化関数(bc#49 B3 — exec 経路の silent-coerce 修正)。
741
+ const nodeMaterializers = ctx.ir.components
742
+ .filter((c) => isTypedComponent(c))
743
+ .map((c) => {
744
+ const typedNodes = new Map();
745
+ for (const n of c.body) {
746
+ const ot = n.outType;
747
+ if (ot !== undefined && "component" in n)
748
+ typedNodes.set(n.id, deriveTypeRef(ot, plan));
749
+ }
750
+ return emitNodeMaterializers(c, typedNodes, plan);
751
+ })
752
+ .filter((s) => s.length > 0)
753
+ .join("\n\n");
754
+ const runners = ctx.ir.components
755
+ .filter((c) => isTypedComponent(c))
756
+ .map((c) => emitTypedRunner(c, plan))
757
+ .join("\n\n");
758
+ const typedDispatch = emitTypedDispatch(ctx);
759
+ // gofmt-clean のため、非空セクションだけを 1 空行区切りで綴じる(空セクションで二重空行を出さない)。
760
+ const banner = `// ── typed layer (bc#47 / Layer B2 — Go REAL de-box, STRUCT-NATIVE exec) ─────────────
761
+ // Unlike the TS oracle (type erasure, no runtime de-box), the Go typed layer executes typed
762
+ // components in STRUCT space: run_typed_* drives the plan with the RunPlan primitive (plan SSoT)
763
+ // and per-node thunks that marshal the handler result DIRECTLY into the node's outType struct at
764
+ // the boundary (raw->typed) and store the STRUCT in a typed scope. The dynamic Value node-result
765
+ // *Obj scope and the PrimObj(...) output tree are NEVER built. ref/field access reads the structs;
766
+ // the output is assembled as a struct and serialized to a Value ONLY at the final return boundary.
767
+ // Bind routes typed components here; untyped components/IR stay byte-identical to go-straightline.`;
768
+ const sections = [
769
+ banner,
770
+ decls || undefined,
771
+ marshallers ? `// raw -> typed marshallers (§4.4): de-box a dynamic Value into a concrete struct, field by field.\n${marshallers}` : undefined,
772
+ `// typed -> Value serializers (final return boundary only): canonical observation for the equivalence pin.\n${serializers}`,
773
+ `// must-form helpers used inside the single-expression struct materialization / output assembly.\n${mustHelpers}`,
774
+ arrOptNodeHelpers ? `// arr/opt node de-box helpers (concrete slice/pointer materialization).\n${arrOptNodeHelpers}` : undefined,
775
+ nodeMaterializers ? `// scalar/arr/opt node materializers: FAIL-CLOSED de-box (TYPE_MISMATCH/MISSING_PROP) for typed nodes whose outType is not a named obj.\n${nodeMaterializers}` : undefined,
776
+ `// struct-native typed runners: the REAL de-box exec path (called by Bind for typed components).\n${runners}`,
777
+ typedDispatch,
778
+ ];
779
+ const typedBody = sections.filter((s) => !!s).join("\n\n");
780
+ // straight-line 本体は `}\n` で終わる。空行 1 つを挟んで typed 層、末尾は改行 1 つ(gofmt)。
781
+ return `${patchedStraightline.replace(/\n+$/, "\n")}\n${typedBody}\n`;
782
+ }
783
+ /**
784
+ * injectTypedDispatch — go-straightline の `func dispatch(...)` 本体先頭に typedDispatch 相談を挿入する。
785
+ * typed component は struct-native de-box 経路へ、未 typed は straight-line の run_* へフォールバックする。
786
+ * dispatch 本体だけを触り、IR リテラル(fingerprint 対象)・run_*・helper は byte 不変(後方互換の骨組みは保つ)。
787
+ */
788
+ function injectTypedDispatch(code) {
789
+ const marker = `func dispatch(name string, handlers ${PKG}.ComponentExec, input *${PKG}.Obj) (${PKG}.Value, error) {\n`;
790
+ const at = code.indexOf(marker);
791
+ if (at < 0)
792
+ throw new Error("go-typed emit: could not find the straight-line dispatch function to make typed-aware");
793
+ const insertAt = at + marker.length;
794
+ const inject = `\tif v, err, handled := typedDispatch(name, handlers, input); handled {\n\t\treturn v, err\n\t}\n`;
795
+ return code.slice(0, insertAt) + inject + code.slice(insertAt);
796
+ }
797
+ /** typed dispatch: Bind が typed component を struct-native run_typed_* で駆動する(実 de-box)。 */
798
+ function emitTypedDispatch(ctx) {
799
+ const arms = ctx.ir.components
800
+ .filter((c) => isTypedComponent(c))
801
+ .map((c) => {
802
+ const fn = sanitize(c.name);
803
+ return `\tif name == ${JSON.stringify(c.name)} {
804
+ \t\tv, err := run_typed_${fn}(handlers, input)
805
+ \t\treturn v, err, true
806
+ \t}`;
807
+ })
808
+ .join("\n");
809
+ return `// typedDispatch is consulted by Bind BEFORE the straight-line dispatch: typed components run through
810
+ // the struct-native de-boxed runner; untyped components fall through to the straight-line path unchanged.
811
+ func typedDispatch(name string, handlers ${PKG}.ComponentExec, input *${PKG}.Obj) (${PKG}.Value, error, bool) {
812
+ ${arms}
813
+ \treturn nil, nil, false
814
+ }`;
815
+ }
816
+ /**
817
+ * goTypedEmitter — 言語識別子 `go-typed` の emitter plugin(bc#47 B2, 実 de-box)。
818
+ * go-straightline 本体(byte 同一)+ typed 層(struct/marshaller/serializer/typedView)。
819
+ * `outType` 無し IR は go-straightline と byte-identical(後方互換)。
820
+ */
821
+ export const goTypedEmitter = {
822
+ language: "go-typed",
823
+ fileExtension: ".go",
824
+ defaultRuntimeImport: "github.com/foo-ogawa/behavior-contracts/go",
825
+ filenameHint: "behaviors.typed.generated.go",
826
+ emit,
827
+ };
828
+ //# sourceMappingURL=emit-straightline-typed-go.js.map