Skip to content

Commit 0f6f0e4

Browse files
committed
v3: preserve import order and validate declared casts
1 parent 56cb2d7 commit 0f6f0e4

2 files changed

Lines changed: 209 additions & 14 deletions

File tree

vlib/v3/gen/fastc/fastc.v

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ mut:
274274
struct FastcSourceHeader {
275275
module_name string
276276
imports map[string]string
277+
import_order []string
277278
blank_imports []string
278279
has_globals bool
279280
}
@@ -736,6 +737,7 @@ fn fastc_resolve_source_files(paths []string, prefs &pref.Preferences) ![]FastcS
736737
header = FastcSourceHeader{
737738
module_name: queued.module_name
738739
imports: header.imports
740+
import_order: header.import_order
739741
blank_imports: header.blank_imports
740742
has_globals: header.has_globals
741743
}
@@ -773,9 +775,12 @@ fn fastc_resolve_source_files(paths []string, prefs &pref.Preferences) ![]FastcS
773775
}
774776

775777
fn fastc_header_imported_modules(header FastcSourceHeader) []string {
776-
mut modules := header.imports.values()
777-
modules << header.blank_imports
778-
return modules
778+
if header.import_order.len > 0 {
779+
return header.import_order.clone()
780+
}
781+
mut fallback := header.imports.values()
782+
fallback << header.blank_imports
783+
return fallback
779784
}
780785

781786
fn fastc_sources_in_dependency_order(sources []FastcSourceFile) ![]FastcSourceFile {
@@ -870,7 +875,6 @@ fn fastc_append_module_sources(module_name string, sources []FastcSourceFile, mu
870875
}
871876
}
872877
}
873-
dependencies.sort()
874878
for dependency in dependencies {
875879
fastc_append_module_sources(dependency, sources, mut visiting, mut visited, mut ordered)!
876880
}
@@ -897,6 +901,7 @@ fn fastc_scan_source_header(source string, path string, prefs &pref.Preferences)
897901
scan.init(file, source)
898902
mut module_name := ''
899903
mut imports := map[string]string{}
904+
mut import_order := []string{}
900905
mut blank_imports := []string{}
901906
mut has_globals := false
902907
mut brace_depth := 0
@@ -953,6 +958,9 @@ fn fastc_scan_source_header(source string, path string, prefs &pref.Preferences)
953958
fastc_register_import_alias(import_path, alias, path, mut imports, mut
954959
blank_imports)!
955960
fastc_register_selective_imports(import_path, selected_names, path, mut imports)!
961+
if import_path !in import_order {
962+
import_order << import_path
963+
}
956964
tok = next_token
957965
}
958966
if tok == .rpar {
@@ -963,18 +971,28 @@ fn fastc_scan_source_header(source string, path string, prefs &pref.Preferences)
963971
import_path, alias, selected_names, next_token := fastc_scan_import(mut scan, tok, path)!
964972
fastc_register_import_alias(import_path, alias, path, mut imports, mut blank_imports)!
965973
fastc_register_selective_imports(import_path, selected_names, path, mut imports)!
974+
if import_path !in import_order {
975+
import_order << import_path
976+
}
966977
tok = next_token
967978
}
968979
if module_name == '' {
969980
module_name = 'main'
970981
}
971982
if prefs.building_v && prefs.backend == 'fastc' && imports['driver'] == 'v3.driver'
972983
&& 'fastcdriver' in imports {
973-
imports['driver'] = imports['fastcdriver']
984+
fastcdriver_module := imports['fastcdriver']
985+
imports['driver'] = fastcdriver_module
986+
for i, imported_module in import_order {
987+
if imported_module == 'v3.driver' {
988+
import_order[i] = fastcdriver_module
989+
}
990+
}
974991
}
975992
return FastcSourceHeader{
976993
module_name: module_name
977994
imports: imports
995+
import_order: import_order
978996
blank_imports: blank_imports
979997
has_globals: has_globals
980998
}
@@ -10685,6 +10703,19 @@ fn (g &Parser) is_enum_type_name(name string) bool {
1068510703
return g.declared_kinds[type_key] == .enum_
1068610704
}
1068710705

10706+
fn (g &Parser) declared_cast_type_key(tokens []FastcExpressionToken, name_index int) ?string {
10707+
if name_index >= 2 && tokens[name_index - 1].tok == .dot && tokens[name_index - 2].tok == .name {
10708+
module_name := g.imports[tokens[name_index - 2].lit] or { return none }
10709+
type_key := fastc_type_key(module_name, tokens[name_index].lit)
10710+
return if type_key in g.declared_types { type_key } else { none }
10711+
}
10712+
if name_index > 0 && tokens[name_index - 1].tok == .dot {
10713+
return none
10714+
}
10715+
return fastc_resolve_declared_type_key(g.module_name, tokens[name_index].lit, g.imports,
10716+
g.declared_types)
10717+
}
10718+
1068810719
fn (g &Parser) validate_expression_calls(tokens []FastcExpressionToken) ! {
1068910720
mut i := 0
1069010721
for i + 1 < tokens.len {
@@ -10824,16 +10855,15 @@ fn (g &Parser) validate_expression_calls(tokens []FastcExpressionToken) ! {
1082410855
i = call_end + 1
1082510856
continue
1082610857
}
10827-
if i == 0 || tokens[i - 1].tok != .dot {
10828-
if _ := fastc_resolve_declared_type_key(g.module_name, name, g.imports,
10829-
g.declared_types)
10830-
{
10831-
if call_args.len != 1 {
10832-
return g.unsupported('cast `${name}` with ${call_args.len} arguments')
10833-
}
10834-
i = call_end + 1
10835-
continue
10858+
if type_key := g.declared_cast_type_key(tokens, i) {
10859+
if call_args.len != 1 {
10860+
return g.unsupported('cast `${name}` with ${call_args.len} arguments')
1083610861
}
10862+
g.validate_declared_cast(type_key, call_args[0], tokens[i].unsafe_depth > 0)!
10863+
i = call_end + 1
10864+
continue
10865+
}
10866+
if i == 0 || tokens[i - 1].tok != .dot {
1083710867
if primitive_type := fastc_primitive_c_type(name) {
1083810868
if call_args.len != 1 {
1083910869
return g.unsupported('cast `${name}` with ${call_args.len} arguments')
@@ -10967,6 +10997,51 @@ fn (g &Parser) validate_primitive_cast(target_type string, operand []FastcExpres
1096710997
return g.unsupported('cast from `${actual_type}` to `${target_type}`')
1096810998
}
1096910999

11000+
fn (g &Parser) validate_declared_cast(type_key string, operand []FastcExpressionToken, in_unsafe bool) ! {
11001+
if g.selfhost {
11002+
return
11003+
}
11004+
target_type := fastc_c_declared_type_name(type_key)
11005+
actual_type := fastc_normalize_inferred_type(g.infer_expression_type(operand)!)
11006+
if actual_type == '' {
11007+
return g.unsupported('unverifiable operand type for cast to `${target_type}`')
11008+
}
11009+
match g.declared_kinds[type_key] {
11010+
.alias_ {
11011+
target_base := g.underlying_alias_type(target_type)
11012+
if target_base == target_type {
11013+
return g.unsupported('unverifiable declared alias cast to `${target_type}`')
11014+
}
11015+
if g.declared_kinds[g.semantic_type_key(target_base)] == .enum_ {
11016+
return g.validate_declared_enum_cast(target_base, actual_type, in_unsafe)
11017+
}
11018+
actual_base := g.underlying_alias_type(actual_type)
11019+
if actual_type == target_type || actual_base == target_base
11020+
|| g.primitive_cast_types_are_compatible(actual_base, target_base, in_unsafe) {
11021+
return
11022+
}
11023+
return g.unsupported('cast from `${actual_type}` to `${target_type}` (alias to `${target_base}`)')
11024+
}
11025+
.enum_ {
11026+
return g.validate_declared_enum_cast(target_type, actual_type, in_unsafe)
11027+
}
11028+
else {}
11029+
}
11030+
}
11031+
11032+
fn (g &Parser) validate_declared_enum_cast(target_type string, actual_type string, in_unsafe bool) ! {
11033+
actual_base := g.underlying_alias_type(actual_type)
11034+
if g.semantic_type_key(actual_base) == g.semantic_type_key(target_type) {
11035+
return
11036+
}
11037+
if !fastc_is_integer_expression_type(actual_base) {
11038+
return g.unsupported('cast from `${actual_type}` to enum `${target_type}`')
11039+
}
11040+
if !in_unsafe {
11041+
return g.unsupported('cast from `${actual_type}` to enum `${target_type}` outside an `unsafe` block')
11042+
}
11043+
}
11044+
1097011045
fn (g &Parser) primitive_cast_types_are_compatible(actual_type string, target_type string, in_unsafe bool) bool {
1097111046
if actual_type == target_type {
1097211047
return true

vlib/v3/gen/fastc/fastc_test.v

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ fn init() {
160160
panic(err)
161161
}
162162
assert header.blank_imports == ['alpha', 'beta']
163+
assert header.import_order == ['alpha', 'beta']
163164
assert '_' !in header.imports
164165
c_source := generate_files([main_file], prefs) or { panic(err) }
165166
assert c_source.contains('\talpha__init();'), c_source
@@ -1111,6 +1112,85 @@ pub fn ping() {}
11111112
assert run_result.output.trim_space() == 'dep init\nmain init\nmain\nmain defer\nmain cleanup\ndep cleanup'
11121113
}
11131114

1115+
fn test_module_lifecycle_preserves_source_import_order() {
1116+
root := os.join_path(os.vtmp_dir(), 'v3_fastc_import_order_${os.getpid()}')
1117+
os.rmdir_all(root) or {}
1118+
os.mkdir_all(os.join_path(root, 'zed')) or { panic(err) }
1119+
os.mkdir_all(os.join_path(root, 'alpha')) or { panic(err) }
1120+
defer {
1121+
os.rmdir_all(root) or {}
1122+
}
1123+
main_file := os.join_path(root, 'main.v')
1124+
os.write_file(main_file, "module main
1125+
1126+
import zed
1127+
import alpha
1128+
1129+
fn main() {
1130+
zed.ping()
1131+
alpha.ping()
1132+
println('main')
1133+
}
1134+
") or {
1135+
panic(err)
1136+
}
1137+
os.write_file(os.join_path(root, 'zed', 'zed.v'), "module zed
1138+
1139+
fn init() {
1140+
println('zed init')
1141+
}
1142+
1143+
fn cleanup() {
1144+
println('zed cleanup')
1145+
}
1146+
1147+
pub fn ping() {}
1148+
") or {
1149+
panic(err)
1150+
}
1151+
os.write_file(os.join_path(root, 'alpha', 'alpha.v'), "module alpha
1152+
1153+
fn init() {
1154+
println('alpha init')
1155+
}
1156+
1157+
fn cleanup() {
1158+
println('alpha cleanup')
1159+
}
1160+
1161+
pub fn ping() {}
1162+
") or {
1163+
panic(err)
1164+
}
1165+
mut prefs := pref.new_preferences()
1166+
prefs.module_search_paths = [root]
1167+
header := fastc_scan_source_header(os.read_file(main_file) or { panic(err) }, main_file, prefs) or {
1168+
panic(err)
1169+
}
1170+
assert header.import_order == ['zed', 'alpha']
1171+
c_source := generate_files([main_file], prefs) or { panic(err) }
1172+
startup_source := c_source.all_after('static void v_fastc_init_globals(void) {')
1173+
zed_init := startup_source.index('\tzed__init();') or { -1 }
1174+
alpha_init := startup_source.index('\talpha__init();') or { -1 }
1175+
assert zed_init >= 0, c_source
1176+
assert alpha_init > zed_init, c_source
1177+
cleanup_source := c_source.all_after('static void v_fastc_cleanup_modules(void) {')
1178+
alpha_cleanup := cleanup_source.index('\talpha__cleanup();') or { -1 }
1179+
zed_cleanup := cleanup_source.index('\tzed__cleanup();') or { -1 }
1180+
assert alpha_cleanup >= 0, c_source
1181+
assert zed_cleanup > alpha_cleanup, c_source
1182+
1183+
c_file := os.join_path(root, 'program.c')
1184+
bin_file := os.join_path(root, 'program')
1185+
os.write_file(c_file, c_source) or { panic(err) }
1186+
tcc := os.join_path(prefs.vroot, 'thirdparty', 'tcc', 'tcc.exe')
1187+
compile_result := cmdexec.run(tcc, ['-std=gnu11', '-o', bin_file, c_file])
1188+
assert compile_result.exit_code == 0, compile_result.output
1189+
run_result := cmdexec.run(bin_file, [])
1190+
assert run_result.exit_code == 0, run_result.output
1191+
assert run_result.output.trim_space() == 'zed init\nalpha init\nmain\nalpha cleanup\nzed cleanup'
1192+
}
1193+
11141194
fn test_module_initializer_signatures_are_validated() {
11151195
prefs := pref.new_preferences()
11161196
for source in [
@@ -1733,6 +1813,46 @@ fn main() {
17331813
assert c_source.contains('println(((bool)(0)));'), c_source
17341814
}
17351815

1816+
fn test_declared_cast_operands_are_validated() {
1817+
prefs := pref.new_preferences()
1818+
for source, expected in {
1819+
'module main\ntype MyType = string\nfn main() { println(MyType(5)) }\n': 'alias to `string`'
1820+
"module main\nenum Color { red blue }\nfn main() { println(Color('red')) }\n": 'to enum `Color`'
1821+
'module main\nenum Color { red blue }\nfn main() { println(Color(1)) }\n': 'outside an `unsafe` block'
1822+
} {
1823+
mut message := ''
1824+
_ := generate(source, 'invalid_declared_cast.v', prefs) or {
1825+
message = err.msg()
1826+
''
1827+
}
1828+
assert message.contains(expected), message
1829+
}
1830+
1831+
c_source := generate("module main
1832+
1833+
type Label = string
1834+
type Count = int
1835+
1836+
enum Color {
1837+
red
1838+
blue
1839+
}
1840+
1841+
fn main() {
1842+
label := Label('ok')
1843+
count := Count(2)
1844+
color := unsafe { Color(1) }
1845+
println(label == Label('ok'))
1846+
println(int(count))
1847+
println(color)
1848+
}
1849+
",
1850+
'valid_declared_casts.v', prefs) or { panic(err) }
1851+
assert c_source.contains('((Label)("ok"))'), c_source
1852+
assert c_source.contains('((Count)(2))'), c_source
1853+
assert c_source.contains('((Color)(1))'), c_source
1854+
}
1855+
17361856
fn test_defer_is_emitted_when_its_lexical_scope_exits() {
17371857
prefs := pref.new_preferences()
17381858
c_source := generate('module main

0 commit comments

Comments
 (0)