From d025b7d80d18675ee5817f060e40a5e6c89b6458 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:24 +0300 Subject: [PATCH] comptime: Detailed `attrs` for methods (fixes #19082) --- vlib/builtin/meta_function.v | 3 +- vlib/v/gen/c/comptime.v | 15 +++++++ .../comptime_method_attributes_test.v | 39 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/comptime/comptime_method_attributes_test.v diff --git a/vlib/builtin/meta_function.v b/vlib/builtin/meta_function.v index a96fd5054..e2dd33963 100644 --- a/vlib/builtin/meta_function.v +++ b/vlib/builtin/meta_function.v @@ -12,7 +12,8 @@ pub struct FunctionData { pub: name string location string - attrs []string + attrs []string // legacy flattened attributes + attributes []VAttribute // structured attributes for `attrs` args []FunctionParam return_type int typ int diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 7e7580099..17ee0d788 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -369,6 +369,16 @@ fn cgen_attrs(attrs []ast.Attr) []string { return res } +fn cgen_vattrs(attrs []ast.Attr) []string { + mut res := []string{cap: attrs.len} + for attr in attrs { + name := cescape_nonascii(util.smart_quote(attr.name, false)) + arg := cescape_nonascii(util.smart_quote(attr.arg, false)) + res << '((VAttribute){.name=_S("${name}"),.has_arg=${attr.has_arg},.arg=_S("${arg}"),.kind=AttributeKind__${attr.kind}})' + } + return res +} + fn (mut g Gen) comptime_at(node ast.AtExpr) { if node.kind == .vmod_file { val := cescape_nonascii(util.smart_quote(node.val, false)) @@ -928,11 +938,16 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { g.writeln('\t${node.val_var}.location = _S("${mlocation}:${method.name_pos.line_nr + 1}:${method.name_pos.col}");') if method.attrs.len == 0 { g.writeln('\t${node.val_var}.attrs = builtin____new_array_with_default(0, 0, sizeof(string), 0);') + g.writeln('\t${node.val_var}.attributes = builtin____new_array_with_default(0, 0, sizeof(VAttribute), 0);') } else { attrs := cgen_attrs(method.attrs) + vattrs := cgen_vattrs(method.attrs) g.writeln( '\t${node.val_var}.attrs = builtin__new_array_from_c_array(${attrs.len}, ${attrs.len}, sizeof(string), _MOV((string[${attrs.len}]){' + attrs.join(', ') + '}));\n') + g.writeln( + '\t${node.val_var}.attributes = builtin__new_array_from_c_array(${vattrs.len}, ${vattrs.len}, sizeof(VAttribute), _MOV((VAttribute[${vattrs.len}]){' + + vattrs.join(', ') + '}));\n') } if method.params.len < 2 { // 0 or 1 (the receiver) args diff --git a/vlib/v/tests/comptime/comptime_method_attributes_test.v b/vlib/v/tests/comptime/comptime_method_attributes_test.v new file mode 100644 index 000000000..e5cf40086 --- /dev/null +++ b/vlib/v/tests/comptime/comptime_method_attributes_test.v @@ -0,0 +1,39 @@ +struct MethodAttributeHolder {} + +@[api_name: 'handler'] +@[secured: true] +@[retries: 3] +fn (m MethodAttributeHolder) endpoint() {} + +fn (m MethodAttributeHolder) no_attrs() {} + +fn find_method_attr(attrs []VAttribute, name string, kind AttributeKind) string { + for attr in attrs { + if attr.name == name && attr.kind == kind { + return attr.arg + } + } + return '' +} + +fn test_comptime_method_attributes_are_structured() { + mut saw_endpoint := false + mut saw_no_attrs := false + $for method in MethodAttributeHolder.methods { + if method.name == 'endpoint' { + saw_endpoint = true + assert method.attrs.len == 3 + assert method.attributes.len == 3 + assert find_method_attr(method.attributes, 'api_name', .string) == 'handler' + assert find_method_attr(method.attributes, 'secured', .bool) == 'true' + assert find_method_attr(method.attributes, 'retries', .number) == '3' + } + if method.name == 'no_attrs' { + saw_no_attrs = true + assert method.attrs.len == 0 + assert method.attributes.len == 0 + } + } + assert saw_endpoint + assert saw_no_attrs +} -- 2.39.5