Skip to content

Commit 7651ba0

Browse files
committed
v3: enforce fastc initialization semantics
1 parent c288428 commit 7651ba0

2 files changed

Lines changed: 257 additions & 4 deletions

File tree

vlib/v3/gen/fastc/fastc.v

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ struct FastcStructField {
259259
name string
260260
typ string
261261
is_public bool
262+
is_required bool
262263
module_name string
263264
path string
264265
imports map[string]string
@@ -689,6 +690,56 @@ fn fastc_resolve_source_files(paths []string, prefs &pref.Preferences) ![]FastcS
689690
return sources
690691
}
691692

693+
fn fastc_sources_in_dependency_order(sources []FastcSourceFile) ![]FastcSourceFile {
694+
mut module_order := []string{}
695+
for source_file in sources {
696+
module_name := source_file.header.module_name
697+
if module_name !in module_order {
698+
module_order << module_name
699+
}
700+
}
701+
mut visiting := []string{}
702+
mut visited := []string{}
703+
mut ordered := []FastcSourceFile{cap: sources.len}
704+
for module_name in module_order {
705+
fastc_append_module_sources(module_name, sources, mut visiting, mut visited, mut ordered)!
706+
}
707+
return ordered
708+
}
709+
710+
fn fastc_append_module_sources(module_name string, sources []FastcSourceFile, mut visiting []string, mut visited []string, mut ordered []FastcSourceFile) ! {
711+
if module_name in visited {
712+
return
713+
}
714+
if module_name in visiting {
715+
return error('fastc parser does not support cyclic module dependency involving `${module_name}`')
716+
}
717+
visiting << module_name
718+
mut dependencies := []string{}
719+
for source_file in sources {
720+
if source_file.header.module_name != module_name {
721+
continue
722+
}
723+
imports := source_file.header.imports.clone()
724+
for dependency in imports.values() {
725+
if dependency != module_name && dependency !in dependencies {
726+
dependencies << dependency
727+
}
728+
}
729+
}
730+
dependencies.sort()
731+
for dependency in dependencies {
732+
fastc_append_module_sources(dependency, sources, mut visiting, mut visited, mut ordered)!
733+
}
734+
for source_file in sources {
735+
if source_file.header.module_name == module_name {
736+
ordered << source_file
737+
}
738+
}
739+
visiting.delete(visiting.len - 1)
740+
visited << module_name
741+
}
742+
692743
fn fastc_source_file_matches_backend(path string) bool {
693744
return !path.ends_with('.arm64.v') && !path.ends_with('.amd64.v')
694745
&& !path.ends_with('.native.v') && !path.ends_with('.wasm.v') && !path.ends_with('.rv64.v')
@@ -1004,7 +1055,8 @@ fn collect_global_names(source string, path string, module_name string, prefs &p
10041055
fn fastc_generate_global_declarations(sources []FastcSourceFile, prefs &pref.Preferences, declared_types map[string]bool, declared_kinds map[string]FastcDeclaredTypeKind, struct_fields map[string]map[string]string, struct_field_info map[string][]FastcStructField, functions map[string]FastcFunctionSignature, constants map[string]string, public_constants map[string]bool, constant_types map[string]string, globals map[string]string, mut global_types map[string]string) !FastcGlobalDeclarations {
10051056
mut out := strings.new_builder(1024)
10061057
mut initializers := strings.new_builder(1024)
1007-
for source_file in sources {
1058+
ordered_sources := fastc_sources_in_dependency_order(sources)!
1059+
for source_file in ordered_sources {
10081060
mut file_set := token.FileSet.new()
10091061
mut file := file_set.add_file(source_file.path, source_file.source.len)
10101062
file.index_lines(source_file.source)
@@ -1543,6 +1595,12 @@ fn fastc_emit_struct_declaration(mut scan scanner.Scanner, is_union bool, source
15431595
}
15441596
tok = next_token
15451597
fastc_register_composite_type(field_type, mut composite_types)
1598+
mut is_required := false
1599+
for tok == .attribute {
1600+
mut attribute_is_required := false
1601+
tok, attribute_is_required = fastc_scan_struct_field_attribute(mut scan)!
1602+
is_required = is_required || attribute_is_required
1603+
}
15461604
mut default_source := ''
15471605
if tok == .assign {
15481606
first_default_token := scan.scan()
@@ -1569,6 +1627,7 @@ fn fastc_emit_struct_declaration(mut scan scanner.Scanner, is_union bool, source
15691627
name: field_name
15701628
typ: field_type
15711629
is_public: fields_are_public
1630+
is_required: is_required
15721631
module_name: source_file.header.module_name
15731632
path: source_file.path
15741633
imports: source_file.header.imports.clone()
@@ -1662,8 +1721,16 @@ fn fastc_emit_enum_declaration(mut scan scanner.Scanner, source_file FastcSource
16621721
tok = scan.scan()
16631722
if tok == .assign {
16641723
tok = scan.scan()
1724+
mut sign := 1
1725+
if tok in [.plus, .minus] {
1726+
sign = if tok == .minus { -1 } else { 1 }
1727+
tok = scan.scan()
1728+
}
16651729
if tok == .number {
16661730
value = scan.lit.int()
1731+
if sign < 0 {
1732+
value = -value
1733+
}
16671734
tok = scan.scan()
16681735
} else {
16691736
tok = fastc_skip_field_default_from_token(mut scan, tok)!
@@ -1832,6 +1899,27 @@ fn fastc_skip_attribute(mut scan scanner.Scanner) !token.Token {
18321899
return tok
18331900
}
18341901

1902+
fn fastc_scan_struct_field_attribute(mut scan scanner.Scanner) !(token.Token, bool) {
1903+
mut tok := scan.scan()
1904+
mut depth := 1
1905+
mut is_required := false
1906+
for depth > 0 {
1907+
if tok == .eof {
1908+
return error('fastc parser does not support unfinished struct field attribute')
1909+
}
1910+
if tok == .name && scan.lit == 'required' {
1911+
is_required = true
1912+
}
1913+
if tok == .lsbr {
1914+
depth++
1915+
} else if tok == .rsbr {
1916+
depth--
1917+
}
1918+
tok = scan.scan()
1919+
}
1920+
return tok, is_required
1921+
}
1922+
18351923
fn fastc_skip_balanced_tokens(mut scan scanner.Scanner, first token.Token, open token.Token, close token.Token) !token.Token {
18361924
mut tok := first
18371925
mut depth := 0
@@ -4108,6 +4196,9 @@ fn (mut g Parser) parse_for() !bool {
41084196
|| !fastc_is_integer_expression_type(end_expression_type) {
41094197
return g.unsupported('range bounds of types `${start_expression_type}` and `${end_expression_type}` must both be integers')
41104198
}
4199+
if !fastc_range_types_are_compatible(start_expression_type, end_expression_type) {
4200+
return g.unsupported('range bounds of types `${start_expression_type}` and `${end_expression_type}` must have compatible integer types')
4201+
}
41114202
if start_value := fastc_integer_literal_value(start_expression) {
41124203
if end_value := fastc_integer_literal_value(end_expression) {
41134204
if start_value >= end_value {
@@ -9742,6 +9833,8 @@ fn (g &Parser) validate_struct_literal_field_visibility(tokens []FastcExpression
97429833
}
97439834
close := fastc_matching_delimiter(tokens, open, .lcbr, .rcbr) or { return }
97449835
mut index := open + 1
9836+
mut initialized_fields := map[string]bool{}
9837+
mut has_update := false
97459838
for index < close {
97469839
for index < close && tokens[index].tok in [.semicolon, .comma] {
97479840
index++
@@ -9750,6 +9843,7 @@ fn (g &Parser) validate_struct_literal_field_visibility(tokens []FastcExpression
97509843
break
97519844
}
97529845
if tokens[index].tok == .ellipsis {
9846+
has_update = true
97539847
index++
97549848
for index < close && tokens[index].tok !in [.semicolon, .comma] {
97559849
index++
@@ -9761,6 +9855,7 @@ fn (g &Parser) validate_struct_literal_field_visibility(tokens []FastcExpression
97619855
}
97629856
field_name := tokens[index].lit
97639857
field := g.struct_field_metadata(c_type, field_name) or { return }
9858+
initialized_fields[field_name] = true
97649859
if !g.struct_field_is_visible(field) {
97659860
type_name := g.semantic_type_key(c_type).all_after_last('.')
97669861
return g.unsupported('private field `${type_name}.${field.name}` from imported module `${field.module_name}`')
@@ -9802,6 +9897,14 @@ fn (g &Parser) validate_struct_literal_field_visibility(tokens []FastcExpression
98029897
index++
98039898
}
98049899
}
9900+
if !has_update {
9901+
for field in g.struct_field_info[layout_type] {
9902+
if field.is_required && field.name !in initialized_fields {
9903+
type_name := g.semantic_type_key(c_type).all_after_last('.')
9904+
return g.unsupported('field `${type_name}.${field.name}` must be initialized')
9905+
}
9906+
}
9907+
}
98059908
}
98069909

98079910
fn fastc_matching_rpar(tokens []FastcExpressionToken, open int) ?int {
@@ -10424,11 +10527,40 @@ fn fastc_number_expression_type(literal string) string {
1042410527
}
1042510528

1042610529
fn fastc_integer_literal_value(tokens []FastcExpressionToken) ?i64 {
10427-
if tokens.len != 1 || tokens[0].tok != .number
10428-
|| fastc_number_expression_type(tokens[0].lit) != 'integer literal' {
10530+
mut sign := i64(1)
10531+
mut number_index := 0
10532+
if tokens.len == 2 && tokens[0].tok in [.plus, .minus] {
10533+
sign = if tokens[0].tok == .minus { -1 } else { 1 }
10534+
number_index = 1
10535+
} else if tokens.len != 1 {
1042910536
return none
1043010537
}
10431-
return tokens[0].lit.replace('_', '').i64()
10538+
if tokens[number_index].tok != .number
10539+
|| fastc_number_expression_type(tokens[number_index].lit) != 'integer literal' {
10540+
return none
10541+
}
10542+
mut value := tokens[number_index].lit.replace('_', '').i64()
10543+
if sign < 0 {
10544+
value = -value
10545+
}
10546+
return value
10547+
}
10548+
10549+
fn fastc_range_types_are_compatible(left string, right string) bool {
10550+
if left == right {
10551+
return true
10552+
}
10553+
if fastc_is_integer_literal_expression_type(left)
10554+
&& fastc_is_integer_literal_expression_type(right) {
10555+
return true
10556+
}
10557+
if fastc_is_integer_literal_expression_type(left) {
10558+
return fastc_call_types_are_compatible(left, right)
10559+
}
10560+
if fastc_is_integer_literal_expression_type(right) {
10561+
return fastc_call_types_are_compatible(right, left)
10562+
}
10563+
return false
1043210564
}
1043310565

1043410566
fn fastc_common_arithmetic_type(left string, right string) string {

vlib/v3/gen/fastc/fastc_test.v

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,40 @@ fn main() {
263263
assert c_source.contains('__v_fastc_struct_default.retries=(default_retries());'), c_source
264264
}
265265

266+
fn test_required_struct_fields_must_be_initialized() {
267+
mut prefs := pref.new_preferences()
268+
prefs.building_v = true
269+
mut message := ''
270+
_ := generate('module main
271+
272+
struct Config {
273+
name string @[required]
274+
}
275+
276+
fn main() {
277+
Config{}
278+
}
279+
',
280+
'missing_required_struct_field.v', prefs) or {
281+
message = err.msg()
282+
''
283+
}
284+
assert message.contains('field `Config.name` must be initialized'), message
285+
286+
generate('module main
287+
288+
struct Config {
289+
name string @[required]
290+
}
291+
292+
fn main() {
293+
config := Config{name: "set"}
294+
println(config.name)
295+
}
296+
',
297+
'initialized_required_struct_field.v', prefs) or { panic(err) }
298+
}
299+
266300
fn test_generate_files_rejects_private_imported_struct_fields() {
267301
root := os.join_path(os.vtmp_dir(), 'v3_fastc_private_fields_${os.getpid()}')
268302
os.rmdir_all(root) or {}
@@ -389,6 +423,73 @@ fn main() {
389423
assert c_source.contains('v_fastc_init_globals();'), c_source
390424
}
391425

426+
fn test_imported_global_initializers_run_before_importer_globals() {
427+
root := os.join_path(os.vtmp_dir(), 'v3_fastc_global_order_${os.getpid()}')
428+
os.rmdir_all(root) or {}
429+
os.mkdir_all(os.join_path(root, 'dep')) or { panic(err) }
430+
defer {
431+
os.rmdir_all(root) or {}
432+
}
433+
main_file := os.join_path(root, 'main.v')
434+
dep_file := os.join_path(root, 'dep', 'dep.v')
435+
os.write_file(main_file, 'module main
436+
437+
import dep
438+
439+
__global copied = dep.current()
440+
441+
fn main() {
442+
println(copied)
443+
}
444+
') or {
445+
panic(err)
446+
}
447+
os.write_file(dep_file, 'module dep
448+
449+
__global current_value = 42
450+
451+
pub fn current() int {
452+
return current_value
453+
}
454+
') or {
455+
panic(err)
456+
}
457+
mut prefs := pref.new_preferences()
458+
prefs.module_search_paths = [root]
459+
c_source := generate_files([main_file], prefs) or { panic(err) }
460+
dependency_initializer := c_source.index('dep__current_value = 42;') or { -1 }
461+
importer_initializer := c_source.index('copied = dep__current();') or { -1 }
462+
assert dependency_initializer >= 0, c_source
463+
assert importer_initializer > dependency_initializer, c_source
464+
465+
c_file := os.join_path(root, 'program.c')
466+
bin_file := os.join_path(root, 'program')
467+
os.write_file(c_file, c_source) or { panic(err) }
468+
tcc := os.join_path(prefs.vroot, 'thirdparty', 'tcc', 'tcc.exe')
469+
compile_result := cmdexec.run(tcc, ['-std=gnu11', '-o', bin_file, c_file])
470+
assert compile_result.exit_code == 0, compile_result.output
471+
run_result := cmdexec.run(bin_file, [])
472+
assert run_result.exit_code == 0, run_result.output
473+
assert run_result.output.trim_space() == '42'
474+
}
475+
476+
fn test_negative_enum_discriminants_are_preserved() {
477+
prefs := pref.new_preferences()
478+
c_source := generate('module main
479+
480+
enum Foo {
481+
a = 1
482+
d = -10
483+
e
484+
}
485+
486+
fn main() {}
487+
',
488+
'negative_enum_discriminant.v', prefs) or { panic(err) }
489+
assert c_source.contains('#define Foo__d ((Foo)-10)'), c_source
490+
assert c_source.contains('#define Foo__e ((Foo)-9)'), c_source
491+
}
492+
392493
fn test_select_statements_are_rejected() {
393494
prefs := pref.new_preferences()
394495
mut message := ''
@@ -1025,11 +1126,31 @@ fn test_range_bounds_must_be_integers() {
10251126
}
10261127
}
10271128

1129+
fn test_range_bound_integer_types_must_be_compatible() {
1130+
prefs := pref.new_preferences()
1131+
for source in [
1132+
'module main\nfn main() { for i in u64(0) .. -1 { println(i) } }\n',
1133+
'module main\nfn main() { for i in i64(0) .. u64(3) { println(i) } }\n',
1134+
] {
1135+
mut message := ''
1136+
_ := generate(source, 'incompatible_range_bounds.v', prefs) or {
1137+
message = err.msg()
1138+
''
1139+
}
1140+
assert message.contains('range bounds of types'), message
1141+
assert message.contains('must have compatible integer types'), message
1142+
}
1143+
1144+
generate('module main\nfn main() { for i in u64(0) .. 3 { println(i) } }\n',
1145+
'compatible_range_bound_literal.v', prefs) or { panic(err) }
1146+
}
1147+
10281148
fn test_literal_range_must_not_be_empty() {
10291149
prefs := pref.new_preferences()
10301150
for source in [
10311151
'module main\nfn main() { for i in 4 .. 2 { println(i) } }\n',
10321152
'module main\nfn main() { for i in 2 .. 2 { println(i) } }\n',
1153+
'module main\nfn main() { for i in 4 .. -2 { println(i) } }\n',
10331154
] {
10341155
mut message := ''
10351156
_ := generate(source, 'empty_literal_range.v', prefs) or {

0 commit comments

Comments
 (0)