| 1 | import os |
| 2 | |
| 3 | const mini_calc_vexe = @VEXE |
| 4 | const mini_calc_tests_dir = os.dir(@FILE) |
| 5 | const mini_calc_v3_dir = os.dir(mini_calc_tests_dir) |
| 6 | const mini_calc_vlib_dir = os.dir(mini_calc_v3_dir) |
| 7 | const mini_calc_v3_src = os.join_path(mini_calc_v3_dir, 'v3.v') |
| 8 | const mini_calc_repo_root = os.dir(mini_calc_vlib_dir) |
| 9 | |
| 10 | fn mini_calc_build_v3() string { |
| 11 | v3_bin := os.join_path(os.temp_dir(), 'v3_mini_calc_markused_test_${os.getpid()}') |
| 12 | os.rm(v3_bin) or {} |
| 13 | build := |
| 14 | os.execute('${mini_calc_vexe} -gc none -path "${mini_calc_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${mini_calc_v3_src}') |
| 15 | assert build.exit_code == 0, build.output |
| 16 | return v3_bin |
| 17 | } |
| 18 | |
| 19 | fn mini_calc_write_file(root string, rel string, src string) { |
| 20 | path := os.join_path(root, rel) |
| 21 | os.mkdir_all(os.dir(path)) or { panic(err) } |
| 22 | os.write_file(path, src) or { panic(err) } |
| 23 | } |
| 24 | |
| 25 | fn mini_calc_compile_run(v3_bin string, name string, files map[string]string, main_file string) (string, string) { |
| 26 | root := os.join_path(os.temp_dir(), 'v3_mini_calc_markused_${name}_${os.getpid()}') |
| 27 | os.rmdir_all(root) or {} |
| 28 | mini_calc_write_file(root, 'v.mod', "Module { name: 'mini_calc_markused' }\n") |
| 29 | for rel, src in files { |
| 30 | mini_calc_write_file(root, rel, src) |
| 31 | } |
| 32 | main_path := os.join_path(root, main_file) |
| 33 | bin := os.join_path(root, 'out') |
| 34 | compile := os.execute('${v3_bin} ${main_path} -b c -o ${bin}') |
| 35 | assert compile.exit_code == 0, compile.output |
| 36 | run := os.execute(bin) |
| 37 | assert run.exit_code == 0, run.output |
| 38 | generated := os.read_file(bin + '.c') or { panic(err) } |
| 39 | return run.output.trim_space(), generated |
| 40 | } |
| 41 | |
| 42 | fn mini_calc_compile_bad(v3_bin string, name string, files map[string]string, main_file string) string { |
| 43 | root := os.join_path(os.temp_dir(), 'v3_mini_calc_markused_bad_${name}_${os.getpid()}') |
| 44 | os.rmdir_all(root) or {} |
| 45 | mini_calc_write_file(root, 'v.mod', "Module { name: 'mini_calc_markused_bad' }\n") |
| 46 | for rel, src in files { |
| 47 | mini_calc_write_file(root, rel, src) |
| 48 | } |
| 49 | main_path := os.join_path(root, main_file) |
| 50 | bin := os.join_path(root, 'out') |
| 51 | compile := os.execute('${v3_bin} ${main_path} -b c -o ${bin}') |
| 52 | assert compile.exit_code != 0, '${name}: compile unexpectedly succeeded: ${compile.output}' |
| 53 | return compile.output |
| 54 | } |
| 55 | |
| 56 | fn test_top_level_for_local_receiver_roots_recursive_chain_only() { |
| 57 | v3_bin := mini_calc_build_v3() |
| 58 | output, generated := mini_calc_compile_run(v3_bin, 'for_top_level_receiver', { |
| 59 | 'main.v': 'module main |
| 60 | |
| 61 | struct Parser {} |
| 62 | |
| 63 | fn (mut p Parser) expr() !int { |
| 64 | return p.term()! |
| 65 | } |
| 66 | |
| 67 | fn (mut p Parser) term() !int { |
| 68 | return 8 |
| 69 | } |
| 70 | |
| 71 | fn (mut p Parser) unused() !int { |
| 72 | return 99 |
| 73 | } |
| 74 | |
| 75 | for i := 0; i < 1; i++ { |
| 76 | mut parser := Parser{} |
| 77 | result := parser.expr() or { 0 } |
| 78 | println(int_str(result)) |
| 79 | } |
| 80 | ' |
| 81 | }, 'main.v') |
| 82 | assert output == '8' |
| 83 | assert generated.contains('Parser__expr('), generated |
| 84 | assert generated.contains('Parser__term('), generated |
| 85 | assert !generated.contains('Parser__unused('), generated |
| 86 | } |
| 87 | |
| 88 | fn test_top_level_future_local_receiver_is_not_rooted() { |
| 89 | v3_bin := mini_calc_build_v3() |
| 90 | output := mini_calc_compile_bad(v3_bin, 'future_local_receiver', { |
| 91 | 'main.v': 'module main |
| 92 | |
| 93 | struct Parser {} |
| 94 | |
| 95 | fn (mut p Parser) expr() !int { |
| 96 | return 7 |
| 97 | } |
| 98 | |
| 99 | result := parser.expr() or { 0 } |
| 100 | mut parser := Parser{} |
| 101 | println(int_str(result)) |
| 102 | ' |
| 103 | }, 'main.v') |
| 104 | assert output.contains('unknown function `parser.expr`') || output.contains('unknown__expr') |
| 105 | || output.contains('C compilation failed'), output |
| 106 | } |
| 107 | |
| 108 | fn test_top_level_same_declaration_receiver_is_not_rooted() { |
| 109 | v3_bin := mini_calc_build_v3() |
| 110 | output := mini_calc_compile_bad(v3_bin, 'same_decl_receiver', { |
| 111 | 'main.v': 'module main |
| 112 | |
| 113 | struct Parser {} |
| 114 | |
| 115 | fn (mut p Parser) expr() !int { |
| 116 | return 7 |
| 117 | } |
| 118 | |
| 119 | parser := parser.expr() or { 0 } |
| 120 | println(int_str(parser)) |
| 121 | ' |
| 122 | }, 'main.v') |
| 123 | assert output.contains('unknown function `parser.expr`') || output.contains('unknown__expr') |
| 124 | || output.contains('C compilation failed'), output |
| 125 | } |
| 126 | |
| 127 | fn test_top_level_multi_decl_same_statement_receiver_is_not_rooted() { |
| 128 | v3_bin := mini_calc_build_v3() |
| 129 | output := mini_calc_compile_bad(v3_bin, 'multi_decl_same_statement_receiver', { |
| 130 | 'main.v': 'module main |
| 131 | |
| 132 | struct Parser {} |
| 133 | |
| 134 | fn (mut p Parser) expr() !int { |
| 135 | return 7 |
| 136 | } |
| 137 | |
| 138 | parser, result := Parser{}, parser.expr() or { 0 } |
| 139 | _ := parser |
| 140 | println(int_str(result)) |
| 141 | ' |
| 142 | }, 'main.v') |
| 143 | assert output.contains('unknown function `parser.expr`') || output.contains('unknown__expr') |
| 144 | || output.contains('C compilation failed'), output |
| 145 | } |
| 146 | |
| 147 | fn test_top_level_block_and_if_scope_do_not_leak_receiver_type() { |
| 148 | v3_bin := mini_calc_build_v3() |
| 149 | output, generated := mini_calc_compile_run(v3_bin, 'block_scope', { |
| 150 | 'main.v': 'module main |
| 151 | |
| 152 | struct Parser {} |
| 153 | |
| 154 | fn (mut p Parser) expr() !int { |
| 155 | return 1 |
| 156 | } |
| 157 | |
| 158 | { |
| 159 | mut parser := Parser{} |
| 160 | _ := parser |
| 161 | } |
| 162 | if true { |
| 163 | mut parser := Parser{} |
| 164 | _ := parser |
| 165 | } |
| 166 | println("ok") |
| 167 | ' |
| 168 | }, 'main.v') |
| 169 | assert output == 'ok' |
| 170 | assert !generated.contains('Parser__expr('), generated |
| 171 | } |
| 172 | |
| 173 | fn test_explicit_main_ignores_top_level_receiver_calls() { |
| 174 | v3_bin := mini_calc_build_v3() |
| 175 | output, generated := mini_calc_compile_run(v3_bin, 'explicit_main', { |
| 176 | 'main.v': 'module main |
| 177 | |
| 178 | struct Parser {} |
| 179 | |
| 180 | fn (mut p Parser) expr() !int { |
| 181 | return p.term()! |
| 182 | } |
| 183 | |
| 184 | fn (mut p Parser) term() !int { |
| 185 | return 7 |
| 186 | } |
| 187 | |
| 188 | mut parser := Parser{} |
| 189 | _ := parser.expr() or { 0 } |
| 190 | |
| 191 | fn main() { |
| 192 | println("entry") |
| 193 | } |
| 194 | ' |
| 195 | }, 'main.v') |
| 196 | assert output == 'entry' |
| 197 | assert !generated.contains('Parser__expr('), generated |
| 198 | assert !generated.contains('Parser__term('), generated |
| 199 | } |
| 200 | |
| 201 | fn test_top_level_local_receiver_does_not_root_imported_homonym() { |
| 202 | v3_bin := mini_calc_build_v3() |
| 203 | output, generated := mini_calc_compile_run(v3_bin, 'imported_homonym', { |
| 204 | 'main.v': 'module main |
| 205 | |
| 206 | import othermod |
| 207 | |
| 208 | struct Parser {} |
| 209 | |
| 210 | fn (mut p Parser) expr() !int { |
| 211 | return 5 |
| 212 | } |
| 213 | |
| 214 | mut parser := Parser{} |
| 215 | println(int_str(parser.expr() or { 0 })) |
| 216 | println(int_str(othermod.touch())) |
| 217 | ' |
| 218 | 'othermod/mod.v': 'module othermod |
| 219 | |
| 220 | pub struct Parser {} |
| 221 | |
| 222 | pub fn touch() int { |
| 223 | return 2 |
| 224 | } |
| 225 | |
| 226 | pub fn (mut p Parser) expr() !int { |
| 227 | return 100 |
| 228 | } |
| 229 | ' |
| 230 | }, 'main.v') |
| 231 | assert output == '5\n2' |
| 232 | assert generated.contains('Parser__expr('), generated |
| 233 | assert !generated.contains('othermod__Parser__expr('), generated |
| 234 | } |
| 235 | |
| 236 | fn test_top_level_local_receiver_shadows_module_alias() { |
| 237 | v3_bin := mini_calc_build_v3() |
| 238 | output, generated := mini_calc_compile_run(v3_bin, 'local_receiver_alias_shadow', { |
| 239 | 'main.v': 'module main |
| 240 | |
| 241 | import parsermod as parser |
| 242 | |
| 243 | struct Parser {} |
| 244 | |
| 245 | fn (mut p Parser) expr() !int { |
| 246 | return 12 |
| 247 | } |
| 248 | |
| 249 | mut parser := Parser{} |
| 250 | println(int_str(parser.expr() or { 0 })) |
| 251 | ' |
| 252 | 'parsermod/mod.v': 'module parsermod |
| 253 | |
| 254 | pub fn expr() int { |
| 255 | return 3 |
| 256 | } |
| 257 | ' |
| 258 | }, 'main.v') |
| 259 | assert output == '12' |
| 260 | assert generated.contains('Parser__expr('), generated |
| 261 | assert !generated.contains('parsermod__expr('), generated |
| 262 | } |
| 263 | |
| 264 | fn test_top_level_import_alias_then_local_receiver_shadow_direct_calls() { |
| 265 | v3_bin := mini_calc_build_v3() |
| 266 | output, generated := mini_calc_compile_run(v3_bin, 'import_alias_then_local_receiver_shadow', { |
| 267 | 'main.v': 'module main |
| 268 | |
| 269 | import parsermod as parser |
| 270 | |
| 271 | struct Parser {} |
| 272 | |
| 273 | fn (mut p Parser) expr() !int { |
| 274 | return 99 |
| 275 | } |
| 276 | |
| 277 | println(int_str(parser.expr())) |
| 278 | mut parser := Parser{} |
| 279 | println(int_str(parser.expr() or { 0 })) |
| 280 | ' |
| 281 | 'parsermod/mod.v': 'module parsermod |
| 282 | |
| 283 | pub fn expr() int { |
| 284 | return 17 |
| 285 | } |
| 286 | ' |
| 287 | }, 'main.v') |
| 288 | assert output == '17\n99' |
| 289 | assert generated.contains('parsermod__expr('), generated |
| 290 | assert generated.contains('Parser__expr('), generated |
| 291 | } |
| 292 | |
| 293 | fn test_mini_calculator_recursive_descent_compiles_and_runs() { |
| 294 | v3_bin := mini_calc_build_v3() |
| 295 | example := os.join_path(mini_calc_repo_root, 'examples', 'mini_calculator_recursive_descent.v') |
| 296 | bin := os.join_path(os.temp_dir(), 'v3_mini_calculator_recursive_descent_${os.getpid()}') |
| 297 | compile := os.execute('${v3_bin} ${example} -b c -o ${bin}') |
| 298 | assert compile.exit_code == 0, compile.output |
| 299 | run := os.execute("printf '2 * (5-1)\\nexit\\n' | ${bin}") |
| 300 | assert run.exit_code == 0, run.output |
| 301 | assert run.output.contains('8'), run.output |
| 302 | generated := os.read_file(bin + '.c') or { panic(err) } |
| 303 | assert generated.contains('Parser__expr('), generated |
| 304 | assert generated.contains('Parser__term('), generated |
| 305 | assert generated.contains('Parser__factor('), generated |
| 306 | assert generated.contains('Parser__number('), generated |
| 307 | } |
| 308 | |