Skip to content

Commit 114fda5

Browse files
fix: preserve attribute call syntax in vfmt (#26769)
* fix: preserve attribute call syntax in vfmt Fixes #26766 * fix: avoid decl-after-label in v2 parser
1 parent eaa108b commit 114fda5

7 files changed

Lines changed: 177 additions & 35 deletions

File tree

‎vlib/v/ast/attr.v‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,17 @@ pub:
2525
ct_opt bool // true for [if user_defined_name?]
2626
pos token.Pos
2727
has_at bool // new syntax `@[attr]`
28+
// original call-style metadata for `@[foo(...)]`, used by vfmt
29+
call_name string
30+
call_arg_name string
2831
pub mut:
2932
ct_expr Expr // .kind == comptime_define, for [if !name]
3033
ct_evaled bool // whether ct_skip has been evaluated already
3134
ct_skip bool // is the comptime expr *false*, filled by checker
3235
}
3336

3437
pub fn (a &Attr) debug() string {
35-
return 'Attr{ name: "${a.name}", has_arg: ${a.has_arg}, arg: "${a.arg}", kind: ${a.kind}, ct_expr: ${a.ct_expr}, ct_opt: ${a.ct_opt}, ct_skip: ${a.ct_skip}}'
38+
return 'Attr{ name: "${a.name}", has_arg: ${a.has_arg}, arg: "${a.arg}", kind: ${a.kind}, ct_expr: ${a.ct_expr}, ct_opt: ${a.ct_opt}, ct_skip: ${a.ct_skip}, call_name: "${a.call_name}", call_arg_name: "${a.call_arg_name}" }'
3639
}
3740

3841
// str returns the string representation without square brackets

‎vlib/v/fmt/attrs.v‎

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ module fmt
66
import v.ast
77

88
pub fn (mut f Fmt) attrs(attrs []ast.Attr) {
9+
if attrs_have_call_syntax(attrs) {
10+
f.call_syntax_attrs(attrs)
11+
return
12+
}
13+
f.legacy_attrs(attrs)
14+
}
15+
16+
fn (mut f Fmt) legacy_attrs(attrs []ast.Attr) {
917
mut sorted_attrs := attrs.clone()
1018
// Sort the attributes. The ones with arguments come first
1119
sorted_attrs.sort_with_compare(fn (a &ast.Attr, b &ast.Attr) int {
@@ -21,6 +29,24 @@ pub fn (mut f Fmt) attrs(attrs []ast.Attr) {
2129
}
2230
}
2331

32+
fn (mut f Fmt) call_syntax_attrs(attrs []ast.Attr) {
33+
mut i := 0
34+
for i < attrs.len {
35+
if attrs[i].call_name.len > 0 {
36+
group, next_idx := attr_call_group(attrs, i)
37+
f.writeln('@[${attr_call_group_str(group)}]')
38+
i = next_idx
39+
continue
40+
}
41+
mut j := i
42+
for j < attrs.len && attrs[j].call_name.len == 0 {
43+
j++
44+
}
45+
f.legacy_attrs(attrs[i..j])
46+
i = j
47+
}
48+
}
49+
2450
@[params]
2551
pub struct AttrsOptions {
2652
pub:
@@ -31,6 +57,22 @@ pub fn (mut f Fmt) single_line_attrs(attrs []ast.Attr, options AttrsOptions) {
3157
if attrs.len == 0 {
3258
return
3359
}
60+
if attrs_have_call_syntax(attrs) {
61+
if options.same_line {
62+
f.write(' ')
63+
}
64+
f.write('@[')
65+
f.write(single_line_attrs_text(attrs))
66+
f.write(']')
67+
if !options.same_line {
68+
f.writeln('')
69+
}
70+
return
71+
}
72+
f.legacy_single_line_attrs(attrs, options)
73+
}
74+
75+
fn (mut f Fmt) legacy_single_line_attrs(attrs []ast.Attr, options AttrsOptions) {
3476
mut sorted_attrs := attrs.clone()
3577
sorted_attrs.sort(a.name < b.name)
3678
if options.same_line {
@@ -53,13 +95,75 @@ fn inline_attrs_len(attrs []ast.Attr) int {
5395
if attrs.len == 0 {
5496
return 0
5597
}
56-
mut n := 2 // ' ['.len
57-
for i, attr in attrs {
58-
if i > 0 {
59-
n += 2 // '; '.len
98+
return 3 + single_line_attrs_text(attrs).len // ' [' + ']'.len
99+
}
100+
101+
fn attrs_have_call_syntax(attrs []ast.Attr) bool {
102+
return attrs.any(it.call_name.len > 0)
103+
}
104+
105+
fn single_line_attrs_text(attrs []ast.Attr) string {
106+
if !attrs_have_call_syntax(attrs) {
107+
mut sorted_attrs := attrs.clone()
108+
sorted_attrs.sort(a.name < b.name)
109+
mut parts := []string{cap: sorted_attrs.len}
110+
for attr in sorted_attrs {
111+
parts << '${attr}'
112+
}
113+
return parts.join('; ')
114+
}
115+
mut parts := []string{}
116+
mut i := 0
117+
for i < attrs.len {
118+
if attrs[i].call_name.len > 0 {
119+
group, next_idx := attr_call_group(attrs, i)
120+
parts << attr_call_group_str(group)
121+
i = next_idx
122+
continue
123+
}
124+
parts << '${attrs[i]}'
125+
i++
126+
}
127+
return parts.join('; ')
128+
}
129+
130+
fn attr_call_group(attrs []ast.Attr, start int) ([]ast.Attr, int) {
131+
first := attrs[start]
132+
mut end := start + 1
133+
for end < attrs.len && attrs[end].call_name == first.call_name
134+
&& attrs[end].pos.pos == first.pos.pos {
135+
end++
136+
}
137+
return attrs[start..end], end
138+
}
139+
140+
fn attr_call_group_str(attrs []ast.Attr) string {
141+
if attrs.len == 0 {
142+
return ''
143+
}
144+
mut args := []string{}
145+
for attr in attrs {
146+
if !attr.has_arg {
147+
continue
148+
}
149+
mut arg := ''
150+
if attr.call_arg_name.len > 0 {
151+
arg += '${attr.call_arg_name}: '
60152
}
61-
n += '${attr}'.len
153+
arg += attr_value_str(attr)
154+
args << arg
155+
}
156+
if args.len == 0 {
157+
return '${attrs[0].call_name}()'
158+
}
159+
return '${attrs[0].call_name}(${args.join(', ')})'
160+
}
161+
162+
fn attr_value_str(attr ast.Attr) string {
163+
quote := if attr.quote == `"` { '"' } else { "'" }
164+
return match attr.kind {
165+
.plain, .number, .bool { attr.arg }
166+
.string { '${quote}${attr.arg}${quote}' }
167+
.comptime_define { 'if ${attr.arg}' }
62168
}
63-
n++ // ']'.len
64-
return n
65169
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@[xml(name: 'foo', prefix: 'f', namespace: 'https://example.com/xmlns/foo')]
2+
struct Foo {}
3+
4+
@[deprecated(msg: 'use bar() instead', after: '2026-06-01')]
5+
fn foo() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@[xml(name: 'foo', prefix: 'f', namespace: 'https://example.com/xmlns/foo')]
2+
struct Foo {}
3+
4+
@[deprecated(msg: 'use bar() instead', after: '2026-06-01')]
5+
fn foo() {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@[unsafe()]
2+
fn allow_unsafe_parentheses() {}
3+
4+
@[deprecated('use new_fn instead')]
5+
fn old_positional() {}
6+
7+
@[deprecated(msg: 'use new_fn instead', after: '2999-10-10')]
8+
fn old_named() {}
9+
10+
struct Config {
11+
flag bool @[custom(flag: true, count: 2)]
12+
value string @[xml(name: 'cfg'); raw]
13+
}

‎vlib/v/parser/attribute.v‎

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
4242
mut base_kind := ast.AttrKind.plain
4343
mut base_arg := ''
4444
mut base_quote := u8(`'`)
45+
mut base_arg_name := ''
4546
mut base_has_arg := false
4647
mut attrs := []ast.Attr{}
4748
mut has_base_arg := false
@@ -66,16 +67,19 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
6667
base_arg = arg
6768
base_kind = kind
6869
base_quote = quote
70+
base_arg_name = arg_name
6971
has_base_arg = true
7072
} else {
7173
attrs << ast.Attr{
72-
name: '${name}_${arg_name}'
73-
has_arg: true
74-
arg: arg
75-
kind: kind
76-
quote: quote
77-
pos: apos.extend(p.prev_tok.pos())
78-
has_at: is_at
74+
name: '${name}_${arg_name}'
75+
has_arg: true
76+
arg: arg
77+
kind: kind
78+
quote: quote
79+
pos: apos.extend(p.prev_tok.pos())
80+
has_at: is_at
81+
call_name: name
82+
call_arg_name: arg_name
7983
}
8084
}
8185
} else if !has_base_arg {
@@ -86,13 +90,14 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
8690
has_base_arg = true
8791
} else {
8892
attrs << ast.Attr{
89-
name: '${name}_${positional_arg_idx}'
90-
has_arg: true
91-
arg: arg
92-
kind: kind
93-
quote: quote
94-
pos: apos.extend(p.prev_tok.pos())
95-
has_at: is_at
93+
name: '${name}_${positional_arg_idx}'
94+
has_arg: true
95+
arg: arg
96+
kind: kind
97+
quote: quote
98+
pos: apos.extend(p.prev_tok.pos())
99+
has_at: is_at
100+
call_name: name
96101
}
97102
positional_arg_idx++
98103
}
@@ -104,13 +109,15 @@ fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast
104109
}
105110
p.check(.rpar)
106111
base_attr := ast.Attr{
107-
name: name
108-
has_arg: base_has_arg
109-
arg: base_arg
110-
kind: base_kind
111-
quote: base_quote
112-
pos: apos.extend(p.prev_tok.pos())
113-
has_at: is_at
112+
name: name
113+
has_arg: base_has_arg
114+
arg: base_arg
115+
kind: base_kind
116+
quote: base_quote
117+
pos: apos.extend(p.prev_tok.pos())
118+
has_at: is_at
119+
call_name: name
120+
call_arg_name: base_arg_name
114121
}
115122
attrs.insert(0, base_attr)
116123
return attrs
@@ -125,16 +132,19 @@ fn (mut p Parser) parse_attr(is_at bool) []ast.Attr {
125132
apos := if is_at { p.peek_token(-2).pos() } else { p.prev_tok.pos() }
126133
if p.tok.kind == .key_unsafe {
127134
p.next()
135+
mut call_name := ''
128136
if p.tok.kind == .lpar {
129137
p.check(.lpar)
130138
p.check(.rpar)
139+
call_name = 'unsafe'
131140
}
132141
return [
133142
ast.Attr{
134-
name: 'unsafe'
135-
kind: kind
136-
pos: apos.extend(p.prev_tok.pos())
137-
has_at: is_at
143+
name: 'unsafe'
144+
kind: kind
145+
pos: apos.extend(p.prev_tok.pos())
146+
has_at: is_at
147+
call_name: call_name
138148
},
139149
]
140150
}

‎vlib/v2/parser/parser.v‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,16 @@ pub fn (mut p Parser) parse_file(filename string, mut file_set token.FileSet) as
6868
if filename == '' {
6969
panic('parser.parse_file empty filename')
7070
}
71+
mut src := ''
72+
mut sw := time.StopWatch{}
7173
if !p.pref.verbose {
7274
unsafe {
7375
goto start_no_time
7476
}
7577
}
76-
mut sw := time.new_stopwatch()
78+
sw = time.new_stopwatch()
7779
start_no_time:
78-
src := os.read_file(filename) or { p.error('error reading `' + filename + '`') }
80+
src = os.read_file(filename) or { p.error('error reading `' + filename + '`') }
7981
p.init(filename, src, mut file_set)
8082
// start
8183
p.next()

0 commit comments

Comments
 (0)