~bigbes/tarantool

tarantool-protobuf

ref: 06b2978a0f71c67829bbb516ecec6edf32f78667 tarantool-protobuf/cmd/protoc-gen-tarantool/internal/gen/inline.go -rw-r--r-- 26.5 KiB
06b2978a — Eugene Blikh codegen: resolve (tarantool.lua_package) via global type registry 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
package gen

import (
	"fmt"
	"strings"

	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/reflect/protoreflect"
)

// emitInlineMessage emits per-message _new / _encode / _decode functions with
// no descriptor dispatch. Tag bytes are precomputed as Lua string literals;
// each scalar field's encode/decode call resolves to one wire.<typed> call.
func emitInlineMessage(w *writer, file *protogen.File, m *protogen.Message, imports map[string]string, prefix string) {
	name := luaTypeName(m.Desc.FullName(), file.Desc.Package())
	full := emmyMessageFullName(m)
	selfPath := luaPackagePath(file.Desc, prefix)

	emitEmmyWrapperAnnotations(w, name, full, wrapperNew)
	w.line("function M.%s_new(t) return t or {} end", name)
	w.line("")

	emitInlineEncode(w, name, m, file, selfPath, imports, prefix)
	emitInlineDecode(w, name, m, file, selfPath, imports, prefix)
	emitEmmyWrapperAnnotations(w, name, full, wrapperDecodeLazy)
	w.line("function M.%s_decode_lazy(b) return pb.decode_lazy(M.%s_descriptor, b) end", name, name)
	emitEmmyWrapperAnnotations(w, name, full, wrapperText)
	w.line("function M.%s_text(t, opts) return pb.text.encode(M.%s_descriptor, t, opts) end", name, name)
	emitOptionalAccessors(w, name, m, full)
	w.line("")
}

func emitInlineEncode(w *writer, name string, m *protogen.Message, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	emitEmmyWrapperAnnotations(w, name, emmyMessageFullName(m), wrapperEncode)
	w.line("function M.%s_encode(t)", name)
	w.line("    if type(t) ~= 'table' then")
	w.line("        error(\"expected table for %s, got \" .. type(t), 0)", m.Desc.FullName())
	w.line("    end")
	w.line("    local out, n = {}, 0")
	w.line("    local v")

	// Oneof pre-pass: pick the active branch per oneof (last-set wins).
	for _, oo := range realOneofs(m) {
		w.line("    local %s", oneofVar(string(oo.Desc.Name())))
		for _, f := range oo.Fields {
			w.line("    if t.%s ~= nil then %s = %q end",
				string(f.Desc.Name()),
				oneofVar(string(oo.Desc.Name())),
				string(f.Desc.Name()))
		}
	}

	for _, f := range m.Fields {
		emitInlineEncodeField(w, f, file, selfPath, imports, prefix)
	}
	// Preserve unknown fields captured at decode time.
	w.line("    local _uf = t._unknown_fields")
	w.line("    if _uf ~= nil and _uf ~= '' then n = n + 1; out[n] = _uf end")
	w.line("    return table.concat(out)")
	w.line("end")
	w.line("")
}

// realOneofs returns the message's non-synthetic oneofs (skips the ones
// proto3 expands explicit `optional` into).
func realOneofs(m *protogen.Message) []*protogen.Oneof {
	var out []*protogen.Oneof
	for _, oo := range m.Oneofs {
		if oo.Fields[0].Desc.HasOptionalKeyword() {
			continue
		}
		out = append(out, oo)
	}
	return out
}

func oneofVar(name string) string { return "_of_" + name }

// fieldRealOneof returns the oneof name a field belongs to, or "" if the
// field is not in a real oneof (i.e. either standalone or in a synthetic
// proto3 explicit-optional oneof).
func fieldRealOneof(f *protogen.Field) string {
	if f.Oneof == nil || f.Desc.HasOptionalKeyword() {
		return ""
	}
	return string(f.Oneof.Desc.Name())
}

func emitInlineEncodeField(w *writer, f *protogen.Field, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	id := int32(f.Desc.Number())
	tag := tagBytesLit(id, wireTypeForField(f))
	fname := string(f.Desc.Name())

	w.line("    -- field %d: %s", id, fname)
	w.line("    v = t.%s", fname)

	oneof := fieldRealOneof(f)
	// Default presence gate: a regular nil check. For message fields we
	// also need to accept box.NULL (which == nil via Tarantool's cdata
	// metamethod) because google.protobuf.Value uses it as the canonical
	// null_value sentinel.
	gate := "v ~= nil"
	if oneof != "" {
		gate = fmt.Sprintf("%s == %q", oneofVar(oneof), fname)
	} else if f.Message != nil {
		gate = "v ~= nil or type(v) == 'cdata'"
	}
	// Explicit-optional fields: presence is meaningful, no default elision.
	hasPresence := oneof != "" || f.Desc.HasOptionalKeyword()

	switch {
	case f.Desc.IsMap():
		emitInlineEncodeMap(w, f, tag, file, selfPath, imports, prefix)
	case f.Desc.IsList():
		emitInlineEncodeRepeated(w, f, tag, fname, file, selfPath, imports, prefix)
	case f.Message != nil:
		// Split the length-delimited payload into separate `out` slots:
		// emit the tag, then `varint(#body)`, then the body. This avoids
		// the per-field `varint(#body) .. body` string concatenation that
		// `wire.encode_len` would do. `table.concat` at the end of
		// `_encode` joins everything in one pass — same final bytes,
		// one fewer allocation per nested-message field.
		ref := typeRef(file, f.Message.Desc, selfPath, imports, "_encode", prefix)
		w.line("    if %s then", gate)
		w.line("        local _b = %s(v)", ref)
		w.line("        n = n + 1; out[n] = %s", tag)
		w.line("        n = n + 1; out[n] = wire.encode_varint(#_b)")
		w.line("        n = n + 1; out[n] = _b")
		w.line("    end")
	case f.Enum != nil:
		enumLocal := typeRef(file, f.Enum.Desc, selfPath, imports, "", prefix)
		fullName := string(f.Enum.Desc.FullName())
		// Presence (oneof or optional): emit even when value is the enum default.
		w.line("    if %s then", gate)
		w.line("        local nv = v")
		w.line("        if type(v) == 'string' then")
		w.line("            nv = %s[v]", enumLocal)
		w.line("            if nv == nil then error(\"unknown enum value '\" .. v .. \"' for %s\", 0) end", fullName)
		w.line("        end")
		if !hasPresence {
			w.line("        if nv ~= 0 then")
			w.line("            n = n + 1; out[n] = %s", tag)
			w.line("            n = n + 1; out[n] = wire.encode_int32(nv)")
			w.line("        end")
		} else {
			w.line("        n = n + 1; out[n] = %s", tag)
			w.line("        n = n + 1; out[n] = wire.encode_int32(nv)")
		}
		w.line("    end")
	default:
		st := scalarName(f.Desc.Kind())
		if st == "" {
			panic("unhandled scalar kind: " + f.Desc.Kind().String())
		}
		if !hasPresence {
			w.line("    if v ~= nil and %s then", scalarNotDefaultExpr(st, "v"))
		} else {
			w.line("    if %s then", gate)
		}
		if st == "string" || st == "bytes" {
			// Length-delimited scalar: same split rationale as nested
			// messages above. `encode_string` / `encode_bytes` would
			// concatenate the length prefix and body — emit them as
			// separate `out` slots instead and let table.concat join.
			w.line("        n = n + 1; out[n] = %s", tag)
			w.line("        n = n + 1; out[n] = wire.encode_varint(#v)")
			w.line("        n = n + 1; out[n] = v")
		} else {
			w.line("        n = n + 1; out[n] = %s", tag)
			w.line("        n = n + 1; out[n] = wire.encode_%s(v)", st)
		}
		w.line("    end")
	}
}

func emitInlineEncodeRepeated(w *writer, f *protogen.Field, tag, fname string, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	switch {
	case f.Message != nil:
		ref := typeRef(file, f.Message.Desc, selfPath, imports, "_encode", prefix)
		w.line("    if v ~= nil and #v > 0 then")
		w.line("        local _tag = %s", tag)
		w.line("        for _i = 1, #v do")
		w.line("            local _b = %s(v[_i])", ref)
		w.line("            n = n + 1; out[n] = _tag")
		w.line("            n = n + 1; out[n] = wire.encode_varint(#_b)")
		w.line("            n = n + 1; out[n] = _b")
		w.line("        end")
		w.line("    end")
	case f.Enum != nil:
		enumLocal := typeRef(file, f.Enum.Desc, selfPath, imports, "", prefix)
		fullName := string(f.Enum.Desc.FullName())
		w.line("    if v ~= nil and #v > 0 then")
		w.line("        local parts, m = {}, 0")
		w.line("        for _i = 1, #v do")
		w.line("            local elem = v[_i]")
		w.line("            local nv = elem")
		w.line("            if type(elem) == 'string' then")
		w.line("                nv = %s[elem]", enumLocal)
		w.line("                if nv == nil then error(\"unknown enum value '\" .. elem .. \"' for %s\", 0) end", fullName)
		w.line("            end")
		w.line("            m = m + 1; parts[m] = wire.encode_int32(nv)")
		w.line("        end")
		w.line("        local _b = table.concat(parts)")
		w.line("        n = n + 1; out[n] = %s", tag)
		w.line("        n = n + 1; out[n] = wire.encode_varint(#_b)")
		w.line("        n = n + 1; out[n] = _b")
		w.line("    end")
	default:
		st := scalarName(f.Desc.Kind())
		packable := st != "string" && st != "bytes"
		if packable && f.Desc.IsPacked() {
			w.line("    if v ~= nil and #v > 0 then")
			w.line("        local parts, m = {}, 0")
			w.line("        for _i = 1, #v do")
			w.line("            m = m + 1; parts[m] = wire.encode_%s(v[_i])", st)
			w.line("        end")
			w.line("        local _b = table.concat(parts)")
			w.line("        n = n + 1; out[n] = %s", tag)
			w.line("        n = n + 1; out[n] = wire.encode_varint(#_b)")
			w.line("        n = n + 1; out[n] = _b")
			w.line("    end")
		} else if st == "string" || st == "bytes" {
			w.line("    if v ~= nil and #v > 0 then")
			w.line("        local _tag = %s", tag)
			w.line("        for _i = 1, #v do")
			w.line("            local _b = v[_i]")
			w.line("            n = n + 1; out[n] = _tag")
			w.line("            n = n + 1; out[n] = wire.encode_varint(#_b)")
			w.line("            n = n + 1; out[n] = _b")
			w.line("        end")
			w.line("    end")
		} else {
			w.line("    if v ~= nil and #v > 0 then")
			w.line("        local _tag = %s", tag)
			w.line("        for _i = 1, #v do")
			w.line("            n = n + 1; out[n] = _tag")
			w.line("            n = n + 1; out[n] = wire.encode_%s(v[_i])", st)
			w.line("        end")
			w.line("    end")
		}
	}
}

func emitInlineDecode(w *writer, name string, m *protogen.Message, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	emitEmmyWrapperAnnotations(w, name, emmyMessageFullName(m), wrapperDecode)
	w.line("function M.%s_decode(buf)", name)
	w.line("    if type(buf) ~= 'string' then")
	w.line("        error(\"expected string for %s decode, got \" .. type(buf), 0)", m.Desc.FullName())
	w.line("    end")
	w.line("    local result = {}")
	w.line("    local pos, len = 1, #buf")
	w.line("    local _uf")
	w.line("    while pos <= len do")
	w.line("        local _tag_start = pos")
	w.line("        local id, wt")
	w.line("        id, wt, pos = wire.decode_tag(buf, pos)")

	first := true
	for _, f := range m.Fields {
		op := "elseif"
		if first {
			op = "if"
			first = false
		}
		w.line("        %s id == %d then", op, f.Desc.Number())
		emitInlineDecodeFieldBody(w, f, file, selfPath, imports, prefix)
	}
	if first {
		// No fields — every wire byte is unknown.
		w.line("        if true then")
	}
	w.line("        else")
	w.line("            pos = wire.skip_field(buf, pos, wt, id)")
	w.line("            if _uf == nil then _uf = {} end")
	w.line("            _uf[#_uf + 1] = buf:sub(_tag_start, pos - 1)")
	w.line("        end")
	w.line("    end")
	w.line("    if _uf ~= nil then result._unknown_fields = table.concat(_uf) end")
	w.line("    return result")
	w.line("end")
	w.line("")
}

func emitInlineDecodeFieldBody(w *writer, f *protogen.Field, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	fname := string(f.Desc.Name())
	switch {
	case f.Desc.IsMap():
		emitInlineDecodeMap(w, f, fname, file, selfPath, imports, prefix)
	case f.Desc.IsList():
		emitInlineDecodeRepeated(w, f, fname, file, selfPath, imports, prefix)
	case f.Message != nil:
		ref := typeRef(file, f.Message.Desc, selfPath, imports, "_decode", prefix)
		w.line("            local payload")
		w.line("            payload, pos = wire.decode_len(buf, pos)")
		if isWellKnownTypeFile(f.Message.Desc.ParentFile()) {
			// WKT decoders return unwrapped values (datetime, number, string),
			// not Lua tables — there is nothing to merge into. Replace.
			w.line("            result.%s = %s(payload)", fname, ref)
		} else {
			// Per proto3 spec, repeated occurrences of a singular message
			// field merge recursively. This holds for oneof branches too;
			// sibling clearing below enforces oneof exclusivity.
			descRef := typeRef(file, f.Message.Desc, selfPath, imports, "_descriptor", prefix)
			w.line("            local prev = result.%s", fname)
			w.line("            if prev == nil then")
			w.line("                result.%s = %s(payload)", fname, ref)
			w.line("            else")
			w.line("                pb.codec.merge_message(%s, prev, %s(payload))",
				descRef, ref)
			w.line("            end")
		}
	case f.Enum != nil:
		w.line("            local u")
		w.line("            u, pos = wire.decode_varint(buf, pos)")
		w.line("            result.%s = wire.varint_to_int32(u)", fname)
	default:
		st := scalarName(f.Desc.Kind())
		w.line("            local val")
		w.line("            val, pos = wire.decode_%s(buf, pos)", st)
		w.line("            result.%s = val", fname)
	}

	// Oneof: clear sibling branches so callers see exactly one set field.
	if oneof := fieldRealOneof(f); oneof != "" {
		for _, sib := range f.Oneof.Fields {
			if sib == f {
				continue
			}
			w.line("            result.%s = nil", string(sib.Desc.Name()))
		}
	}
}

func emitInlineDecodeRepeated(w *writer, f *protogen.Field, fname string, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	w.line("            local list = result.%s", fname)
	w.line("            if list == nil then list = {}; result.%s = list end", fname)

	switch {
	case f.Message != nil:
		ref := typeRef(file, f.Message.Desc, selfPath, imports, "_decode", prefix)
		w.line("            local payload")
		w.line("            payload, pos = wire.decode_len(buf, pos)")
		w.line("            list[#list + 1] = %s(payload)", ref)
	case f.Enum != nil:
		// Enums are packable (proto3 default). Accept both packed and per-element.
		w.line("            if wt == 2 then")
		w.line("                local payload")
		w.line("                payload, pos = wire.decode_len(buf, pos)")
		w.line("                local p2, lim = 1, #payload")
		w.line("                while p2 <= lim do")
		w.line("                    local u")
		w.line("                    u, p2 = wire.decode_varint(payload, p2)")
		w.line("                    list[#list + 1] = wire.varint_to_int32(u)")
		w.line("                end")
		w.line("            else")
		w.line("                local u")
		w.line("                u, pos = wire.decode_varint(buf, pos)")
		w.line("                list[#list + 1] = wire.varint_to_int32(u)")
		w.line("            end")
	default:
		st := scalarName(f.Desc.Kind())
		packable := st != "string" && st != "bytes"
		if packable {
			w.line("            if wt == 2 then")
			w.line("                local payload")
			w.line("                payload, pos = wire.decode_len(buf, pos)")
			w.line("                local p2, lim = 1, #payload")
			w.line("                while p2 <= lim do")
			w.line("                    local val")
			w.line("                    val, p2 = wire.decode_%s(payload, p2)", st)
			w.line("                    list[#list + 1] = val")
			w.line("                end")
			w.line("            else")
			w.line("                local val")
			w.line("                val, pos = wire.decode_%s(buf, pos)", st)
			w.line("                list[#list + 1] = val")
			w.line("            end")
		} else {
			w.line("            local val")
			w.line("            val, pos = wire.decode_%s(buf, pos)", st)
			w.line("            list[#list + 1] = val")
		}
	}
}

// ----------------------------------------------------------------------------
// Map field codegen
// ----------------------------------------------------------------------------

// emitInlineEncodeMap emits the encode block for a map<K,V> field. Map fields
// are wire-equivalent to `repeated <Field>Entry`, where the synthetic Entry
// message has key=field 1 and value=field 2.
func emitInlineEncodeMap(w *writer, f *protogen.Field, tag string, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	keyF, valF := f.Message.Fields[0], f.Message.Fields[1]

	keyTag := tagBytesLit(1, mapSubFieldWireType(keyF))
	valTag := tagBytesLit(2, mapSubFieldWireType(valF))

	w.line("    if v ~= nil and next(v) ~= nil then")
	w.line("        local _tag, _ktag, _vtag = %s, %s, %s", tag, keyTag, valTag)
	w.line("        for _k, _val in pairs(v) do")
	w.line("            local entry, _m = {}, 0")

	// Key emit
	keyDef := mapKeyDefaultExpr(keyF)
	w.line("            if _k ~= %s then", keyDef)
	emitMapPiece(w, "entry", "_m", "_ktag", "_k", keyF, file, selfPath, imports, prefix)
	w.line("            end")

	// Value emit
	emitMapValueGuard(w, valF, "_val")
	emitMapPiece(w, "entry", "_m", "_vtag", "_val", valF, file, selfPath, imports, prefix)
	w.line("            end")

	w.line("            n = n + 1; out[n] = _tag")
	w.line("            n = n + 1; out[n] = wire.encode_len(table.concat(entry))")
	w.line("        end")
	w.line("    end")
}

// emitMapPiece emits the two-line append:
//
//	<list>[<idx> + 1] = <tag>; <list>[<idx> + 2] = wire.encode_<typed>(<expr>)
//
// (or the message form). Increments <idx> by 2 in two separate statements.
func emitMapPiece(w *writer, list, idx, tag, valExpr string, f *protogen.Field, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	switch {
	case f.Message != nil:
		ref := typeRef(file, f.Message.Desc, selfPath, imports, "_encode", prefix)
		w.line("                %s = %s + 1; %s[%s] = %s",
			idx, idx, list, idx, tag)
		w.line("                %s = %s + 1; %s[%s] = wire.encode_len(%s(%s))",
			idx, idx, list, idx, ref, valExpr)
	case f.Enum != nil:
		// For enum value: input may be string name; resolve.
		enumLocal := typeRef(file, f.Enum.Desc, selfPath, imports, "", prefix)
		fullName := string(f.Enum.Desc.FullName())
		w.line("                local _nv = %s", valExpr)
		w.line("                if type(%s) == 'string' then", valExpr)
		w.line("                    _nv = %s[%s]", enumLocal, valExpr)
		w.line("                    if _nv == nil then error(\"unknown enum value '\" .. %s .. \"' for %s\", 0) end",
			valExpr, fullName)
		w.line("                end")
		w.line("                %s = %s + 1; %s[%s] = %s", idx, idx, list, idx, tag)
		w.line("                %s = %s + 1; %s[%s] = wire.encode_int32(_nv)", idx, idx, list, idx)
	default:
		st := scalarName(f.Desc.Kind())
		w.line("                %s = %s + 1; %s[%s] = %s", idx, idx, list, idx, tag)
		w.line("                %s = %s + 1; %s[%s] = wire.encode_%s(%s)",
			idx, idx, list, idx, st, valExpr)
	}
}

// emitMapValueGuard emits an `if <not-default> then` guarding the value emit
// for default-elision in map entries. Closing `end` is the caller's job.
func emitMapValueGuard(w *writer, f *protogen.Field, valExpr string) {
	switch {
	case f.Message != nil:
		// Messages have no notion of "default value" elision in this position;
		// emit unconditionally (nil is excluded by the outer iteration anyway).
		w.line("            if %s ~= nil then", valExpr)
	case f.Enum != nil:
		// Need to resolve string -> int first, but we delay that. The cheap
		// guard here is only for default elision, so check `~= 0` once it's
		// been resolved. To keep things simple, always emit and let the proto
		// receiver re-resolve to default. (Defaults round-trip correctly.)
		w.line("            do")
	default:
		st := scalarName(f.Desc.Kind())
		w.line("            if %s then", scalarNotDefaultExpr(st, valExpr))
	}
}

// mapKeyDefaultExpr returns the Lua literal for the proto3 default of a map key.
// Only string + integer + bool keys are valid in maps.
func mapKeyDefaultExpr(f *protogen.Field) string {
	switch f.Desc.Kind() {
	case protoreflect.StringKind:
		return "''"
	case protoreflect.BoolKind:
		return "false"
	}
	return "0"
}

// mapSubFieldWireType returns the wire type for a map entry's key or value
// (both are singular and never packed).
func mapSubFieldWireType(f *protogen.Field) int {
	switch {
	case f.Message != nil:
		return 2 // LEN
	case f.Enum != nil:
		return 0 // VARINT
	}
	switch f.Desc.Kind() {
	case protoreflect.Int32Kind, protoreflect.Int64Kind,
		protoreflect.Uint32Kind, protoreflect.Uint64Kind,
		protoreflect.Sint32Kind, protoreflect.Sint64Kind,
		protoreflect.BoolKind:
		return 0 // VARINT
	case protoreflect.Fixed32Kind, protoreflect.Sfixed32Kind, protoreflect.FloatKind:
		return 5 // I32
	case protoreflect.Fixed64Kind, protoreflect.Sfixed64Kind, protoreflect.DoubleKind:
		return 1 // I64
	case protoreflect.StringKind, protoreflect.BytesKind:
		return 2 // LEN
	}
	panic("unhandled map sub-field kind: " + f.Desc.Kind().String())
}

// emitInlineDecodeMap emits the decode block for a map<K,V> field.
func emitInlineDecodeMap(w *writer, f *protogen.Field, fname string, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	keyF, valF := f.Message.Fields[0], f.Message.Fields[1]

	w.line("            local map = result.%s", fname)
	w.line("            if map == nil then map = {}; result.%s = map end", fname)
	w.line("            local payload")
	w.line("            payload, pos = wire.decode_len(buf, pos)")
	w.line("            local _ep, _elim = 1, #payload")
	w.line("            local _key, _val = %s, %s",
		mapDefaultExpr(keyF), mapDefaultExpr(valF))
	w.line("            while _ep <= _elim do")
	w.line("                local eid, ewt")
	w.line("                eid, ewt, _ep = wire.decode_tag(payload, _ep)")
	w.line("                if eid == 1 then")
	emitMapDecode(w, "_key", keyF, file, selfPath, imports, prefix)
	w.line("                elseif eid == 2 then")
	emitMapDecode(w, "_val", valF, file, selfPath, imports, prefix)
	w.line("                else")
	w.line("                    _ep = wire.skip_field(payload, _ep, ewt, eid)")
	w.line("                end")
	w.line("            end")
	if mapKeyNeedsCdataDedup(keyF) {
		// 64-bit int keys are LuaJIT cdata; LuaJIT hashes cdata by pointer,
		// so duplicate-key wire entries land in different hash buckets even
		// though __eq matches. Walk once to find a canonical key and
		// preserve proto3 "last value wins" semantics. Only emitted for
		// cdata-yielding key types so string/int32-keyed maps stay on the
		// JIT trace.
		w.line("            for _k in pairs(map) do")
		w.line("                if _k == _key then _key = _k; break end")
		w.line("            end")
	}
	w.line("            map[_key] = _val")
}

// mapKeyNeedsCdataDedup reports whether a map key type yields LuaJIT
// cdata and therefore needs pointer-vs-value dedup on decode. Mirrors the
// runtime gate set up in pb.finalize_message.
func mapKeyNeedsCdataDedup(keyF *protogen.Field) bool {
	switch keyF.Desc.Kind() {
	case protoreflect.Int64Kind, protoreflect.Uint64Kind,
		protoreflect.Sint64Kind, protoreflect.Fixed64Kind,
		protoreflect.Sfixed64Kind:
		return true
	}
	return false
}

// mapDefaultExpr returns the Lua expression for a map sub-field's default.
func mapDefaultExpr(f *protogen.Field) string {
	switch {
	case f.Message != nil:
		return "{}"
	case f.Enum != nil:
		return "0"
	}
	switch f.Desc.Kind() {
	case protoreflect.StringKind, protoreflect.BytesKind:
		return "''"
	case protoreflect.BoolKind:
		return "false"
	}
	return "0"
}

// emitMapDecode emits the per-sub-field decode body inside the map entry's
// while-loop. Stores into <dst>; advances _ep.
func emitMapDecode(w *writer, dst string, f *protogen.Field, file *protogen.File, selfPath string, imports map[string]string, prefix string) {
	switch {
	case f.Message != nil:
		ref := typeRef(file, f.Message.Desc, selfPath, imports, "_decode", prefix)
		w.line("                    local _payload")
		w.line("                    _payload, _ep = wire.decode_len(payload, _ep)")
		w.line("                    %s = %s(_payload)", dst, ref)
	case f.Enum != nil:
		w.line("                    local _u")
		w.line("                    _u, _ep = wire.decode_varint(payload, _ep)")
		w.line("                    %s = wire.varint_to_int32(_u)", dst)
	default:
		st := scalarName(f.Desc.Kind())
		w.line("                    %s, _ep = wire.decode_%s(payload, _ep)", dst, st)
	}
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// wireTypeForField returns the proto wire type used to encode this field.
//
// For repeated packable fields with packed=true (proto3 default for primitive
// scalars and enums), the *element* tag is LEN — caller still calls this with
// care. We return the singular-element wire type and let the emit logic decide
// when to swap it to LEN for packed encoding.
func wireTypeForField(f *protogen.Field) int {
	switch {
	case f.Message != nil:
		return 2 // LEN
	case f.Enum != nil:
		// Repeated packed enums use LEN tag; non-packed elements use VARINT.
		if f.Desc.IsList() && f.Desc.IsPacked() {
			return 2
		}
		return 0 // VARINT
	}
	switch f.Desc.Kind() {
	case protoreflect.Int32Kind, protoreflect.Int64Kind,
		protoreflect.Uint32Kind, protoreflect.Uint64Kind,
		protoreflect.Sint32Kind, protoreflect.Sint64Kind,
		protoreflect.BoolKind:
		// Repeated packed primitives use LEN tag.
		if f.Desc.IsList() && f.Desc.IsPacked() {
			return 2
		}
		return 0 // VARINT
	case protoreflect.Fixed32Kind, protoreflect.Sfixed32Kind, protoreflect.FloatKind:
		if f.Desc.IsList() && f.Desc.IsPacked() {
			return 2
		}
		return 5 // I32
	case protoreflect.Fixed64Kind, protoreflect.Sfixed64Kind, protoreflect.DoubleKind:
		if f.Desc.IsList() && f.Desc.IsPacked() {
			return 2
		}
		return 1 // I64
	case protoreflect.StringKind, protoreflect.BytesKind:
		return 2 // LEN (never packable)
	}
	panic("unhandled kind: " + f.Desc.Kind().String())
}

// tagBytesLit returns a Lua string literal (e.g. "\"\\x0a\"") encoding the
// varint for tag = (id << 3) | wireType. Tags are 1 byte for ids ≤ 15 with
// VARINT/I32/I64, 1 byte for ids ≤ 31 with LEN, 2 bytes for ids ≤ 2047,
// and so on.
func tagBytesLit(id int32, wireType int) string {
	tag := uint64(id)*8 + uint64(wireType)
	var b []byte
	for tag >= 128 {
		b = append(b, byte(tag&0x7f)|0x80)
		tag >>= 7
	}
	b = append(b, byte(tag))
	return luaByteString(b)
}

func luaByteString(b []byte) string {
	var sb strings.Builder
	sb.WriteByte('"')
	for _, c := range b {
		sb.WriteString(fmt.Sprintf("\\x%02x", c))
	}
	sb.WriteByte('"')
	return sb.String()
}

// scalarNotDefaultExpr returns a Lua expression that evaluates true when
// the value `v` is NOT the proto3 default for the given scalar type.
// Default is elided on encode.
//
// Floats and doubles need a sign-bit guard: -0.0 == 0.0 in IEEE, but
// they aren't the proto3 default (the wire bytes differ, and the
// TextFormatInput conformance corpus pins this). `1/v == -math.huge`
// is the standard sign-bit probe — division by +0 yields +inf, by -0
// yields -inf, and any non-zero value short-circuits via `v ~= 0`.
func scalarNotDefaultExpr(scalar, v string) string {
	switch scalar {
	case "string", "bytes":
		return v + ` ~= ''`
	case "bool":
		return v + ` ~= false`
	case "float", "double":
		return "(" + v + ` ~= 0 or 1/` + v + " == -math.huge)"
	}
	return v + ` ~= 0`
}