Skip to content

Commit d91e56e

Browse files
committed
v3: address PR review batch 13 — unsigned shift in fixed-array lengths, copy generic method-value map to cgen workers
The const fixed-array-length evaluators now handle V's unsigned right shift `>>>`. The string evaluator gains it as a three-char operator (matched before `>>`), and the cgen fixed-array dimension (fixed_array_len_raw) folds any const expression to an integer literal first — `>>>` has no C form, so a digit-leading expression like `[8 >>> 1]int` must not pass through raw. The literal-length guard rejects a wrong element count and the emitted C dimension is valid. (The AST evaluator already handled `.right_shift_unsigned`.) clone_parallel_type_checker now copies generic_method_value_info into each parallel-cgen worker checker. It is the read-only map cgen uses to recover substituted signatures for generic-receiver method values (`Box[int].method` as a callback); without it a worker on a large enough non-Windows build saw an empty map and gen_method_value_closure fell through instead of emitting the wrapper/specialized call. (fn_d_parallel.v was also brought to vfmt formatting.)
1 parent 114a8b2 commit d91e56e

4 files changed

Lines changed: 90 additions & 58 deletions

File tree

vlib/v3/gen/c/cleanc.v

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,15 +1555,17 @@ fn (mut g FlatGen) fixed_array_len_raw(raw_len string, fallback int) string {
15551555
if raw_len.len == 0 {
15561556
return '${fallback}'
15571557
}
1558+
// A literal or const-expression size (`8`, `SEGS + 1`, `1 << 2`, `8 >>> 1`) folds to an
1559+
// integer; emit that literal so the C dimension is always valid — `>>>` has no C form,
1560+
// so a digit-leading expression like `8 >>> 1` must not be passed through raw — and a
1561+
// non-numeric expr isn't c_name-mangled (`SEGS_+_1`) into an undeclared identifier.
1562+
if v := g.tc.const_int_value(raw_len, []string{}) {
1563+
return v.str()
1564+
}
15581565
clean_len := raw_len.replace('_', '')
15591566
if clean_len.len > 0 && clean_len[0] >= `0` && clean_len[0] <= `9` {
15601567
return clean_len
15611568
}
1562-
// A const-expression size (`SEGS + 1`) evaluates to a literal; otherwise the whole
1563-
// string would be c_name-mangled (`SEGS_+_1`) into an undeclared identifier.
1564-
if v := g.tc.const_int_value(raw_len, []string{}) {
1565-
return v.str()
1566-
}
15671569
const_name := g.const_ref_name(raw_len)
15681570
if const_name.len > 0 {
15691571
expr := g.const_expr_to_string(g.const_vals[const_name], []string{})

vlib/v3/gen/c/fn_d_parallel.v

Lines changed: 51 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -352,54 +352,54 @@ fn (mut g FlatGen) fn_ptr_type_key(typ types.FnType) string {
352352
// worker and, under -gc none, never freed.
353353
fn (g &FlatGen) new_parallel_worker(worker_id int) &FlatGen {
354354
return &FlatGen{
355-
sb: strings.new_builder(64_000)
356-
a: unsafe { g.a }
357-
used_fns: g.used_fns
358-
used_fn_names: g.used_fn_names
359-
str_lits: g.str_lits.clone()
360-
str_lit_ids: g.str_lit_ids.clone()
361-
global_types: g.global_types
362-
enum_vals: g.enum_vals
363-
interfaces: g.interfaces
364-
const_vals: g.const_vals
365-
const_modules: g.const_modules
366-
const_init_order: g.const_init_order
367-
global_modules: g.global_modules
368-
global_inits: g.global_inits
369-
global_init_order: g.global_init_order
370-
iface_impls: g.iface_impls
371-
iface_type_ids: g.iface_type_ids
372-
module_init_fns: g.module_init_fns
373-
module_init_fn_modules: g.module_init_fn_modules
374-
module_imports: g.module_imports
375-
tc: g.clone_parallel_type_checker()
376-
has_builtins: g.has_builtins
377-
tmp_count: (worker_id + 1) * 100_000
378-
line_start: true
379-
modules: g.modules
380-
fn_ptr_types: g.fn_ptr_types.clone()
355+
sb: strings.new_builder(64_000)
356+
a: unsafe { g.a }
357+
used_fns: g.used_fns
358+
used_fn_names: g.used_fn_names
359+
str_lits: g.str_lits.clone()
360+
str_lit_ids: g.str_lit_ids.clone()
361+
global_types: g.global_types
362+
enum_vals: g.enum_vals
363+
interfaces: g.interfaces
364+
const_vals: g.const_vals
365+
const_modules: g.const_modules
366+
const_init_order: g.const_init_order
367+
global_modules: g.global_modules
368+
global_inits: g.global_inits
369+
global_init_order: g.global_init_order
370+
iface_impls: g.iface_impls
371+
iface_type_ids: g.iface_type_ids
372+
module_init_fns: g.module_init_fns
373+
module_init_fn_modules: g.module_init_fn_modules
374+
module_imports: g.module_imports
375+
tc: g.clone_parallel_type_checker()
376+
has_builtins: g.has_builtins
377+
tmp_count: (worker_id + 1) * 100_000
378+
line_start: true
379+
modules: g.modules
380+
fn_ptr_types: g.fn_ptr_types.clone()
381381
fixed_array_ret_wrappers: g.fixed_array_ret_wrappers
382-
fn_decl_param_types: g.fn_decl_param_types
383-
fn_decl_ret_types: g.fn_decl_ret_types
384-
struct_decl_infos: g.struct_decl_infos
385-
struct_decl_short_infos: g.struct_decl_short_infos
386-
runtime_inits: g.runtime_inits.clone()
387-
compiler_vroot: g.compiler_vroot
388-
cur_param_names: g.cur_param_names.clone()
389-
cur_param_type_values: g.cur_param_type_values.clone()
390-
cur_param_types: g.cur_param_types.clone()
391-
cur_fn_ret: g.cur_fn_ret
392-
cur_fn_ret_is_optional: g.cur_fn_ret_is_optional
393-
cur_fn_ret_base: g.cur_fn_ret_base
394-
expected_expr_type: g.expected_expr_type
395-
expected_enum: g.expected_enum
396-
needed_optional_types: g.needed_optional_types.clone()
397-
emitted_optional_types: g.emitted_optional_types.clone()
398-
emitted_fns: g.emitted_fns.clone()
399-
array_method_cache: g.array_method_cache.clone()
400-
param_types_cache: g.param_types_cache.clone()
401-
embedded_fields_by_type: g.embedded_fields_by_type
402-
param_types_by_short: g.param_types_by_short
382+
fn_decl_param_types: g.fn_decl_param_types
383+
fn_decl_ret_types: g.fn_decl_ret_types
384+
struct_decl_infos: g.struct_decl_infos
385+
struct_decl_short_infos: g.struct_decl_short_infos
386+
runtime_inits: g.runtime_inits.clone()
387+
compiler_vroot: g.compiler_vroot
388+
cur_param_names: g.cur_param_names.clone()
389+
cur_param_type_values: g.cur_param_type_values.clone()
390+
cur_param_types: g.cur_param_types.clone()
391+
cur_fn_ret: g.cur_fn_ret
392+
cur_fn_ret_is_optional: g.cur_fn_ret_is_optional
393+
cur_fn_ret_base: g.cur_fn_ret_base
394+
expected_expr_type: g.expected_expr_type
395+
expected_enum: g.expected_enum
396+
needed_optional_types: g.needed_optional_types.clone()
397+
emitted_optional_types: g.emitted_optional_types.clone()
398+
emitted_fns: g.emitted_fns.clone()
399+
array_method_cache: g.array_method_cache.clone()
400+
param_types_cache: g.param_types_cache.clone()
401+
embedded_fields_by_type: g.embedded_fields_by_type
402+
param_types_by_short: g.param_types_by_short
403403
}
404404
}
405405

@@ -461,6 +461,10 @@ fn (g &FlatGen) clone_parallel_type_checker() &types.TypeChecker {
461461
diagnostic_files: g.tc.diagnostic_files
462462
cur_fn_ret_type: g.tc.cur_fn_ret_type
463463
smartcasts: g.tc.smartcasts
464+
// Read-only map cgen uses to recover substituted signatures for generic-receiver
465+
// method values (`Box[int].method` as a callback); without it a parallel worker
466+
// sees an empty map and gen_method_value_closure falls through.
467+
generic_method_value_info: g.tc.generic_method_value_info
464468
}
465469
}
466470

vlib/v3/tests/type_checker_errors_test.v

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,3 +643,21 @@ fn test_pr_review_codegen_batch_twelve() {
643643
'struct Point {\n\tx int\n\ty int\n}\nfn main() {\n\tmut arr := []&Point{}\n\tarr << &Point{1, 2}\n\tprintln(int_str(arr[0].x + arr[0].y))\n}\n')
644644
assert pos == '3'
645645
}
646+
647+
// Regression tests for the thirteenth PR-review batch (vlang/v#27557).
648+
fn test_pr_review_codegen_batch_thirteen() {
649+
v3_bin := build_v3()
650+
// V's unsigned right shift `>>>` is a valid constant-length operator. It must fold in
651+
// the const evaluator (so the array dimension is emitted as a numeric literal, since
652+
// `>>>` has no C form) and in the literal-length guard. `8 >>> 1` = 4, `16 >>> 2` = 4,
653+
// `(1 << 5) >>> 2` = 8.
654+
ushift := run_good(v3_bin, 'good_unsigned_shift_fixed_array_len',
655+
'const shamt = 16 >>> 2\nfn main() {\n\ta := [8 >>> 1]int{}\n\tb := [shamt]int{}\n\tc := [(1 << 5) >>> 2]int{}\n\tprintln(int_str(a.len + b.len + c.len))\n}\n')
656+
// 4 + 4 + 8 = 16
657+
assert ushift == '16'
658+
// The fixed-array literal-length guard evaluates `>>>` too: `[1, 2]` (two elements)
659+
// does not match an expected `[8 >>> 1]int` (four), so it is rejected.
660+
run_bad(v3_bin, 'bad_unsigned_shift_fixed_array_literal_len',
661+
'fn take(a [8 >>> 1]int) int {\n\treturn a[0]\n}\nfn main() {\n\t_ := take([1, 2]!)\n}\n',
662+
'cannot use')
663+
}

vlib/v3/types/checker.v

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5370,10 +5370,10 @@ pub fn (tc &TypeChecker) const_int_value(name string, seen []string) ?int {
53705370
return tc.const_int_value(expr[1..expr.len - 1].trim_space(), seen)
53715371
}
53725372
// Operators grouped by precedence level, lowest first: `+ - | ^` (additive/bitwise),
5373-
// then `* / % & << >>` (multiplicative/shift), matching V/Go precedence. Split on the
5374-
// rightmost top-level operator of the lowest level present. Two-character operators
5375-
// (`<<`, `>>`) are matched before single characters; `idx + op.len` skips the operator.
5376-
for level in [['+', '-', '|', '^'], ['*', '/', '%', '&', '<<', '>>']] {
5373+
// then `* / % & << >> >>>` (multiplicative/shift), matching V/Go precedence. Split on
5374+
// the rightmost top-level operator of the lowest level present. Longer operators are
5375+
// matched first (`>>>` before `>>`, two-char before one); `idx + op.len` skips it.
5376+
for level in [['+', '-', '|', '^'], ['*', '/', '%', '&', '<<', '>>', '>>>']] {
53775377
mut idx := -1
53785378
mut op := ''
53795379
mut depth := 0
@@ -5391,6 +5391,13 @@ pub fn (tc &TypeChecker) const_int_value(name string, seen []string) ?int {
53915391
continue
53925392
}
53935393
if depth == 0 {
5394+
three := if i + 3 <= expr.len { expr[i..i + 3] } else { '' }
5395+
if three.len == 3 && three in level {
5396+
idx = i
5397+
op = three
5398+
i += 3
5399+
continue
5400+
}
53945401
two := if i + 2 <= expr.len { expr[i..i + 2] } else { '' }
53955402
if two.len == 2 && two in level {
53965403
idx = i
@@ -5418,7 +5425,7 @@ pub fn (tc &TypeChecker) const_int_value(name string, seen []string) ?int {
54185425
if (op == '/' || op == '%') && r == 0 {
54195426
return none
54205427
}
5421-
if (op == '<<' || op == '>>') && (r < 0 || r >= 64) {
5428+
if (op == '<<' || op == '>>' || op == '>>>') && (r < 0 || r >= 64) {
54225429
return none
54235430
}
54245431
return match op {
@@ -5431,7 +5438,8 @@ pub fn (tc &TypeChecker) const_int_value(name string, seen []string) ?int {
54315438
'^' { l ^ r }
54325439
'&' { l & r }
54335440
'<<' { l << r }
5434-
else { l >> r }
5441+
'>>' { l >> r }
5442+
else { int(u64(l) >> r) }
54355443
}
54365444
}
54375445
return none

0 commit comments

Comments
 (0)