Fix macOS CI with default caching and integer migration (#31154)
* cgen: scope cleanup switch cases for older Apple Clang * checker: let zero assignments leave reference sum variants * math.big: pass signed byte counts to memory helpers * checker: limit zero literal inspection to reference narrowing * checker: stop loop narrowing scans before the current assignment * tests: repair integer migration inputs for macOS self tests * checker: return platform int for translated pointer differences * examples: use signed byte counts for graphics buffers * cgen: preserve headerless mmap bindings in cached modules * fastc: repair pointer difference formatting and bootstrap fallback
This commit is contained in:
1 parent
b4a41308ec
commit
8bd7ffa3f8
19 files changed
+194
-34
No files matched your search
@@ -113,6 +113,15 @@ source changes. Compiler and library edits deliberately preserve progress. Use
|
||||
artifacts needed by later tasks. Do not run multiple `ci` jobs in the same checkout
|
||||
concurrently; they share the checkpoint and build outputs.
|
||||
|
||||
The macOS self-tests use default module caching. Generated cleanup for detached
|
||||
thread results must also compile with older Apple Clang versions that reject
|
||||
declarations immediately after `case` labels. The focused regression checks
|
||||
uncached, cold-cache and warm-cache builds, including nested error payloads:
|
||||
|
||||
```sh
|
||||
./v test vlib/v/gen/c/detached_spawn_cleanup_test.v
|
||||
```
|
||||
|
||||
### Testing the resume behavior
|
||||
|
||||
On macOS or Linux with a working V compiler, run:
|
||||
|
||||
@@ -739,6 +739,7 @@ voidptr // this one is mostly used for [C interoperability](#v-and-c)
|
||||
`isize` and `usize` are deprecated. Replace `isize` with `int` and `usize` with `uint`.
|
||||
Use `uint` for pointer addresses and C `size_t` values to preserve
|
||||
the full unsigned range. Existing spellings still compile with deprecation warnings.
|
||||
Pointer subtraction returns `int`, including in translated C code.
|
||||
|
||||
### 128-bit integers
|
||||
|
||||
@@ -5174,6 +5175,8 @@ fixed-array elements.
|
||||
A mutable sum argument requires a variable of the sum type: it can replace the complete sum
|
||||
with another variant. Wrap a concrete value in a sum variable before passing it with `mut`.
|
||||
Inside a mutable smartcast, modifying a field modifies the selected payload in that sum.
|
||||
Assigning `0` to a sum with integer and reference variants selects the integer variant,
|
||||
including when its current variant is a reference.
|
||||
|
||||
Sum payloads must have finite layout. References, dynamic arrays, and maps break recursive
|
||||
storage cycles; ordinary fields, options, and fixed arrays do not. A function variant stores
|
||||
|
||||
@@ -32,8 +32,8 @@ struct AppState {
|
||||
mut:
|
||||
gg &gg.Context = unsafe { nil }
|
||||
iidx int
|
||||
pixels &u32 = unsafe { &u32(vcalloc(pwidth * pheight * sizeof(u32))) }
|
||||
npixels &u32 = unsafe { &u32(vcalloc(pwidth * pheight * sizeof(u32))) } // all drawing happens here, results are swapped at the end
|
||||
pixels &u32 = unsafe { &u32(vcalloc(pwidth * pheight * int(sizeof(u32)))) }
|
||||
npixels &u32 = unsafe { &u32(vcalloc(pwidth * pheight * int(sizeof(u32)))) } // all drawing happens here, results are swapped at the end
|
||||
view ViewRect = ViewRect{-3.0773593290970673, 1.4952456603855397, -2.019938598189011, 2.3106642054225945}
|
||||
scale int = 1
|
||||
ntasks int = runtime.nr_jobs()
|
||||
|
||||
@@ -17,7 +17,7 @@ mut:
|
||||
@[direct_array_access]
|
||||
fn (mut state AppState) update() {
|
||||
for {
|
||||
unsafe { vmemset(&state.pixels, 0, pwidth * pheight * sizeof(gg.Color)) }
|
||||
unsafe { vmemset(&state.pixels, 0, pwidth * pheight * int(sizeof(gg.Color))) }
|
||||
state.draw_sky() or {}
|
||||
time.sleep(30_000 * time.millisecond)
|
||||
}
|
||||
|
||||
@@ -291,8 +291,8 @@ fn left_shift_digits_in_place(mut a []u64, amount int) {
|
||||
a.grow_len(amount)
|
||||
sptr := &u8(a.data)
|
||||
dptr := &u8(a.data) + u64(amount) * u64(elem_size)
|
||||
vmemmove(dptr, sptr, u64(old_len) * u64(elem_size))
|
||||
vmemset(sptr, 0, u64(amount) * u64(elem_size))
|
||||
vmemmove(dptr, sptr, old_len * elem_size)
|
||||
vmemset(sptr, 0, amount * elem_size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6057,6 +6057,13 @@ fn (g &FlatGen) core_native_header_is_redeclared(source_file string, include_arg
|
||||
return false
|
||||
}
|
||||
relative := path[root.len + 6..]
|
||||
// Cached module headers stand in for every source, but their owner can be
|
||||
// any file in that module. Keep os's mmap declarations headerless on warm builds.
|
||||
if !isnil(g.a) && source_file in g.a.cached_header_sources
|
||||
&& relative.starts_with('os/') && include_arg == '<sys/mman.h>' {
|
||||
return g.target.pointer_bits == 64 && g.target.os in ['macos', 'linux', 'android', 'termux',
|
||||
'freebsd', 'openbsd', 'dragonfly']
|
||||
}
|
||||
if relative.starts_with('term/termios/termios_') && relative.ends_with('.c.v') {
|
||||
return include_arg in ['<termios.h>', '<sys/ioctl.h>']
|
||||
}
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
module c
|
||||
|
||||
import os
|
||||
import v.cmdexec
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_detached_spawn_nested_error_cleanup_compiles_as_c11() ! {
|
||||
cc := os.find_abs_path_of_executable('clang') or { return }
|
||||
dir := os.join_path(os.vtmp_dir(), 'detached_error_cleanup_${os.getpid()}')
|
||||
os.mkdir_all(dir)!
|
||||
defer { os.rmdir_all(dir) or {} }
|
||||
source := os.join_path(dir, 'main.v')
|
||||
os.write_file(source, 'struct NestedError { cause IError }
|
||||
fn (e NestedError) msg() string { return e.cause.msg() }
|
||||
fn (e NestedError) code() int { return 1 }
|
||||
interface Container { cause IError }
|
||||
type Choice = NestedError | int
|
||||
fn fail() ! { return NestedError{cause: error("nested")} }
|
||||
fn boxed() Container { return NestedError{cause: error("boxed")} }
|
||||
fn choice() Choice { return NestedError{cause: error("choice")} }
|
||||
fn main() {
|
||||
spawn fail()
|
||||
spawn boxed()
|
||||
spawn choice()
|
||||
}
|
||||
')!
|
||||
// Modern Clang accepts declarations after case labels as a C23 extension;
|
||||
// older macOS CI compilers reject them. Exercise uncached, cold and warm builds.
|
||||
for mode in ['uncached', 'cold', 'warm'] {
|
||||
mut args := ['-cc', cc, '-cstrict', '-show-c-output', '-cflags',
|
||||
'-std=gnu11 -Wc23-extensions -Wno-unknown-warning-option', '-o',
|
||||
os.join_path(dir, 'program')]
|
||||
if mode == 'uncached' {
|
||||
args << '-nocache'
|
||||
}
|
||||
args << source
|
||||
result := cmdexec.run_in_merged_with_env(@VEXE, args, dir, {
|
||||
'V3CACHE': os.join_path(dir, 'cache')
|
||||
'VJOBS': '1'
|
||||
})
|
||||
assert result.exit_code == 0, '${mode}: ${result.output}'
|
||||
assert !result.output.contains('label followed by a declaration'), '${mode}: ${result.output}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_detached_spawn_cleanup_reaches_deep_thread_results() {
|
||||
mut ast := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&ast)
|
||||
|
||||
@@ -30,6 +30,13 @@ fn test_mmap_header_ownership_is_limited_to_supported_shipped_bindings() {
|
||||
a.cached_header_sources['/cache/os.vh'] = source
|
||||
g.a = &a
|
||||
assert g.core_native_header_is_redeclared('/cache/os.vh', '<sys/mman.h>')
|
||||
a.cached_header_sources['/cache/os.vh'] = '/vroot/vlib/os/args.v'
|
||||
assert g.core_native_header_is_redeclared('/cache/os.vh', '<sys/mman.h>')
|
||||
a.cached_header_sources['/cache/user.vh'] = '/project/os/args.v'
|
||||
assert !g.core_native_header_is_redeclared('/cache/user.vh', '<sys/mman.h>')
|
||||
g.set_target(pref.target_from('linux', 'arm32')!)
|
||||
assert !g.core_native_header_is_redeclared('/cache/os.vh', '<sys/mman.h>')
|
||||
g.set_target(pref.target_from('macos', 'arm64')!)
|
||||
g.no_builtin = true
|
||||
assert !g.core_native_header_is_redeclared(source, '<sys/mman.h>')
|
||||
g.no_builtin = false
|
||||
|
||||
+7
-3
@@ -1738,11 +1738,12 @@ fn (mut g FlatGen) gen_ownership_drop_value_inner(typ types.Type, expr string, d
|
||||
continue
|
||||
}
|
||||
concrete_ct := g.value_c_type(concrete_type)
|
||||
g.writeln('case ${id}:')
|
||||
g.writeln('case ${id}: {')
|
||||
g.indent++
|
||||
g.gen_ownership_drop_value_inner(concrete_type, '*((${concrete_ct}*)${object})', depth + 1, mut expanding)
|
||||
g.writeln('break;')
|
||||
g.indent--
|
||||
g.writeln('}')
|
||||
}
|
||||
g.writeln('default: break;')
|
||||
g.writeln('}')
|
||||
@@ -1760,7 +1761,7 @@ fn (mut g FlatGen) gen_ownership_drop_value_inner(typ types.Type, expr string, d
|
||||
resolved_variant := g.resolve_variant(sum_name_id, variant)
|
||||
variant_type := g.tc.parse_canonical_type_cached(resolved_variant)
|
||||
idx := g.sum_type_index(sum_name_id, resolved_variant)
|
||||
g.writeln('case ${idx}:')
|
||||
g.writeln('case ${idx}: {')
|
||||
g.indent++
|
||||
clean_variant_type := unalias_type(variant_type)
|
||||
if clean_variant_type is types.Pointer {
|
||||
@@ -1774,6 +1775,7 @@ fn (mut g FlatGen) gen_ownership_drop_value_inner(typ types.Type, expr string, d
|
||||
g.writeln('}')
|
||||
g.writeln('break;')
|
||||
g.indent--
|
||||
g.writeln('}')
|
||||
continue
|
||||
}
|
||||
field := sum_field_name(flat.source_type_name(resolved_variant))
|
||||
@@ -1781,6 +1783,7 @@ fn (mut g FlatGen) gen_ownership_drop_value_inner(typ types.Type, expr string, d
|
||||
g.gen_ownership_drop_value_inner(variant_type, payload, depth + 1, mut expanding)
|
||||
g.writeln('break;')
|
||||
g.indent--
|
||||
g.writeln('}')
|
||||
}
|
||||
g.writeln('default: break;')
|
||||
g.writeln('}')
|
||||
@@ -1813,7 +1816,7 @@ fn (mut g FlatGen) gen_ownership_drop_result_error(expr string, depth int, mut e
|
||||
continue
|
||||
}
|
||||
concrete_ct := g.value_c_type(concrete_type)
|
||||
g.writeln('case ${id}:')
|
||||
g.writeln('case ${id}: {')
|
||||
g.indent++
|
||||
if concrete in ['MessageError', 'builtin.MessageError'] {
|
||||
// MessageError is introduced by generated result/error lowering and can be
|
||||
@@ -1824,6 +1827,7 @@ fn (mut g FlatGen) gen_ownership_drop_result_error(expr string, depth int, mut e
|
||||
}
|
||||
g.writeln('break;')
|
||||
g.indent--
|
||||
g.writeln('}')
|
||||
}
|
||||
g.writeln('default: break;')
|
||||
g.writeln('}')
|
||||
|
||||
@@ -185,6 +185,8 @@ fn main() {
|
||||
assert null is &Item
|
||||
assert voidptr(null as &Item) == unsafe { nil }
|
||||
}')!
|
||||
result := os.exec([@VEXE, '-new-compiler', '-nocache', '-no-closures', 'run', path])
|
||||
assert result.exit_code == 0, result.output
|
||||
for flags in [['-nocache'], []string{}] {
|
||||
result := os.exec([@VEXE, '-new-compiler', ...flags, '-no-closures', 'run', path])
|
||||
assert result.exit_code == 0, result.output
|
||||
}
|
||||
}
|
||||
@@ -271,3 +271,30 @@ fn main() {
|
||||
assert !source.contains('extern ptrdiff_t write(i32, const void*'), source
|
||||
declaration_abi_run_c('target_write', source)
|
||||
}
|
||||
|
||||
fn test_fastc_interpolates_native_pointer_differences() {
|
||||
$if !macos && !linux {
|
||||
return
|
||||
}
|
||||
mut prefs := pref.new_preferences()
|
||||
prefs.vroot = @VEXEROOT
|
||||
program := r"module main
|
||||
@[c_extern]
|
||||
fn C.native_difference() C.ptrdiff_t
|
||||
fn main() {
|
||||
difference := C.native_difference()
|
||||
assert '${difference}' == '-15'
|
||||
assert '${difference:05d}' == '-0015'
|
||||
}
|
||||
"
|
||||
for selfhost in [false, true] {
|
||||
prefs.target_libc_headers = selfhost
|
||||
source := if selfhost {
|
||||
declaration_abi_generate('ptrdiff', program, prefs)
|
||||
} else {
|
||||
generate(program, 'ptrdiff.c.v', prefs) or { panic(err) }
|
||||
}
|
||||
assert source.contains('extern ptrdiff_t native_difference('), source
|
||||
declaration_abi_run_c('ptrdiff_${selfhost}', '#include <stddef.h>\n' + source + '\nptrdiff_t native_difference(void) { return -15; }\n')
|
||||
}
|
||||
}
|
||||
@@ -2239,7 +2239,7 @@ fn (mut g Parser) parse_parallel_assignment(initial_names []string, initial_mut
|
||||
component_type := if i < component_types.len {
|
||||
flat.source_type_name(component_types[i])
|
||||
} else {
|
||||
'uint', 'usize'
|
||||
'uint'
|
||||
}
|
||||
c_name := fastc_c_identifier(name)
|
||||
c_type := fastc_output_c_type(component_type)
|
||||
|
||||
@@ -492,11 +492,15 @@ fn (mut g Parser) read_interpolated_string() !string {
|
||||
value_type := fastc_normalize_inferred_type(g.last_expression_type)
|
||||
value_key := g.semantic_type_key(value_type)
|
||||
alias_has_str_method := g.declared_kinds[flat.source_type_name(value_key)] == .alias_ && '${flat.source_type_name(value_key)}.str' in g.functions
|
||||
interpolation_type := if alias_has_str_method {
|
||||
mut interpolation_type := if alias_has_str_method {
|
||||
flat.source_type_name(value_type)
|
||||
} else {
|
||||
flat.source_type_name(g.underlying_alias_type(value_type))
|
||||
}
|
||||
// C pointer differences use the signed pointer-sized integer formatter.
|
||||
if interpolation_type == 'ptrdiff_t' {
|
||||
interpolation_type = 'isize'
|
||||
}
|
||||
mut format_specifier := ''
|
||||
if g.tok == .colon {
|
||||
g.next()
|
||||
|
||||
+2
-2
@@ -22,11 +22,11 @@ fn test_stringified_usize_field_should_be_always_positive() {
|
||||
assert sa.contains('f_isize: -1')
|
||||
assert sa.contains('f_i64: -1')
|
||||
assert sa.contains('f_i32: -1')
|
||||
i := sa.split_into_lines().filter(it.contains('int'))[0]
|
||||
i := sa.split_into_lines().filter(it.contains('f_isize:'))[0]
|
||||
assert i.contains('-'), 'all `i` fields should be negative, but ${i} != ${a.f_isize}'
|
||||
|
||||
assert sa.contains('f_u64: 18446744073709551615')
|
||||
assert sa.contains('f_u32: 4294967295')
|
||||
u := sa.split_into_lines().filter(it.contains('uint'))[0]
|
||||
u := sa.split_into_lines().filter(it.contains('f_usize:'))[0]
|
||||
assert !u.contains('-'), 'all `u` fields should be positive, but ${u} != ${a.f_usize}'
|
||||
}
|
||||
@@ -2,7 +2,7 @@ interface Number2 {}
|
||||
|
||||
fn t(v Number2) {
|
||||
match v {
|
||||
int, i32, int, i64 {
|
||||
int, i32, i64 {
|
||||
x := int(v)
|
||||
println(x)
|
||||
assert x == 42
|
||||
@@ -22,4 +22,6 @@ fn t(v Number2) {
|
||||
|
||||
fn test_cast_interface_value_in_match() {
|
||||
t(int(42))
|
||||
t(i32(42))
|
||||
t(i64(42))
|
||||
}
|
||||
@@ -215,6 +215,10 @@ fn test_translated_pointer_subtraction_retains_pointer_width() {
|
||||
delta := unsafe { end - start }
|
||||
assert typeof(delta).name == 'int'
|
||||
assert delta == 3
|
||||
assert sizeof(delta) == sizeof(voidptr)
|
||||
reverse := unsafe { start - end }
|
||||
assert typeof(reverse).name == 'int'
|
||||
assert reverse == -3
|
||||
}
|
||||
|
||||
fn translated_mixed_branch(flag bool) int {
|
||||
|
||||
@@ -1067,6 +1067,11 @@ fn (tc &TypeChecker) assignment_preserves_smartcast(op flat.Op, lhs_id flat.Node
|
||||
// type (`x = 5` in `if mut x is int`), as in V 0.5.2. Any other value may select
|
||||
// another variant, so the narrowing ends.
|
||||
fn (tc &TypeChecker) assigned_value_keeps_narrowing(rhs_id flat.NodeId, rhs_type &Type, narrowed &Type) bool {
|
||||
// An untyped zero selects a numeric variant rather than a null pointer.
|
||||
if (narrowed is Pointer || (narrowed is Alias && narrowed.base_type is Pointer))
|
||||
&& tc.zero_literal_can_be_pointer(rhs_id, narrowed) {
|
||||
return false
|
||||
}
|
||||
return tc.expr_compatible(rhs_id, rhs_type, narrowed)
|
||||
|| tc.pointer_value_compatible(rhs_type, narrowed)
|
||||
}
|
||||
|
||||
@@ -16564,12 +16564,13 @@ fn (tc &TypeChecker) lexical_smartcast_type_in_parents(id flat.NodeId, key strin
|
||||
// The loop condition re-narrows `key` at the top of every iteration, but a
|
||||
// write to `key` earlier in the same iteration drops that narrowing for the
|
||||
// rest of the body (the dynamic pass deletes the smartcast on assignment).
|
||||
// Only reconstruct the condition smartcast when no preceding body statement,
|
||||
// nor the statement holding `id`, writes `key`.
|
||||
// Only reconstruct the condition smartcast when no statement before `id`
|
||||
// in this iteration writes `key`.
|
||||
if body_index >= 3 && tc.condition_may_smartcast_key(tc.a.child(parent, 1), key) {
|
||||
for binding in tc.extract_smartcasts(tc.a.child(parent, 1)) {
|
||||
if binding.name == key
|
||||
&& !tc.for_body_writes_key_before(parent, body_index, current, key, binding.typ) {
|
||||
&& !tc.for_body_writes_key_before(parent, body_index, current, id, key,
|
||||
binding.typ) {
|
||||
return binding.typ
|
||||
}
|
||||
}
|
||||
@@ -16604,18 +16605,19 @@ fn (tc &TypeChecker) lexical_smartcast_type_in_parents(id flat.NodeId, key strin
|
||||
return none
|
||||
}
|
||||
|
||||
// for_body_writes_key_before reports whether any loop-body statement up to and
|
||||
// including `current` (the statement holding the checked node) ends the narrowing
|
||||
// of `key` to `narrowed` (see subtree_ends_narrowing). Such a write invalidates the
|
||||
// loop-condition narrowing for the rest of that iteration, so the lexical fallback
|
||||
// must not restore it.
|
||||
fn (tc &TypeChecker) for_body_writes_key_before(parent flat.Node, body_index int, current flat.NodeId, key string, narrowed Type) bool {
|
||||
// for_body_writes_key_before reports whether any loop-body statement before
|
||||
// the checked `target` within `current` ends the narrowing of `key` to `narrowed`
|
||||
// (see subtree_ends_narrowing). Such a write invalidates the loop-condition
|
||||
// narrowing for the rest of that iteration, so the lexical fallback must not restore it.
|
||||
fn (tc &TypeChecker) for_body_writes_key_before(parent flat.Node, body_index int, current flat.NodeId, target flat.NodeId, key string, narrowed Type) bool {
|
||||
for i in 3 .. body_index {
|
||||
if tc.subtree_ends_narrowing(tc.a.child(parent, i), key, narrowed, true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return tc.subtree_ends_narrowing(current, key, narrowed, true)
|
||||
// The current assignment has not stored its value while its RHS is resolved.
|
||||
// Resolving it here would query the same loop smartcast recursively.
|
||||
return tc.subtree_writes_key_before(current, target, key, narrowed, true)
|
||||
}
|
||||
|
||||
// branch_writes_key_before reports whether a statement that runs before `target`
|
||||
@@ -16633,15 +16635,23 @@ fn (tc &TypeChecker) branch_writes_key_before(branch_id flat.NodeId, target flat
|
||||
if branch.kind !in [.block, .match_branch] {
|
||||
return false
|
||||
}
|
||||
calls := branch.kind == .block
|
||||
return tc.subtree_writes_key_before(branch_id, target, key, narrowed, branch.kind == .block)
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) subtree_writes_key_before(root flat.NodeId, target flat.NodeId, key string, narrowed Type, calls bool) bool {
|
||||
root_kind := tc.a.node(root).kind
|
||||
mut child := target
|
||||
if child == root {
|
||||
return false
|
||||
}
|
||||
for _ in 0 .. 128 {
|
||||
parent_id := tc.direct_parent_id(child)
|
||||
if !tc.valid_node_id(parent_id) {
|
||||
return false
|
||||
}
|
||||
if parent_id == branch_id {
|
||||
return tc.statements_before_end_narrowing(parent_id, child, key, narrowed, calls)
|
||||
if parent_id == root {
|
||||
return root_kind in [.block, .match_branch]
|
||||
&& tc.statements_before_end_narrowing(parent_id, child, key, narrowed, calls)
|
||||
}
|
||||
if tc.a.node(parent_id).kind in [.block, .match_branch, .for_stmt, .for_in_stmt, .lock_expr,
|
||||
.defer_stmt, .select_branch]
|
||||
@@ -16650,7 +16660,7 @@ fn (tc &TypeChecker) branch_writes_key_before(branch_id flat.NodeId, target flat
|
||||
}
|
||||
child = parent_id
|
||||
}
|
||||
return tc.statements_before_end_narrowing(branch_id, flat.empty_node, key, narrowed, calls)
|
||||
return tc.statements_before_end_narrowing(root, flat.empty_node, key, narrowed, calls)
|
||||
}
|
||||
|
||||
// statements_before_end_narrowing reports whether a child of `parent_id` before
|
||||
@@ -20036,11 +20046,7 @@ fn (tc &TypeChecker) resolve_type_nonliteral(id flat.NodeId) Type {
|
||||
}
|
||||
if node.op in [.plus, .minus] {
|
||||
if node.op == .minus && lt is Pointer && rt is Pointer {
|
||||
return if tc.node_is_in_translated_file(id) {
|
||||
builtin_isize_type
|
||||
} else {
|
||||
builtin_int_type
|
||||
}
|
||||
return builtin_int_type
|
||||
}
|
||||
if lt is Pointer && (rt_raw.is_integer()
|
||||
|| (tc.node_is_in_translated_file(id) && translated_integer_type(rt_raw))) {
|
||||
|
||||
@@ -146,6 +146,45 @@ fn test_lexical_parent_cache_forks_own_slots_and_phase_resets_drop_storage() {
|
||||
assert isnil(forked.lexical_parent_memo)
|
||||
}
|
||||
|
||||
fn test_loop_narrowing_resolves_assignment_rhs_before_its_write() {
|
||||
mut a := flat.FlatAst.new()
|
||||
before := a.add_val(.ident, 'value')
|
||||
lhs := a.add_val(.ident, 'value')
|
||||
rhs_value := a.add_val(.ident, 'value')
|
||||
one := a.add_val(.int_literal, '1')
|
||||
rhs := lexical_parent_cache_children(mut a, .infix, [rhs_value, one])
|
||||
a.nodes[int(rhs)].op = .plus
|
||||
write := lexical_parent_cache_children(mut a, .assign, [lhs, rhs])
|
||||
a.nodes[int(write)].op = .assign
|
||||
after_int := a.add_val(.ident, 'value')
|
||||
other_lhs := a.add_val(.ident, 'value')
|
||||
other_rhs := a.add_val(.string_literal, 'changed')
|
||||
other_write := lexical_parent_cache_children(mut a, .assign, [other_lhs, other_rhs])
|
||||
a.nodes[int(other_write)].op = .assign
|
||||
after_string := a.add_val(.ident, 'value')
|
||||
nested := lexical_parent_cache_children(mut a, .block, [other_write, after_string])
|
||||
cond := lexical_parent_cache_condition(mut a, 'value', 'int')
|
||||
init := a.add(.empty)
|
||||
post := a.add(.empty)
|
||||
loop := lexical_parent_cache_children(mut a, .for_stmt, [init, cond, post, before, write,
|
||||
after_int, nested])
|
||||
lexical_parent_cache_children(mut a, .fn_decl, [loop])
|
||||
for cached in [false, true] {
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cache_lexical_parents = cached
|
||||
tc.build_direct_parent_index(&a)
|
||||
tc.sum_types.set('Value', ['int', 'string'])
|
||||
sum := Type(SumType{ name_id: flat.source_type_id('Value') })
|
||||
tc.cur_scope.insert('value', sum)
|
||||
tc.remember_expr_type(lhs, sum)
|
||||
tc.remember_expr_type(other_lhs, sum)
|
||||
assert tc.lexical_smartcast_type_in_parents(before, 'value', false)? == Type(int_)
|
||||
assert tc.lexical_smartcast_type_in_parents(rhs_value, 'value', false)? == Type(int_)
|
||||
assert tc.lexical_smartcast_type_in_parents(after_int, 'value', false)? == Type(int_)
|
||||
assert tc.lexical_smartcast_type_in_parents(after_string, 'value', false) == none
|
||||
}
|
||||
}
|
||||
|
||||
fn test_lexical_parent_cache_matches_uncached_reachability_and_diagnostics() {
|
||||
path := os.join_path(os.vtmp_dir(), 'v3_lexical_parent_differential_${os.getpid()}.v')
|
||||
os.write_file(path, 'module main
|
||||
|
||||
Reference in new issue
Block a user