From 1bd23ec35474c798d48348dac2aad8cf246188d4 Mon Sep 17 00:00:00 2001 From: Hitalo Souza Date: Tue, 23 Jun 2026 05:19:07 -0300 Subject: [PATCH] x.json2: decode option fields via comptime `$zero`/`$new` type accessors (#27535) --- .../generic_fn_instantiation_pruning_test.v | 6 ++++- vlib/x/json2/decode.v | 26 +++---------------- vlib/x/json2/decode_sumtype.v | 14 ++++------ 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/vlib/v/tests/skip_unused/generic_fn_instantiation_pruning_test.v b/vlib/v/tests/skip_unused/generic_fn_instantiation_pruning_test.v index 545787ced..786ef3bae 100644 --- a/vlib/v/tests/skip_unused/generic_fn_instantiation_pruning_test.v +++ b/vlib/v/tests/skip_unused/generic_fn_instantiation_pruning_test.v @@ -155,7 +155,11 @@ fn test_skip_unused_keeps_json2_embedded_struct_decode_helpers() { } assert res.output.contains('x__json2__decode_struct_key_T_main__Req') assert res.output.contains('x__json2__check_required_struct_fields_T_main__Req') - assert res.output.contains('x__json2__create_value_from_optional_T_time__Time') + // the `?time.Time` payload of the embedded `Meta` is decoded through a generic + // instantiation reachable only via comptime `$for field` codegen; skip-unused + // must keep it. Assert the payload decoder itself rather than a specific json2 + // helper name, so the test does not break when those helpers are refactored. + assert res.output.contains('x__json2__Decoder_decode_value_T_time__Time') } fn test_skip_unused_marks_dependencies_inside_generic_anon_fns() { diff --git a/vlib/x/json2/decode.v b/vlib/x/json2/decode.v index b52b2860c..f3416d6b5 100644 --- a/vlib/x/json2/decode.v +++ b/vlib/x/json2/decode.v @@ -335,10 +335,6 @@ fn create_decoded_ptr[T](_ &T) &T { } } -fn create_decoded_option_ptr[U](_ ?&U) &U { - return &U{} -} - fn decoder_field_infos[T]() []DecoderFieldInfo { mut field_infos := []DecoderFieldInfo{} $for field in T.fields { @@ -551,16 +547,11 @@ fn decode_struct_key[T](mut decoder Decoder, val T, key_info ValueInfo, prefix s } } else { $if field.indirections == 1 { - mut decoded_ptr := create_decoded_option_ptr(new_val.$(field.name)) + mut decoded_ptr := $new(field.typ.payload_type.pointee_type) decoder.decode_value(mut decoded_ptr)! new_val.$(field.name) = decoded_ptr } $else { - mut unwrapped_val := create_value_from_optional(new_val.$(field.name)) or { - return StructKeyDecodeResult[T]{ - matched: false - value: val - } - } + mut unwrapped_val := $zero(field.typ.payload_type) decoder.decode_value(mut unwrapped_val)! new_val.$(field.name) = unwrapped_val } @@ -916,8 +907,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { } } else { $if field.typ is $option { - // it would be nicer to do this at the start of the function - // but options cant be passed to generic functions if decoder.current_node.value.value_kind == .null { val.$(field.name) = none @@ -926,14 +915,11 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { } } else { $if field.indirections == 1 { - mut decoded_ptr := - create_decoded_option_ptr(val.$(field.name)) + mut decoded_ptr := $new(field.typ.payload_type.pointee_type) decoder.decode_value(mut decoded_ptr)! val.$(field.name) = decoded_ptr } $else { - mut unwrapped_val := create_value_from_optional(val.$(field.name)) or { - return - } + mut unwrapped_val := $zero(field.typ.payload_type) decoder.decode_value(mut unwrapped_val)! val.$(field.name) = unwrapped_val } @@ -1307,10 +1293,6 @@ fn (mut decoder Decoder) decode_map[V](mut val map[string]V) ! { } } -fn create_value_from_optional[T](_val ?T) ?T { - return T{} -} - fn (mut decoder Decoder) decode_enum[T](mut val T) ! { enum_info := decoder.current_node.value diff --git a/vlib/x/json2/decode_sumtype.v b/vlib/x/json2/decode_sumtype.v index 842428aa0..e8b0ef360 100644 --- a/vlib/x/json2/decode_sumtype.v +++ b/vlib/x/json2/decode_sumtype.v @@ -11,21 +11,17 @@ fn sumtype_variant_name(type_name string) string { return type_name.all_after_last('.') } -fn copy_type[T](_t T) T { - return T{} -} - fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T) !T { $if initialized_sumtype is $sumtype || (T is $alias && T.unaliased_typ is $sumtype) { resolved_sumtype := initialized_sumtype $for v in T.variants { if initialized_sumtype is v { $if initialized_sumtype is time.Time { - mut val := copy_type(initialized_sumtype) + mut val := $zero(v.typ) decoder.decode_sumtype_time(mut val)! return T(val) } $else $if initialized_sumtype !is $option { - mut val := copy_type(initialized_sumtype) + mut val := $zero(v.typ) decoder.decode_value(mut val)! return T(val) } $else { @@ -153,7 +149,7 @@ fn (mut decoder Decoder) get_map_type_workaround[T](initialized_sumtype T) bool $for v in T.variants { if initialized_sumtype is v { $if initialized_sumtype is $map { - val := copy_type(initialized_sumtype) + val := $zero(v.typ) if decoder.current_node.next != unsafe { nil } { return decoder.check_map_type_valid(val, decoder.current_node.next.next) } else { @@ -237,7 +233,7 @@ fn (mut decoder Decoder) get_struct_type_workaround[T](initialized_sumtype T) bo $for v in T.variants { if initialized_sumtype is v { $if initialized_sumtype is $struct { - val := copy_type(initialized_sumtype) + val := $zero(v.typ) return decoder.check_struct_type_valid(val, decoder.current_node) } } @@ -251,7 +247,7 @@ fn (mut decoder Decoder) get_time_type_workaround[T](initialized_sumtype T) bool $for v in T.variants { if initialized_sumtype is v { $if initialized_sumtype is time.Time { - val := copy_type(initialized_sumtype) + val := $zero(v.typ) return decoder.check_sumtype_type_valid(val, decoder.current_node) } } -- 2.39.5