• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ahamez / protox / de8724157512861ad88722a0c5a2caef88658dc6

30 Jan 2025 01:59PM UTC coverage: 94.161% (-0.3%) from 94.424%
de8724157512861ad88722a0c5a2caef88658dc6

push

github

ahamez
refactor: directly pattern match on varint when decoding

Benchmark shows
- from ~1.2% to ~9.8% speedup
- a reduction from ~6.2% to ~11% in memory consumption

26 of 27 new or added lines in 1 file covered. (96.3%)

2 existing lines in 2 files now uncovered.

774 of 822 relevant lines covered (94.16%)

12993.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.21
/lib/protox/define_decoder.ex
1
defmodule Protox.DefineDecoder do
2
  @moduledoc false
3
  # Internal. Generates the decoder of a message.
4

5
  alias Protox.{Field, OneOf, Scalar}
6
  use Protox.{Float, WireTypes}
7

8
  def define(msg_name, fields, required_fields, opts \\ []) do
9
    vars = %{
90✔
10
      bytes: Macro.var(:bytes, __MODULE__),
11
      delimited: Macro.var(:delimited, __MODULE__),
12
      field: Macro.var(:field, __MODULE__),
13
      msg: Macro.var(:msg, __MODULE__),
14
      rest: Macro.var(:rest, __MODULE__),
15
      set_fields: Macro.var(:set_fields, __MODULE__),
16
      value: Macro.var(:value, __MODULE__)
17
    }
18

19
    # The public function to decode the binary protobuf.
20
    decode_fun = make_decode_fun(required_fields, msg_name, vars)
90✔
21

22
    # The function that decodes the binary protobuf and possibly dispatches to other decoding
23
    # functions.
24
    parse_key_value_fun = make_parse_key_value_fun(required_fields, fields, vars, opts)
90✔
25

26
    # The functions that decodes maps.
27
    parse_map_entries = make_parse_map_entries_funs(vars, fields)
90✔
28

29
    quote do
30
      unquote(decode_fun)
31
      unquote(parse_key_value_fun)
32
      unquote(parse_map_entries)
33
    end
34
  end
35

36
  defp make_decode_fun(required_fields, msg_name, vars) do
37
    decode_bang_fun = make_decode_bang_fun(required_fields, msg_name, vars)
90✔
38

39
    quote do
40
      @spec decode(binary()) :: {:ok, struct()} | {:error, any()}
41
      def decode(bytes) do
42
        try do
43
          {:ok, decode!(bytes)}
44
        rescue
45
          e in [Protox.DecodingError, Protox.IllegalTagError, Protox.RequiredFieldsError] ->
46
            {:error, e}
47
        end
48
      end
49

50
      unquote(decode_bang_fun)
51
    end
52
  end
53

54
  defp make_decode_bang_fun([], msg_name, _vars) do
55
    quote do
56
      @spec decode!(binary()) :: struct() | no_return()
57
      def decode!(bytes) do
58
        parse_key_value(bytes, struct(unquote(msg_name)))
59
      end
60
    end
61
  end
62

63
  defp make_decode_bang_fun(required_fields, msg_name, vars) do
64
    quote do
65
      @spec decode!(binary()) :: struct() | no_return()
66
      def decode!(bytes) do
67
        {msg, unquote(vars.set_fields)} = parse_key_value([], bytes, struct(unquote(msg_name)))
5✔
68

69
        case unquote(required_fields) -- unquote(vars.set_fields) do
5✔
70
          [] -> msg
71
          missing_fields -> raise Protox.RequiredFieldsError.new(missing_fields)
72
        end
73
      end
74
    end
75
  end
76

77
  defp make_parse_key_value_fun(required_fields, fields, vars, opts) do
78
    keep_set_fields = required_fields != []
90✔
79

80
    parse_key_value_body =
90✔
81
      make_parse_key_value_body(keep_set_fields, fields, vars, opts)
82

83
    if keep_set_fields do
90✔
84
      quote do
85
        @spec parse_key_value([atom()], binary(), struct()) :: {struct(), [atom()]}
86
        defp parse_key_value(unquote(vars.set_fields), <<>>, msg) do
5✔
87
          {msg, unquote(vars.set_fields)}
5✔
88
        end
89

90
        defp parse_key_value(unquote(vars.set_fields), bytes, msg) do
5✔
91
          unquote(parse_key_value_body)
92
        end
93
      end
94
    else
95
      quote do
96
        @spec parse_key_value(binary(), struct()) :: struct()
97
        defp parse_key_value(<<>>, msg), do: msg
98

99
        defp parse_key_value(bytes, msg), do: unquote(parse_key_value_body)
100
      end
101
    end
102
  end
103

104
  defp make_parse_key_value_body(keep_set_fields, fields, vars, opts) do
105
    # Fragment to parse unknown fields. Those are identified with an unknown tag.
106
    unknown_tag_clause =
90✔
107
      make_parse_key_value_unknown(
108
        vars,
109
        keep_set_fields,
110
        Keyword.fetch!(opts, :unknown_fields_name)
111
      )
112

113
    # Fragment to parse all regular fields.
114
    all_fields_clase = make_parse_key_value_known(vars, fields, keep_set_fields)
90✔
115

116
    all_clauses =
90✔
117
      make_parse_key_value_invalid_varint() ++
118
        make_parse_key_value_tag_0() ++
119
        all_fields_clase ++
120
        unknown_tag_clause
121

122
    # Note we directly pattern-match against the bytes: we don't decode the tag
123
    # and the wire type using Varint.decode. Indeed, as we know the varint encoding
124
    # at compile time, we can generate the appropriate clauses.
125
    # This has the benefit of a small speedup (~1%-10%) and a decrease in memory usage (~10%) from
126
    # the Varint.decode version.
127
    if keep_set_fields do
90✔
128
      quote do
129
        {new_set_fields, unquote(vars.field), rest} =
5✔
130
          case bytes, do: unquote(all_clauses)
131

132
        msg_updated = struct(unquote(vars.msg), unquote(vars.field))
5✔
133
        parse_key_value(new_set_fields, rest, msg_updated)
134
      end
135
    else
136
      quote do
137
        {unquote(vars.field), rest} =
85✔
138
          case bytes, do: unquote(all_clauses)
139

140
        msg_updated = struct(unquote(vars.msg), unquote(vars.field))
85✔
141
        parse_key_value(rest, msg_updated)
142
      end
143
    end
144
  end
145

146
  defp make_parse_key_value_tag_0() do
90✔
147
    quote do
148
      <<0::5, _::3, _rest::binary>> -> raise %Protox.IllegalTagError{}
149
    end
150
  end
151

152
  defp make_parse_key_value_invalid_varint() do
90✔
153
    quote do
154
      <<_::5, 3::3, _rest::binary>> ->
155
        raise Protox.DecodingError.new(bytes, "invalid wire type 3")
156

157
      <<_::5, 4::3, _rest::binary>> ->
158
        raise Protox.DecodingError.new(bytes, "invalid wire type 4")
159

160
      <<_::5, 6::3, _rest::binary>> ->
161
        raise Protox.DecodingError.new(bytes, "invalid wire type 6")
162

163
      <<_::5, 7::3, _rest::binary>> ->
164
        raise Protox.DecodingError.new(bytes, "invalid wire type 7")
165
    end
166
  end
167

168
  defp make_parse_key_value_known(vars, fields, keep_set_fields) do
169
    Enum.flat_map(fields, fn %Field{} = field ->
90✔
170
      single = make_single_case(vars, keep_set_fields, field)
975✔
171

172
      single_generated = single != []
975✔
173
      delimited = make_delimited_case(vars, keep_set_fields, single_generated, field)
975✔
174

175
      delimited ++ single
975✔
176
    end)
177
  end
178

179
  defp make_parse_key_value_unknown(vars, keep_set_fields, unknown_fields_name) do
180
    body =
90✔
181
      quote do
182
        {
183
          unquote(unknown_fields_name),
184
          # Order is important here, we want to keep the order of the unknown fields.
185
          unquote(vars.msg).unquote(unknown_fields_name) ++ [unquote(vars.value)]
90✔
186
        }
187
      end
188

189
    case_return =
90✔
190
      case keep_set_fields do
191
        true -> quote(do: {unquote(vars.set_fields), [unquote(body)], rest})
5✔
192
        # No need to maintain a list of set fields when the list of required fields is empty
193
        false -> quote(do: {[unquote(body)], rest})
85✔
194
      end
195

196
    quote do
197
      <<unquote(vars.bytes)::binary>> ->
90✔
198
        {tag, wire_type, rest} = Protox.Decode.parse_key(unquote(vars.bytes))
90✔
199
        {unquote(vars.value), rest} = Protox.Decode.parse_unknown(tag, wire_type, rest)
90✔
200

201
        unquote(case_return)
202
    end
203
  end
204

205
  defp make_single_case(_vars, _keep_set_fields, %Field{type: {:message, _}}) do
225✔
206
    quote(do: [])
207
  end
208

209
  defp make_single_case(_vars, _keep_set_fields, %Field{type: :string}), do: quote(do: [])
105✔
210
  defp make_single_case(_vars, _keep_set_fields, %Field{type: :bytes}), do: quote(do: [])
20✔
211

212
  defp make_single_case(_vars, _keep_set_fields, %Field{type: {x, _}}) when x != :enum do
95✔
213
    quote(do: [])
214
  end
215

216
  defp make_single_case(vars, keep_set_fields, %Field{} = field) do
217
    parse_single = make_parse_single(vars.bytes, field.type)
530✔
218
    update_field = make_update_field(vars.value, field, vars, _wrap_value = true)
530✔
219

220
    # No need to maintain a list of set fields for proto3.
221
    clause_return =
530✔
222
      case keep_set_fields do
223
        true -> quote do: {[unquote(field.name) | set_fields], [unquote(update_field)], rest}
5✔
224
        false -> quote do: {[unquote(update_field)], rest}
525✔
225
      end
226

227
    key_bytes = make_key_bytes(field)
530✔
228

229
    # The last 3 bits of the first byte are the wire type, which we can to ignore here as we know beforehand
230
    # how the field is encoded.
231
    <<first_byte::5, _wire_type::3, tail::binary>> = key_bytes
530✔
232

233
    clause =
530✔
234
      case tail do
235
        "" ->
236
          quote do
237
            <<unquote(first_byte)::5, _wire_type::3, unquote(vars.bytes)::binary>>
135✔
238
          end
239

240
        _ ->
241
          quote do
242
            <<unquote(first_byte)::5, _wire_type::3, unquote(tail), unquote(vars.bytes)::binary>>
395✔
243
          end
244
      end
245

246
    quote do
247
      unquote(clause) ->
248
        {value, rest} = unquote(parse_single)
249
        unquote(clause_return)
250
    end
251
  end
252

253
  defp make_delimited_case(
254
         vars,
255
         keep_set_fields,
256
         single_generated,
257
         %Field{type: {:message, _}} = field
258
       ) do
259
    make_delimited_case_impl(vars, keep_set_fields, single_generated, field)
225✔
260
  end
261

262
  defp make_delimited_case(vars, keep_set_fields, single_generated, %Field{type: :bytes} = field) do
263
    make_delimited_case_impl(vars, keep_set_fields, single_generated, field)
20✔
264
  end
265

266
  defp make_delimited_case(vars, keep_set_fields, single_generated, %Field{type: :string} = field) do
267
    make_delimited_case_impl(vars, keep_set_fields, single_generated, field)
105✔
268
  end
269

270
  defp make_delimited_case(_vars, _keep_set_fields, _single_generated, %Field{kind: %Scalar{}}) do
280✔
271
    []
272
  end
273

274
  defp make_delimited_case(_vars, _keep_set_fields, _single_generated, %Field{kind: %OneOf{}}) do
35✔
275
    []
276
  end
277

278
  defp make_delimited_case(vars, keep_set_fields, single_generated, %Field{} = field) do
279
    make_delimited_case_impl(vars, keep_set_fields, single_generated, field)
310✔
280
  end
281

282
  defp make_delimited_case_impl(vars, keep_set_fields, single_generated, %Field{} = field) do
283
    # If the case to decode single occurrences of repeated elements has been generated,
284
    # it means that it's a repeated field of scalar elements (as non-scalar cannot be packed,
285
    # see https://developers.google.com/protocol-buffers/docs/encoding#optional).
286
    # Thus, it's useless to wrap in a list the result of the decoding as it means
287
    # we're using a parse_repeated_* function that always returns a list.
288
    update_field =
660✔
289
      if field.type == :bytes do
660✔
290
        make_update_field(vars.delimited, field, vars, _wrap_value = !single_generated)
20✔
291
      else
292
        parse_delimited = make_parse_delimited(vars.delimited, field.type)
640✔
293
        make_update_field(parse_delimited, field, vars, _wrap_value = !single_generated)
640✔
294
      end
295

296
    case_return =
660✔
297
      case keep_set_fields do
298
        true -> quote do: {[unquote(field.name) | set_fields], [unquote(update_field)], rest}
5✔
299
        false -> quote do: {[unquote(update_field)], rest}
655✔
300
      end
301

302
    key_bytes = make_key_bytes(%Field{field | kind: :packed})
660✔
303

304
    clause =
660✔
305
      if single_generated do
306
        # If the single clause was not generated for this field, we don't need the wire type
307
        # discrimant as there is only one clause matching for this field.
308
        quote do
309
          <<unquote(key_bytes), unquote(vars.bytes)::binary>>
215✔
310
        end
311
      else
312
        <<first_byte::5, _wire_type::3, tail::binary>> = key_bytes
445✔
313

314
        case tail do
445✔
315
          "" ->
316
            quote do
317
              <<unquote(first_byte)::5, _wire_type::3, unquote(vars.bytes)::binary>>
75✔
318
            end
319

320
          _ ->
321
            quote do
322
              <<unquote(first_byte)::5, _wire_type::3, unquote(tail),
323
                unquote(vars.bytes)::binary>>
370✔
324
            end
325
        end
326
      end
327

328
    quote do
329
      unquote(clause) ->
330
        {len, unquote(vars.bytes)} = Protox.Varint.decode(unquote(vars.bytes))
660✔
331

332
        {unquote(vars.delimited), rest} = Protox.Decode.parse_delimited(unquote(vars.bytes), len)
660✔
333
        unquote(case_return)
334
    end
335
  end
336

337
  defp make_update_field(value, %Field{kind: :map} = field, vars, _wrap_value) do
338
    quote do
339
      {entry_key, entry_value} = unquote(value)
340

341
      {unquote(field.name),
95✔
342
       Map.put(unquote(vars.msg).unquote(field.name), entry_key, entry_value)}
95✔
343
    end
344
  end
345

346
  defp make_update_field(
347
         value,
348
         %Field{label: :proto3_optional, kind: %OneOf{}, type: {:message, _}} = field,
349
         vars,
350
         _wrap_value
351
       ) do
352
    quote do
353
      case unquote(vars.msg).unquote(field.name) do
×
354
        {unquote(field.name), previous_value} ->
×
355
          {unquote(field.name), Protox.MergeMessage.merge(previous_value, unquote(value))}
×
356

357
        _ ->
358
          {unquote(field.name), unquote(value)}
×
359
      end
360
    end
361
  end
362

363
  defp make_update_field(
364
         value,
365
         %Field{kind: %OneOf{}, type: {:message, _}} = field,
366
         vars,
367
         _wrap_value
368
       ) do
369
    quote do
370
      case unquote(vars.msg).unquote(field.kind.parent) do
5✔
371
        {unquote(field.name), previous_value} ->
5✔
372
          {unquote(field.kind.parent),
5✔
373
           {unquote(field.name), Protox.MergeMessage.merge(previous_value, unquote(value))}}
5✔
374

375
        _ ->
376
          {unquote(field.kind.parent), {unquote(field.name), unquote(value)}}
5✔
377
      end
378
    end
379
  end
380

381
  defp make_update_field(value, %Field{kind: %OneOf{}} = field, _vars, _wrap_value) do
382
    case field.label do
45✔
383
      :proto3_optional ->
×
384
        quote(do: {unquote(field.name), unquote(value)})
×
385

386
      _ ->
45✔
387
        quote(do: {unquote(field.kind.parent), {unquote(field.name), unquote(value)}})
45✔
388
    end
389
  end
390

391
  defp make_update_field(
120✔
392
         value,
393
         %Field{kind: %Scalar{}, type: {:message, _}} = field,
394
         vars,
395
         _wrap_value
396
       ) do
397
    quote do
398
      {
399
        unquote(field.name),
120✔
400
        Protox.MergeMessage.merge(unquote(vars.msg).unquote(field.name), unquote(value))
120✔
401
      }
402
    end
403
  end
404

405
  defp make_update_field(value, %Field{kind: %Scalar{}} = field, _vars, _wrap_value) do
375✔
406
    quote(do: {unquote(field.name), unquote(value)})
375✔
407
  end
408

409
  defp make_update_field(value, %Field{} = field, vars, true = _wrap_value) do
335✔
410
    quote do
411
      {unquote(field.name), unquote(vars.msg).unquote(field.name) ++ [unquote(value)]}
335✔
412
    end
413
  end
414

415
  defp make_update_field(value, %Field{} = field, vars, false = _wrap_value) do
215✔
416
    quote do
417
      {unquote(field.name), unquote(vars.msg).unquote(field.name) ++ unquote(value)}
215✔
418
    end
419
  end
420

421
  defp make_parse_delimited(bytes_var, :bytes) do
422
    quote(do: unquote(bytes_var))
5✔
423
  end
424

425
  defp make_parse_delimited(bytes_var, :string) do
426
    quote(do: Protox.Decode.validate_string!(unquote(bytes_var)))
427
  end
428

429
  defp make_parse_delimited(bytes_var, {:enum, mod}) do
430
    quote(do: Protox.Decode.parse_repeated_enum([], unquote(bytes_var), unquote(mod)))
431
  end
432

433
  defp make_parse_delimited(bytes_var, {:message, mod}) do
434
    quote(do: unquote(mod).decode!(unquote(bytes_var)))
435
  end
436

437
  defp make_parse_delimited(bytes_var, :bool) do
438
    quote(do: Protox.Decode.parse_repeated_bool([], unquote(bytes_var)))
439
  end
440

441
  defp make_parse_delimited(bytes_var, :int32) do
442
    quote(do: Protox.Decode.parse_repeated_int32([], unquote(bytes_var)))
443
  end
444

445
  defp make_parse_delimited(bytes_var, :uint32) do
446
    quote(do: Protox.Decode.parse_repeated_uint32([], unquote(bytes_var)))
447
  end
448

449
  defp make_parse_delimited(bytes_var, :sint32) do
450
    quote(do: Protox.Decode.parse_repeated_sint32([], unquote(bytes_var)))
451
  end
452

453
  defp make_parse_delimited(bytes_var, :int64) do
454
    quote(do: Protox.Decode.parse_repeated_int64([], unquote(bytes_var)))
455
  end
456

457
  defp make_parse_delimited(bytes_var, :uint64) do
458
    quote(do: Protox.Decode.parse_repeated_uint64([], unquote(bytes_var)))
459
  end
460

461
  defp make_parse_delimited(bytes_var, :sint64) do
462
    quote(do: Protox.Decode.parse_repeated_sint64([], unquote(bytes_var)))
463
  end
464

465
  defp make_parse_delimited(bytes_var, :fixed32) do
466
    quote(do: Protox.Decode.parse_repeated_fixed32([], unquote(bytes_var)))
467
  end
468

469
  defp make_parse_delimited(bytes_var, :fixed64) do
470
    quote(do: Protox.Decode.parse_repeated_fixed64([], unquote(bytes_var)))
471
  end
472

473
  defp make_parse_delimited(bytes_var, :sfixed32) do
474
    quote(do: Protox.Decode.parse_repeated_sfixed32([], unquote(bytes_var)))
475
  end
476

477
  defp make_parse_delimited(bytes_var, :sfixed64) do
478
    quote(do: Protox.Decode.parse_repeated_sfixed64([], unquote(bytes_var)))
479
  end
480

481
  defp make_parse_delimited(bytes_var, :float) do
482
    quote(do: Protox.Decode.parse_repeated_float([], unquote(bytes_var)))
483
  end
484

485
  defp make_parse_delimited(bytes_var, :double) do
486
    quote(do: Protox.Decode.parse_repeated_double([], unquote(bytes_var)))
487
  end
488

489
  defp make_parse_delimited(bytes_var, {key_type, value_type}) do
490
    unset_map_value =
95✔
491
      case value_type do
492
        {:message, msg_type} -> quote(do: struct(unquote(msg_type)))
493
        _ -> quote(do: Protox.Default.default(unquote(value_type)))
494
      end
495

496
    parser_fun_name = make_map_decode_fun_name(key_type, value_type)
95✔
497

498
    quote do
499
      {map_key, map_value} = unquote(parser_fun_name)({:unset, :unset}, unquote(bytes_var))
500

501
      map_key =
502
        case map_key do
503
          :unset -> Protox.Default.default(unquote(key_type))
504
          _ -> map_key
505
        end
506

507
      map_value =
508
        case map_value do
509
          :unset -> unquote(unset_map_value)
510
          _ -> map_value
511
        end
512

513
      {map_key, map_value}
514
    end
515
  end
516

517
  defp make_parse_single(bytes_var, :double) do
518
    quote(do: Protox.Decode.parse_double(unquote(bytes_var)))
519
  end
520

521
  defp make_parse_single(bytes_var, :float) do
522
    quote(do: Protox.Decode.parse_float(unquote(bytes_var)))
523
  end
524

525
  defp make_parse_single(bytes_var, :sfixed64) do
526
    quote(do: Protox.Decode.parse_sfixed64(unquote(bytes_var)))
527
  end
528

529
  defp make_parse_single(bytes_var, :fixed64) do
530
    quote(do: Protox.Decode.parse_fixed64(unquote(bytes_var)))
531
  end
532

533
  defp make_parse_single(bytes_var, :sfixed32) do
534
    quote(do: Protox.Decode.parse_sfixed32(unquote(bytes_var)))
535
  end
536

537
  defp make_parse_single(bytes_var, :fixed32) do
538
    quote(do: Protox.Decode.parse_fixed32(unquote(bytes_var)))
539
  end
540

541
  defp make_parse_single(bytes_var, :bool) do
542
    quote(do: Protox.Decode.parse_bool(unquote(bytes_var)))
543
  end
544

545
  defp make_parse_single(bytes_var, :sint32) do
546
    quote(do: Protox.Decode.parse_sint32(unquote(bytes_var)))
547
  end
548

549
  defp make_parse_single(bytes_var, :sint64) do
550
    quote(do: Protox.Decode.parse_sint64(unquote(bytes_var)))
551
  end
552

553
  defp make_parse_single(bytes_var, :uint32) do
554
    quote(do: Protox.Decode.parse_uint32(unquote(bytes_var)))
555
  end
556

557
  defp make_parse_single(bytes_var, :uint64) do
558
    quote(do: Protox.Decode.parse_uint64(unquote(bytes_var)))
559
  end
560

561
  defp make_parse_single(bytes_var, :int32) do
562
    quote(do: Protox.Decode.parse_int32(unquote(bytes_var)))
563
  end
564

565
  defp make_parse_single(bytes_var, :int64) do
566
    quote(do: Protox.Decode.parse_int64(unquote(bytes_var)))
567
  end
568

569
  defp make_parse_single(bytes_var, {:enum, mod}) do
570
    quote(do: Protox.Decode.parse_enum(unquote(bytes_var), unquote(mod)))
571
  end
572

573
  defp make_parse_map_entries_funs(vars, fields) do
574
    {maps, _other_fields} = Protox.Defs.split_maps(fields)
90✔
575

576
    maps
577
    |> Enum.map(fn %Field{kind: :map} = field ->
578
      key_type = elem(field.type, 0)
95✔
579
      value_type = elem(field.type, 1)
95✔
580

581
      fun_name = make_map_decode_fun_name(key_type, value_type)
95✔
582

583
      key_parser = make_parse_map_entry(vars, key_type)
95✔
584
      value_parser = make_parse_map_entry(vars, value_type)
95✔
585

586
      code =
95✔
587
        quote do
588
          defp unquote(fun_name)(map_entry, <<>>) do
589
            map_entry
590
          end
591

592
          # https://developers.google.com/protocol-buffers/docs/proto3#backwards-compatibility
593
          # Maps are equivalent to:
594
          #   message MapFieldEntry {
595
          #     key_type key = 1;
596
          #     value_type value = 2;
597
          #   }
598
          # repeated MapFieldEntry map_field = N;
599
          #
600
          defp unquote(fun_name)({entry_key, entry_value}, unquote(vars.bytes)) do
95✔
601
            {map_entry, unquote(vars.rest)} =
95✔
602
              case Protox.Decode.parse_key(unquote(vars.bytes)) do
95✔
603
                # key
604
                {1, _, unquote(vars.rest)} ->
95✔
605
                  {res, unquote(vars.rest)} = unquote(key_parser)
95✔
606
                  {{res, entry_value}, unquote(vars.rest)}
95✔
607

608
                # value
609
                {2, _, unquote(vars.rest)} ->
95✔
610
                  {res, unquote(vars.rest)} = unquote(value_parser)
95✔
611
                  {{entry_key, res}, unquote(vars.rest)}
95✔
612

613
                {tag, wire_type, unquote(vars.rest)} ->
95✔
614
                  {_, unquote(vars.rest)} =
95✔
615
                    Protox.Decode.parse_unknown(tag, wire_type, unquote(vars.rest))
95✔
616

617
                  {{entry_key, entry_value}, unquote(vars.rest)}
95✔
618
              end
619

620
            unquote(fun_name)(map_entry, unquote(vars.rest))
95✔
621
          end
622
        end
623

624
      {fun_name, code}
625
    end)
626
    |> Enum.sort(fn {lhs_fun_name, _}, {rhs_fun_name, _} -> lhs_fun_name < rhs_fun_name end)
355✔
627
    |> Enum.dedup_by(fn {fun_name, _} -> fun_name end)
95✔
628
    |> Enum.map(fn {_, code} -> code end)
90✔
629
  end
630

631
  defp make_map_decode_fun_name(key_type, value_type) do
632
    value_name =
190✔
633
      case value_type do
634
        {:message, sub_msg} -> "msg_#{Atom.to_string(sub_msg)}"
20✔
635
        {:enum, enum} -> "enum_#{Atom.to_string(enum)}"
20✔
636
        ty -> "#{Atom.to_string(ty)}"
150✔
637
      end
638

639
    value_name =
190✔
640
      value_name
641
      |> Macro.underscore()
642
      |> String.replace("/", "_")
643

644
    String.to_atom("parse_#{Atom.to_string(key_type)}_#{value_name}")
190✔
645
  end
646

647
  defp make_parse_map_entry(vars, type) do
648
    parse_delimited =
190✔
649
      quote do
650
        {len, new_rest} = Protox.Varint.decode(unquote(vars.rest))
190✔
651
        {unquote(vars.delimited), new_rest} = Protox.Decode.parse_delimited(new_rest, len)
190✔
652

653
        {unquote(make_parse_delimited(vars.delimited, type)), new_rest}
190✔
654
      end
655

656
    case type do
190✔
657
      :string -> parse_delimited
35✔
658
      :bytes -> parse_delimited
5✔
659
      {:message, _} -> parse_delimited
10✔
660
      _ -> make_parse_single(vars.rest, type)
140✔
661
    end
662
  end
663

664
  # Compute at compile time the varint representation of a field
665
  # tag and wire type.
666
  defp make_key_bytes(%Field{} = field) do
667
    # We need to convert the type to something recognized
668
    # by Protox.Encode.make_key_bytes/2.
669
    ty =
1,190✔
670
      case field.kind do
1,190✔
NEW
671
        :map -> :map_entry
×
672
        :packed -> :packed
805✔
673
        _ -> field.type
385✔
674
      end
675

676
    Protox.Encode.make_key_bytes(field.tag, ty) |> IO.iodata_to_binary()
1,190✔
677
  end
678
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc