compiler: avoid repeated self-compilation lookups and copies (#31197)
* compiler: reduce repeated self-compilation lookups and copies Reuse immutable type and call metadata, avoid unnecessary name work and node copies, and accelerate standard map, string and builder operations. Add focused coverage for cache invalidation and borrowed storage lifetime. * docs: record paired self-compilation results
This commit is contained in:
1 parent
3cb0db1bf7
commit
6cce632507
88 files changed
+7062
-491
No files matched your search
@@ -47,6 +47,103 @@ fn string_index_byte(data &u8, size int, needle u8) int {
|
||||
}
|
||||
}
|
||||
|
||||
// string_index_pair searches adjacent bytes without reading outside the span.
|
||||
@[inline]
|
||||
fn string_index_pair(data &u8, size int, first u8, second u8) int {
|
||||
$if prod && !tinyc && !msvc && !native && little_endian {
|
||||
if size >= 8 && size <= 32 {
|
||||
low_bits := u64(0x7f7f7f7f7f7f7f7f)
|
||||
repeated_a := u64(first) * u64(0x0101010101010101)
|
||||
repeated_b := u64(second) * u64(0x0101010101010101)
|
||||
mut offset := 0
|
||||
for {
|
||||
mut word := u64(0)
|
||||
// Seven adjacent pairs fit in this bounded, unaligned window.
|
||||
unsafe { C.memcpy(&word, data + offset, 8) }
|
||||
da := word ^ repeated_a
|
||||
// Exact masks prevent a borrow from creating a false adjacent match.
|
||||
za := ~(((da & low_bits) + low_bits) | da | low_bits)
|
||||
mut zb := za
|
||||
if first != second {
|
||||
db := word ^ repeated_b
|
||||
zb = ~(((db & low_bits) + low_bits) | db | low_bits)
|
||||
}
|
||||
mask := za & (zb >> 8)
|
||||
if mask != 0 {
|
||||
return offset + int(C.__builtin_ctzll(mask) >> 3)
|
||||
}
|
||||
if offset + 8 == size { return -1 }
|
||||
offset = if offset + 15 > size { size - 8 } else { offset + 7 }
|
||||
}
|
||||
}
|
||||
}
|
||||
last_start := size - 2
|
||||
for i := 0; i <= last_start; i++ {
|
||||
if unsafe { data[i] } != first {
|
||||
next := string_index_byte(unsafe { data + i + 1 }, last_start - i, first)
|
||||
if next < 0 { return -1 }
|
||||
i += next + 1
|
||||
}
|
||||
if unsafe { data[i + 1] } == second { return i }
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// string_contains_byte checks readable bytes without computing a match position.
|
||||
@[inline]
|
||||
fn string_contains_byte(data &u8, size int, needle u8) bool {
|
||||
if size <= 0 {
|
||||
return false
|
||||
}
|
||||
$if freestanding || vinix || native || v2_native_windows_pe_minimal ? {
|
||||
return string_index_byte(data, size, needle) >= 0
|
||||
} $else {
|
||||
$if prod && !tinyc && !msvc && !native {
|
||||
if size <= 32 {
|
||||
if size >= 8 {
|
||||
ones := u64(0x0101010101010101)
|
||||
repeated := u64(needle) * ones
|
||||
mut head := u64(0)
|
||||
mut tail := u64(0)
|
||||
// Every unaligned load stays inside the readable span.
|
||||
unsafe {
|
||||
C.memcpy(&head, data, 8)
|
||||
C.memcpy(&tail, data + size - 8, 8)
|
||||
}
|
||||
head ^= repeated
|
||||
tail ^= repeated
|
||||
mut mask := ((head - ones) & ~head) | ((tail - ones) & ~tail)
|
||||
if size > 16 {
|
||||
unsafe {
|
||||
C.memcpy(&head, data + 8, 8)
|
||||
C.memcpy(&tail, data + size - 16, 8)
|
||||
}
|
||||
head ^= repeated
|
||||
tail ^= repeated
|
||||
mask |= ((head - ones) & ~head) | ((tail - ones) & ~tail)
|
||||
}
|
||||
return mask & u64(0x8080808080808080) != 0
|
||||
}
|
||||
if size >= 4 {
|
||||
ones := u32(0x01010101)
|
||||
repeated := u32(needle) * ones
|
||||
mut head := u32(0)
|
||||
mut tail := u32(0)
|
||||
unsafe {
|
||||
C.memcpy(&head, data, 4)
|
||||
C.memcpy(&tail, data + size - 4, 4)
|
||||
}
|
||||
head ^= repeated
|
||||
tail ^= repeated
|
||||
return (((head - ones) & ~head) | ((tail - ones) & ~tail)) & u32(0x80808080) != 0
|
||||
}
|
||||
return unsafe { data[0] == needle || data[size >> 1] == needle || data[size - 1] == needle }
|
||||
}
|
||||
}
|
||||
return !isnil(unsafe { C.memchr(data, i32(needle), uint(size)) })
|
||||
}
|
||||
}
|
||||
|
||||
// string_last_index_byte searches complete words without reading outside the span.
|
||||
@[inline]
|
||||
fn string_last_index_byte(data &u8, size int, needle u8) int {
|
||||
|
||||
@@ -124,10 +124,12 @@ fn map_hash_int_2(pkey voidptr) u64 {
|
||||
return C.wyhash64(*unsafe { &u16(pkey) }, 0)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn map_hash_int_4(pkey voidptr) u64 {
|
||||
return C.wyhash64(*unsafe { &u32(pkey) }, 0)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn map_hash_int_8(pkey voidptr) u64 {
|
||||
return C.wyhash64(*unsafe { &u64(pkey) }, 0)
|
||||
}
|
||||
|
||||
+2
-1
@@ -479,7 +479,8 @@ fn (m &map) key_to_index(pkey voidptr) (u32, u32) {
|
||||
m.hash_fn(pkey)
|
||||
}
|
||||
index := hash & m.even_index
|
||||
meta := ((hash >> m.shift) & hash_mask) | probe_inc
|
||||
// Valid table growth cannot exhaust the 64-bit hash before the u32 index.
|
||||
meta := ((hash >> (m.shift & 63)) & hash_mask) | probe_inc
|
||||
return u32(index), u32(meta)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
@[has_globals]
|
||||
module builtin
|
||||
|
||||
__global (
|
||||
callback_kind_hash_calls int
|
||||
callback_kind_eq_calls int
|
||||
)
|
||||
|
||||
fn callback_kind_custom_hash(key voidptr) u64 {
|
||||
callback_kind_hash_calls++
|
||||
return map_hash_int_8(key) ^ u64(0xa0761d6478bd642f)
|
||||
}
|
||||
|
||||
fn callback_kind_custom_eq(a voidptr, b voidptr) bool {
|
||||
callback_kind_eq_calls++
|
||||
return map_eq_int_8(a, b)
|
||||
}
|
||||
|
||||
fn check_callback_kind_tags(values &map, hash_kind u8, eq_kind u8) {
|
||||
$if v3_backend ? {
|
||||
assert values.data.hash_kind == hash_kind
|
||||
assert values.data.eq_kind == eq_kind
|
||||
}
|
||||
}
|
||||
|
||||
fn check_callback_kind_reads(values &map, count int) {
|
||||
for i in 0 .. count + 1 {
|
||||
key := u64(i) << 40
|
||||
assert values.exists(&key) == (i < count)
|
||||
found := values.get_check(&key)
|
||||
zero := -1
|
||||
if i < count {
|
||||
assert found != unsafe { nil }
|
||||
assert unsafe { *&int(found) } == i + 10
|
||||
assert unsafe { *&int(values.get(&key, &zero)) } == i + 10
|
||||
} else {
|
||||
assert found == unsafe { nil }
|
||||
assert values.get(&key, &zero) == voidptr(&zero)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_callback_kinds_preserve_independent_custom_callbacks_and_clones() {
|
||||
for mode in 0 .. 4 {
|
||||
hash_fn := if mode & 1 != 0 { callback_kind_custom_hash } else { map_hash_int_8 }
|
||||
eq_fn := if mode & 2 != 0 { callback_kind_custom_eq } else { map_eq_int_8 }
|
||||
mut values := new_map(int(sizeof(u64)), int(sizeof(int)), hash_fn, eq_fn,
|
||||
map_clone_int_8, map_free_nop)
|
||||
hash_kind := if mode & 1 != 0 { u8(0) } else { u8(8) }
|
||||
eq_kind := if mode & 2 != 0 { u8(0) } else { u8(8) }
|
||||
check_callback_kind_tags(&values, hash_kind, eq_kind)
|
||||
mut empty := unsafe { values.clone() }
|
||||
check_callback_kind_tags(&empty, hash_kind, eq_kind)
|
||||
check_callback_kind_reads(&empty, 0)
|
||||
for i in 0 .. 32 {
|
||||
key := u64(i) << 40
|
||||
value := i + 10
|
||||
values.set(&key, &value)
|
||||
check_callback_kind_reads(&values, i + 1)
|
||||
}
|
||||
callback_kind_hash_calls = 0
|
||||
callback_kind_eq_calls = 0
|
||||
check_callback_kind_reads(&values, 32)
|
||||
assert (callback_kind_hash_calls > 0) == (mode & 1 != 0)
|
||||
assert (callback_kind_eq_calls > 0) == (mode & 2 != 0)
|
||||
mut dense := unsafe { values.clone() }
|
||||
check_callback_kind_reads(&dense, 32)
|
||||
check_callback_kind_tags(&dense, hash_kind, eq_kind)
|
||||
values.reserve(4096)
|
||||
mut sparse := unsafe { values.clone() }
|
||||
check_callback_kind_reads(&sparse, 32)
|
||||
check_callback_kind_tags(&sparse, hash_kind, eq_kind)
|
||||
values.clear()
|
||||
check_callback_kind_tags(&values, hash_kind, eq_kind)
|
||||
mut moved := sparse.move()
|
||||
check_callback_kind_reads(&moved, 32)
|
||||
check_callback_kind_reads(&sparse, 0)
|
||||
check_callback_kind_tags(&sparse, hash_kind, eq_kind)
|
||||
unsafe {
|
||||
empty.free()
|
||||
dense.free()
|
||||
moved.free()
|
||||
sparse.free()
|
||||
values.free()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_callback_kinds_cover_standard_constructors_and_header_reset() {
|
||||
mut strings := map[string]int{}
|
||||
strings['one'] = 1
|
||||
strings['two'] = 2
|
||||
strings['three'] = 3
|
||||
assert strings['three'] == 3
|
||||
mut small := map[u32]int{}
|
||||
for i in 0 .. 32 {
|
||||
small[u32(i) << 24] = i
|
||||
}
|
||||
for i in 0 .. 32 {
|
||||
assert small[u32(i) << 24] == i
|
||||
}
|
||||
$if v3_backend ? {
|
||||
assert strings.data.hash_kind == 1
|
||||
assert strings.data.eq_kind & 15 == 1
|
||||
assert small.data.hash_kind == 4
|
||||
assert small.data.eq_kind == 4
|
||||
}
|
||||
key := u64(1) << 40
|
||||
value := 42
|
||||
$if v3_backend ? {
|
||||
mut data := new_map_data(int(sizeof(u64)), int(sizeof(int)), map_hash_int_8,
|
||||
map_eq_int_8, map_clone_int_8, map_free_nop)
|
||||
assert data.hash_kind == 8
|
||||
assert data.eq_kind == 8
|
||||
data.set(&key, &value)
|
||||
unsafe { data.free() }
|
||||
assert data.hash_kind == 0
|
||||
assert data.eq_kind == 0
|
||||
unsafe { free(data) }
|
||||
}
|
||||
mut dense := new_map_with_dense_array(int(sizeof(u64)), int(sizeof(int)),
|
||||
map_hash_int_8, callback_kind_custom_eq, map_clone_int_8, map_free_nop,
|
||||
DenseArray{ key_bytes: int(sizeof(u64)), value_bytes: int(sizeof(int)) }, unsafe { nil })
|
||||
check_callback_kind_tags(&dense, 8, 0)
|
||||
dense.set(&key, &value)
|
||||
assert dense.exists(&key)
|
||||
assert unsafe { *&int(dense.get_check(&key)) } == value
|
||||
unsafe {
|
||||
strings.free()
|
||||
small.free()
|
||||
dense.free()
|
||||
}
|
||||
}
|
||||
+160
-48
@@ -207,6 +207,9 @@ mut:
|
||||
cached_hashbits u8
|
||||
// Used for right-shifting out used hashbits
|
||||
shift u8
|
||||
// Callback tags occupy existing padding and preserve custom hash/equality independently.
|
||||
hash_kind u8
|
||||
eq_kind u8
|
||||
// Array storing key-values (ordered)
|
||||
key_values DenseArray
|
||||
// Pointer to meta-data:
|
||||
@@ -215,14 +218,16 @@ mut:
|
||||
metas &u32
|
||||
// Extra metas that allows for no ranging when incrementing
|
||||
// index in the hashmap
|
||||
extra_metas u32
|
||||
has_string_keys bool
|
||||
hash_fn MapHashFn
|
||||
key_eq_fn MapEqFn
|
||||
clone_fn MapCloneFn
|
||||
free_fn MapFreeFn
|
||||
extra_metas u32
|
||||
hash_fn MapHashFn
|
||||
key_eq_fn MapEqFn
|
||||
clone_fn MapCloneFn
|
||||
free_fn MapFreeFn
|
||||
// Number of key-values currently in the hashmap
|
||||
count int
|
||||
// Conservative residues of cloned standard string keys; deletions retain a superset.
|
||||
string_length_bits u64
|
||||
string_first_bits u64
|
||||
}
|
||||
|
||||
// map is the pointer-sized internal representation of a V `map` type.
|
||||
@@ -350,10 +355,80 @@ fn map_free_string(pkey voidptr) {
|
||||
fn map_free_nop(_ voidptr) {
|
||||
}
|
||||
|
||||
// Classify once when constructing the map; zero preserves custom callbacks.
|
||||
@[inline]
|
||||
fn map_hash_kind(callback MapHashFn) u8 {
|
||||
return if voidptr(callback) == voidptr(map_hash_string) {
|
||||
u8(1)
|
||||
} else if voidptr(callback) == voidptr(map_hash_int_4) {
|
||||
u8(4)
|
||||
} else if voidptr(callback) == voidptr(map_hash_int_8) {
|
||||
u8(8)
|
||||
} else {
|
||||
u8(0)
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn map_eq_kind(callback MapEqFn) u8 {
|
||||
return if voidptr(callback) == voidptr(map_eq_string) {
|
||||
u8(1)
|
||||
} else if voidptr(callback) == voidptr(map_eq_int_4) {
|
||||
u8(4)
|
||||
} else if voidptr(callback) == voidptr(map_eq_int_8) {
|
||||
u8(8)
|
||||
} else {
|
||||
u8(0)
|
||||
}
|
||||
}
|
||||
|
||||
// The extra bit enables the filter without repeating callback comparisons on reads.
|
||||
// A custom clone is excluded because it can change the stored string bytes.
|
||||
@[inline]
|
||||
fn map_length_filter_eq_kind(hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn) u8 {
|
||||
kind := map_eq_kind(key_eq_fn)
|
||||
if kind == 1 && voidptr(hash_fn) == voidptr(map_hash_string)
|
||||
&& voidptr(clone_fn) == voidptr(map_clone_string) {
|
||||
return kind | u8(16)
|
||||
}
|
||||
return kind
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn map_string_length_bit(key voidptr) u64 {
|
||||
return u64(1) << (u32(unsafe { (&string(key)).len }) & u32(63))
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn map_string_first_bit(key voidptr) u64 {
|
||||
value := unsafe { &string(key) }
|
||||
first := if value.len == 0 { u8(0) } else { unsafe { value.str[0] } }
|
||||
return u64(1) << (u32(first) & u32(63))
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn (m &VMapData) rejects_string_key(key voidptr) bool {
|
||||
return m.eq_kind & 16 != 0
|
||||
&& (m.string_length_bits & map_string_length_bit(key) == 0
|
||||
|| m.string_first_bits & map_string_first_bit(key) == 0)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn (mut m VMapData) remember_string_key(key voidptr) {
|
||||
if m.eq_kind & 16 == 0 {
|
||||
return
|
||||
}
|
||||
m.string_length_bits |= map_string_length_bit(key)
|
||||
m.string_first_bits |= map_string_first_bit(key)
|
||||
if m.string_length_bits == u64(0xffffffffffffffff)
|
||||
&& m.string_first_bits == u64(0xffffffffffffffff) {
|
||||
// Neither saturated filter can reject another lookup until clear.
|
||||
m.eq_kind &= u8(0xef)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_map_data(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn,
|
||||
free_fn MapFreeFn) &VMapData {
|
||||
// for now assume anything bigger than a pointer is a string
|
||||
has_string_keys := key_bytes > int(sizeof(voidptr))
|
||||
// Arenas retain old metadata slabs, so reserve a modest probe tail before
|
||||
// insertion instead of repeatedly copying the slab to add four entries.
|
||||
mut initial_extra_metas := u32(extra_metas_inc)
|
||||
@@ -366,11 +441,12 @@ fn new_map_data(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn Map
|
||||
even_index: init_even_index
|
||||
cached_hashbits: max_cached_hashbits
|
||||
shift: init_log_capicity
|
||||
hash_kind: map_hash_kind(hash_fn)
|
||||
eq_kind: map_length_filter_eq_kind(hash_fn, key_eq_fn, clone_fn)
|
||||
key_values: DenseArray{ key_bytes: key_bytes, value_bytes: value_bytes }
|
||||
metas: unsafe { nil }
|
||||
extra_metas: initial_extra_metas
|
||||
count: 0
|
||||
has_string_keys: has_string_keys
|
||||
hash_fn: hash_fn
|
||||
key_eq_fn: key_eq_fn
|
||||
clone_fn: clone_fn
|
||||
@@ -380,25 +456,31 @@ fn new_map_data(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn Map
|
||||
|
||||
fn new_map_with_dense_array(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn,
|
||||
clone_fn MapCloneFn, free_fn MapFreeFn, key_values DenseArray, metas &u32) map {
|
||||
has_string_keys := key_bytes > int(sizeof(voidptr))
|
||||
return map{
|
||||
data: &VMapData{
|
||||
key_bytes: key_bytes
|
||||
value_bytes: value_bytes
|
||||
even_index: init_even_index
|
||||
cached_hashbits: max_cached_hashbits
|
||||
shift: init_log_capicity
|
||||
key_values: key_values
|
||||
metas: unsafe { metas }
|
||||
extra_metas: extra_metas_inc
|
||||
count: 0
|
||||
has_string_keys: has_string_keys
|
||||
hash_fn: hash_fn
|
||||
key_eq_fn: key_eq_fn
|
||||
clone_fn: clone_fn
|
||||
free_fn: free_fn
|
||||
mut data := &VMapData{
|
||||
key_bytes: key_bytes
|
||||
value_bytes: value_bytes
|
||||
even_index: init_even_index
|
||||
cached_hashbits: max_cached_hashbits
|
||||
shift: init_log_capicity
|
||||
hash_kind: map_hash_kind(hash_fn)
|
||||
eq_kind: map_length_filter_eq_kind(hash_fn, key_eq_fn, clone_fn)
|
||||
key_values: key_values
|
||||
metas: unsafe { metas }
|
||||
extra_metas: extra_metas_inc
|
||||
count: 0
|
||||
hash_fn: hash_fn
|
||||
key_eq_fn: key_eq_fn
|
||||
clone_fn: clone_fn
|
||||
free_fn: free_fn
|
||||
}
|
||||
if data.eq_kind & 16 != 0 {
|
||||
for i in 0 .. key_values.len {
|
||||
if key_values.has_index(i) {
|
||||
data.remember_string_key(key_values.key(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
return map{ data: data }
|
||||
}
|
||||
|
||||
fn new_map(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn) map {
|
||||
@@ -470,6 +552,9 @@ fn (mut m VMapData) clear() {
|
||||
if m.metas == unsafe { nil } {
|
||||
return
|
||||
}
|
||||
m.string_length_bits = 0
|
||||
m.string_first_bits = 0
|
||||
m.eq_kind = map_length_filter_eq_kind(m.hash_fn, m.key_eq_fn, m.clone_fn)
|
||||
unsafe {
|
||||
if m.key_values.all_deleted != 0 {
|
||||
free(m.key_values.all_deleted)
|
||||
@@ -503,24 +588,35 @@ fn (m &VMapData) panic_nil_map_hash_fn() {
|
||||
// the callback for all other key representations and custom comparators.
|
||||
@[inline]
|
||||
fn (m &VMapData) keys_equal(a voidptr, b voidptr) bool {
|
||||
if voidptr(m.key_eq_fn) == voidptr(map_eq_string) {
|
||||
if m.eq_kind & 1 != 0 {
|
||||
return map_eq_string(a, b)
|
||||
}
|
||||
if m.eq_kind == 4 {
|
||||
return map_eq_int_4(a, b)
|
||||
}
|
||||
if m.eq_kind == 8 {
|
||||
return map_eq_int_8(a, b)
|
||||
}
|
||||
return m.key_eq_fn(a, b)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn (m &VMapData) key_to_index(pkey voidptr) (u32, u32) {
|
||||
if voidptr(m.hash_fn) == unsafe { nil } {
|
||||
m.panic_nil_map_hash_fn()
|
||||
}
|
||||
hash := if voidptr(m.hash_fn) == voidptr(map_hash_string) {
|
||||
hash := if m.hash_kind & 1 != 0 {
|
||||
map_hash_string(pkey)
|
||||
} else if m.hash_kind == 4 {
|
||||
map_hash_int_4(pkey)
|
||||
} else if m.hash_kind == 8 {
|
||||
map_hash_int_8(pkey)
|
||||
} else {
|
||||
if voidptr(m.hash_fn) == unsafe { nil } {
|
||||
m.panic_nil_map_hash_fn()
|
||||
}
|
||||
m.hash_fn(pkey)
|
||||
}
|
||||
index := hash & m.even_index
|
||||
meta := ((hash >> m.shift) & hash_mask) | probe_inc
|
||||
// Valid table growth cannot exhaust the 64-bit hash before the u32 index.
|
||||
meta := ((hash >> (m.shift & 63)) & hash_mask) | probe_inc
|
||||
return u32(index), u32(meta)
|
||||
}
|
||||
|
||||
@@ -652,6 +748,7 @@ fn (mut m VMapData) set_general(key voidptr, value voidptr) {
|
||||
pkey := m.key_values.key(kv_index)
|
||||
pvalue := m.key_values.value(kv_index)
|
||||
m.clone_fn(pkey, key)
|
||||
m.remember_string_key(pkey)
|
||||
vmemcpy(pvalue, value, m.value_bytes)
|
||||
}
|
||||
m.meta_greater(index, meta, u32(kv_index))
|
||||
@@ -845,6 +942,9 @@ fn (m &VMapData) get_nonempty(key voidptr, zero voidptr) voidptr {
|
||||
}
|
||||
return zero
|
||||
}
|
||||
if m.rejects_string_key(key) {
|
||||
return zero
|
||||
}
|
||||
mut index, mut meta := m.key_to_index(key)
|
||||
for {
|
||||
stored_meta := unsafe { m.metas[index] }
|
||||
@@ -895,6 +995,9 @@ fn (m &VMapData) get_check_nonempty(key voidptr) voidptr {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if m.rejects_string_key(key) {
|
||||
return 0
|
||||
}
|
||||
mut index, mut meta := m.key_to_index(key)
|
||||
for {
|
||||
stored_meta := unsafe { m.metas[index] }
|
||||
@@ -990,6 +1093,9 @@ fn (m &VMapData) exists_nonempty(key voidptr) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
if m.rejects_string_key(key) {
|
||||
return false
|
||||
}
|
||||
mut index, mut meta := m.key_to_index(key)
|
||||
for {
|
||||
stored_meta := unsafe { m.metas[index] }
|
||||
@@ -1256,23 +1362,26 @@ fn (m &VMapData) clone() &VMapData {
|
||||
}
|
||||
metasize := int(sizeof(u32) * (m.even_index + 2 + m.extra_metas))
|
||||
res := &VMapData{
|
||||
key_bytes: m.key_bytes
|
||||
value_bytes: m.value_bytes
|
||||
even_index: m.even_index
|
||||
cached_hashbits: m.cached_hashbits
|
||||
shift: m.shift
|
||||
key_values: unsafe { m.key_values.clone() }
|
||||
metas: unsafe { &u32(malloc_noscan(metasize)) }
|
||||
extra_metas: m.extra_metas
|
||||
count: m.count
|
||||
has_string_keys: m.has_string_keys
|
||||
hash_fn: m.hash_fn
|
||||
key_eq_fn: m.key_eq_fn
|
||||
clone_fn: m.clone_fn
|
||||
free_fn: m.free_fn
|
||||
key_bytes: m.key_bytes
|
||||
value_bytes: m.value_bytes
|
||||
even_index: m.even_index
|
||||
cached_hashbits: m.cached_hashbits
|
||||
shift: m.shift
|
||||
hash_kind: m.hash_kind
|
||||
eq_kind: m.eq_kind
|
||||
key_values: unsafe { m.key_values.clone() }
|
||||
metas: unsafe { &u32(malloc_noscan(metasize)) }
|
||||
extra_metas: m.extra_metas
|
||||
count: m.count
|
||||
string_length_bits: m.string_length_bits
|
||||
string_first_bits: m.string_first_bits
|
||||
hash_fn: m.hash_fn
|
||||
key_eq_fn: m.key_eq_fn
|
||||
clone_fn: m.clone_fn
|
||||
free_fn: m.free_fn
|
||||
}
|
||||
unsafe { vmemcpy(res.metas, m.metas, metasize) }
|
||||
if !m.has_string_keys {
|
||||
if m.key_bytes <= int(sizeof(voidptr)) {
|
||||
return res
|
||||
}
|
||||
// clone keys
|
||||
@@ -1350,6 +1459,8 @@ fn (m &VMapData) free() {
|
||||
}
|
||||
// TODO: the next lines assume that callback functions are static and independent from each particular
|
||||
// map instance. Closures may invalidate that assumption, so revisit when RC for closures works.
|
||||
m.hash_kind = 0
|
||||
m.eq_kind = 0
|
||||
m.hash_fn = nil
|
||||
m.key_eq_fn = nil
|
||||
m.clone_fn = nil
|
||||
@@ -1361,7 +1472,8 @@ fn (m &VMapData) free() {
|
||||
m.cached_hashbits = 0
|
||||
m.shift = 0
|
||||
m.extra_metas = 0
|
||||
m.has_string_keys = false
|
||||
m.string_length_bits = 0
|
||||
m.string_first_bits = 0
|
||||
m.count = 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
module builtin
|
||||
|
||||
fn map_shift_test_hash(key voidptr) u64 {
|
||||
return unsafe { *&u64(key) }
|
||||
}
|
||||
|
||||
fn test_map_hash_shift_mask_preserves_all_in_range_shifts() {
|
||||
$if v3_backend ? {
|
||||
mut state := VMapData{
|
||||
even_index: 0x7ffffffe
|
||||
hash_fn: map_shift_test_hash
|
||||
metas: unsafe { nil }
|
||||
}
|
||||
for shift in 0 .. 64 {
|
||||
state.shift = u8(shift)
|
||||
for key in [u64(0), 1, 0x8000000000000000, 0xffffffffffffffff, 0x123456789abcdef0] {
|
||||
index, meta := state.key_to_index(&key)
|
||||
assert index == u32(key & 0x7ffffffe)
|
||||
assert meta == u32(((key >> shift) & hash_mask) | probe_inc)
|
||||
}
|
||||
mut key := u64(0x9e3779b97f4a7c15)
|
||||
for _ in 0 .. 1024 {
|
||||
// Xorshift visits mixed high and low bits without overflowing arithmetic.
|
||||
key ^= key << 13
|
||||
key ^= key >> 7
|
||||
key ^= key << 17
|
||||
index, meta := state.key_to_index(&key)
|
||||
assert index == u32(key & 0x7ffffffe)
|
||||
assert meta == u32(((key >> shift) & hash_mask) | probe_inc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_hash_shift_stays_within_hash_until_index_exhausted() {
|
||||
// Model the growth recurrence without allocating exponentially large tables.
|
||||
mut even_index := u64(init_even_index)
|
||||
mut cached := max_cached_hashbits
|
||||
mut shift := init_log_capicity
|
||||
mut expansions := 0
|
||||
for {
|
||||
assert even_index <= 0xfffffffe
|
||||
assert shift == init_log_capicity || shift == init_log_capicity + max_cached_hashbits
|
||||
assert shift < 64
|
||||
next := ((even_index + 2) << 1) - 2
|
||||
if next > 0xfffffffe {
|
||||
break
|
||||
}
|
||||
even_index = next
|
||||
if cached == 0 {
|
||||
shift += max_cached_hashbits
|
||||
cached = max_cached_hashbits
|
||||
} else {
|
||||
cached--
|
||||
}
|
||||
expansions++
|
||||
}
|
||||
assert expansions == 27
|
||||
assert shift == 21
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
@[has_globals]
|
||||
module builtin
|
||||
|
||||
__global (
|
||||
length_filter_hash_calls int
|
||||
length_filter_eq_calls int
|
||||
length_filter_clone_calls int
|
||||
)
|
||||
|
||||
fn length_filter_custom_hash(key voidptr) u64 {
|
||||
length_filter_hash_calls++
|
||||
return map_hash_string(key)
|
||||
}
|
||||
|
||||
fn length_filter_custom_eq(a voidptr, b voidptr) bool {
|
||||
length_filter_eq_calls++
|
||||
return map_eq_string(a, b)
|
||||
}
|
||||
|
||||
fn length_filter_custom_clone(destination voidptr, source voidptr) {
|
||||
length_filter_clone_calls++
|
||||
map_clone_string(destination, source)
|
||||
}
|
||||
|
||||
fn length_filter_shortening_clone(destination voidptr, source voidptr) {
|
||||
length_filter_clone_calls++
|
||||
map_clone_string(destination, source)
|
||||
unsafe { (&string(destination)).len-- }
|
||||
}
|
||||
|
||||
fn length_filter_first_byte_clone(destination voidptr, source voidptr) {
|
||||
length_filter_clone_calls++
|
||||
map_clone_string(destination, source)
|
||||
unsafe { (&string(destination)).str[0] = u8(255) }
|
||||
}
|
||||
|
||||
fn assert_length_filter_read(values &map, key string, expected int) {
|
||||
assert values.exists(&key) == (expected >= 0)
|
||||
found := values.get_check(&key)
|
||||
if expected >= 0 {
|
||||
assert found != unsafe { nil }
|
||||
assert unsafe { *&int(found) } == expected
|
||||
} else {
|
||||
assert found == unsafe { nil }
|
||||
}
|
||||
zero := -1
|
||||
assert unsafe { *&int(values.get(&key, &zero)) } == expected
|
||||
}
|
||||
|
||||
fn new_length_filter_map() map {
|
||||
return new_map(int(sizeof(string)), int(sizeof(int)), map_hash_string, map_eq_string,
|
||||
map_clone_string, map_free_string)
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_handles_length_residues_and_clones() {
|
||||
$if v3_backend ? {
|
||||
mut values := new_length_filter_map()
|
||||
keys := ['', 'a'.repeat(31), 'b'.repeat(32), 'c'.repeat(63), 'd'.repeat(64), 'e'.repeat(129),
|
||||
'a\x00b', '\x00z', 'é', '界', 'f'.repeat(127), 'g'.repeat(128)]
|
||||
for i, key in keys {
|
||||
value := i + 10
|
||||
values.set(&key, &value)
|
||||
}
|
||||
assert values.data.eq_kind == 17
|
||||
expected_bits := u64(1) | (u64(1) << 31) | (u64(1) << 32) | (u64(1) << 63) |
|
||||
(u64(1) << 1) | (u64(1) << 2) | (u64(1) << 3)
|
||||
assert values.data.string_length_bits == expected_bits
|
||||
expected_first := u64(1) | (u64(1) << 33) | (u64(1) << 34) | (u64(1) << 35) |
|
||||
(u64(1) << 36) | (u64(1) << 37) | (u64(1) << 38) | (u64(1) << 3) |
|
||||
(u64(1) << 39)
|
||||
assert values.data.string_first_bits == expected_first
|
||||
missing := 'xxxx'
|
||||
assert values.data.rejects_string_key(&missing)
|
||||
mut direct := unsafe { values.clone() }
|
||||
assert direct.data.string_length_bits == values.data.string_length_bits
|
||||
assert direct.data.string_first_bits == values.data.string_first_bits
|
||||
values.reserve(4096)
|
||||
mut sparse := unsafe { values.clone() }
|
||||
assert sparse.data.string_length_bits == values.data.string_length_bits
|
||||
assert sparse.data.string_first_bits == values.data.string_first_bits
|
||||
for i, key in keys {
|
||||
assert_length_filter_read(&values, key, i + 10)
|
||||
assert_length_filter_read(&direct, key, i + 10)
|
||||
assert_length_filter_read(&sparse, key, i + 10)
|
||||
}
|
||||
assert_length_filter_read(&values, 'xx', -1)
|
||||
assert_length_filter_read(&values, 'z'.repeat(31), -1)
|
||||
key := keys[1]
|
||||
updated := 77
|
||||
bits := values.data.string_length_bits
|
||||
first_bits := values.data.string_first_bits
|
||||
values.set(&key, &updated)
|
||||
assert values.data.string_length_bits == bits
|
||||
assert values.data.string_first_bits == first_bits
|
||||
assert_length_filter_read(&values, key, updated)
|
||||
unsafe {
|
||||
direct.free()
|
||||
sparse.free()
|
||||
values.free()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_survives_deletion_compaction_and_insert_on_read() {
|
||||
$if v3_backend ? {
|
||||
mut values := new_length_filter_map()
|
||||
for i in 0 .. 64 {
|
||||
key := 'entry-${100 + i}'
|
||||
values.set(&key, &i)
|
||||
}
|
||||
bits := values.data.string_length_bits
|
||||
first_bits := values.data.string_first_bits
|
||||
for i in 0 .. 32 {
|
||||
key := 'entry-${100 + i}'
|
||||
unsafe { values.delete(&key) }
|
||||
}
|
||||
assert values.data.key_values.len == 32
|
||||
assert values.data.key_values.deletes == 0
|
||||
assert values.data.string_length_bits == bits
|
||||
assert values.data.string_first_bits == first_bits
|
||||
values.reserve(4096)
|
||||
for i in 0 .. 64 {
|
||||
assert_length_filter_read(&values, 'entry-${100 + i}', if i >= 32 { i } else { -1 })
|
||||
}
|
||||
key := 'new-key-longer'
|
||||
zero := 99
|
||||
assert unsafe { *&int(values.get_and_set(&key, &zero)) } == zero
|
||||
assert !values.data.rejects_string_key(&key)
|
||||
assert_length_filter_read(&values, key, zero)
|
||||
unsafe { values.free() }
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_saturation_clear_empty_clone_and_move() {
|
||||
$if v3_backend ? {
|
||||
mut values := new_length_filter_map()
|
||||
values.clear()
|
||||
assert values.data.eq_kind == 17
|
||||
assert values.data.string_length_bits == 0
|
||||
assert values.data.string_first_bits == 0
|
||||
for i in 0 .. 64 {
|
||||
key := 's'.repeat(i)
|
||||
values.set(&key, &i)
|
||||
}
|
||||
assert values.data.string_length_bits == u64(0xffffffffffffffff)
|
||||
assert values.data.string_first_bits == u64(1) | (u64(1) << 51)
|
||||
assert values.data.eq_kind == 17
|
||||
missing := 'z'
|
||||
assert values.data.rejects_string_key(&missing)
|
||||
assert_length_filter_read(&values, missing, -1)
|
||||
for i in 0 .. 64 {
|
||||
key := [u8(i), u8(120)].bytestr()
|
||||
values.set(&key, &i)
|
||||
}
|
||||
assert values.data.string_first_bits == u64(0xffffffffffffffff)
|
||||
assert values.data.eq_kind == 1
|
||||
mut direct := unsafe { values.clone() }
|
||||
assert direct.data.string_length_bits == u64(0xffffffffffffffff)
|
||||
assert direct.data.string_first_bits == u64(0xffffffffffffffff)
|
||||
assert direct.data.eq_kind == 1
|
||||
values.reserve(4096)
|
||||
mut sparse := unsafe { values.clone() }
|
||||
assert sparse.data.string_length_bits == u64(0xffffffffffffffff)
|
||||
assert sparse.data.string_first_bits == u64(0xffffffffffffffff)
|
||||
assert sparse.data.eq_kind == 1
|
||||
mut moved := sparse.move()
|
||||
assert sparse.data.eq_kind == 17
|
||||
assert sparse.data.string_length_bits == 0
|
||||
assert sparse.data.string_first_bits == 0
|
||||
for i in 0 .. 64 {
|
||||
key := 's'.repeat(i)
|
||||
assert_length_filter_read(&direct, key, i)
|
||||
assert_length_filter_read(&moved, key, i)
|
||||
byte_key := [u8(i), u8(120)].bytestr()
|
||||
assert_length_filter_read(&direct, byte_key, i)
|
||||
assert_length_filter_read(&moved, byte_key, i)
|
||||
}
|
||||
values.clear()
|
||||
assert values.data.eq_kind == 17
|
||||
assert values.data.string_length_bits == 0
|
||||
assert values.data.string_first_bits == 0
|
||||
mut empty := unsafe { values.clone() }
|
||||
assert empty.data.eq_kind == 17
|
||||
assert empty.data.string_length_bits == 0
|
||||
assert empty.data.string_first_bits == 0
|
||||
for i in 1 .. 4 {
|
||||
key := 'r'.repeat(i * 64)
|
||||
values.set(&key, &i)
|
||||
}
|
||||
assert values.data.eq_kind == 17
|
||||
assert values.data.string_length_bits == 1
|
||||
assert values.data.string_first_bits == u64(1) << 50
|
||||
assert_length_filter_read(&values, 'r'.repeat(128), 2)
|
||||
assert_length_filter_read(&values, 's'.repeat(31), -1)
|
||||
unsafe {
|
||||
direct.free()
|
||||
moved.free()
|
||||
sparse.free()
|
||||
empty.free()
|
||||
values.free()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_keeps_length_filter_when_first_is_saturated() {
|
||||
$if v3_backend ? {
|
||||
mut values := new_length_filter_map()
|
||||
for i in 0 .. 64 {
|
||||
key := [u8(i), u8(120)].bytestr()
|
||||
values.set(&key, &i)
|
||||
}
|
||||
assert values.data.string_length_bits == u64(1) << 2
|
||||
assert values.data.string_first_bits == u64(0xffffffffffffffff)
|
||||
assert values.data.eq_kind == 17
|
||||
missing := 'abc'
|
||||
assert values.data.rejects_string_key(&missing)
|
||||
assert_length_filter_read(&values, missing, -1)
|
||||
for i in 0 .. 64 {
|
||||
key := [u8(i), u8(120)].bytestr()
|
||||
assert_length_filter_read(&values, key, i)
|
||||
}
|
||||
unsafe { values.free() }
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_handles_unsigned_first_byte_residues() {
|
||||
$if v3_backend ? {
|
||||
mut values := new_length_filter_map()
|
||||
first_bytes := [u8(0), 63, 64, 127, 128, 255]
|
||||
for i, first in first_bytes {
|
||||
key := [first, u8(i), u8(120)].bytestr()
|
||||
values.set(&key, &i)
|
||||
}
|
||||
assert values.data.string_length_bits == u64(1) << 3
|
||||
assert values.data.string_first_bits == u64(1) | (u64(1) << 63)
|
||||
missing := [u8(1), u8(0), u8(120)].bytestr()
|
||||
assert values.data.rejects_string_key(&missing)
|
||||
assert_length_filter_read(&values, missing, -1)
|
||||
for i, first in first_bytes {
|
||||
key := [first, u8(i), u8(120)].bytestr()
|
||||
assert_length_filter_read(&values, key, i)
|
||||
}
|
||||
unsafe { values.free() }
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_compact_clone_rebuilds_saturated_history() {
|
||||
$if v3_backend ? {
|
||||
mut values := new_length_filter_map()
|
||||
for i in 0 .. 64 {
|
||||
key := 's'.repeat(i)
|
||||
values.set(&key, &i)
|
||||
byte_key := [u8(i), u8(120)].bytestr()
|
||||
values.set(&byte_key, &i)
|
||||
}
|
||||
assert values.data.eq_kind == 1
|
||||
for i in 0 .. 64 {
|
||||
byte_key := [u8(i), u8(120)].bytestr()
|
||||
unsafe { values.delete(&byte_key) }
|
||||
if i < 61 {
|
||||
key := 's'.repeat(i)
|
||||
unsafe { values.delete(&key) }
|
||||
}
|
||||
}
|
||||
assert values.data.count == 3
|
||||
assert values.data.string_length_bits == u64(0xffffffffffffffff)
|
||||
assert values.data.string_first_bits == u64(0xffffffffffffffff)
|
||||
values.reserve(4096)
|
||||
mut compact := unsafe { values.clone() }
|
||||
assert compact.data.eq_kind == 17
|
||||
assert compact.data.string_length_bits == (u64(1) << 61) | (u64(1) << 62) |
|
||||
(u64(1) << 63)
|
||||
assert compact.data.string_first_bits == u64(1) << 51
|
||||
for i in 61 .. 64 {
|
||||
assert_length_filter_read(&compact, 's'.repeat(i), i)
|
||||
}
|
||||
missing := 'x'.repeat(61)
|
||||
assert compact.data.rejects_string_key(&missing)
|
||||
assert_length_filter_read(&compact, missing, -1)
|
||||
unsafe {
|
||||
compact.free()
|
||||
values.free()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_keeps_custom_callbacks_and_clone_lengths() {
|
||||
$if v3_backend ? {
|
||||
for mode in 1 .. 8 {
|
||||
hash_fn := if mode & 1 != 0 { length_filter_custom_hash } else { map_hash_string }
|
||||
eq_fn := if mode & 2 != 0 { length_filter_custom_eq } else { map_eq_string }
|
||||
clone_fn := if mode & 4 != 0 { length_filter_custom_clone } else { map_clone_string }
|
||||
mut values := new_map(int(sizeof(string)), int(sizeof(int)), hash_fn, eq_fn,
|
||||
clone_fn, map_free_string)
|
||||
length_filter_clone_calls = 0
|
||||
for i in 1 .. 5 {
|
||||
key := 'k'.repeat(i)
|
||||
values.set(&key, &i)
|
||||
}
|
||||
assert values.data.eq_kind & 16 == 0
|
||||
assert values.data.string_length_bits == 0
|
||||
assert values.data.string_first_bits == 0
|
||||
assert length_filter_clone_calls == if mode & 4 != 0 { 4 } else { 0 }
|
||||
length_filter_hash_calls = 0
|
||||
length_filter_eq_calls = 0
|
||||
assert_length_filter_read(&values, 'kkkk', 4)
|
||||
assert length_filter_hash_calls == if mode & 1 != 0 { 3 } else { 0 }
|
||||
assert length_filter_eq_calls == if mode & 2 != 0 { 3 } else { 0 }
|
||||
assert_length_filter_read(&values, 'missing-length', -1)
|
||||
assert length_filter_hash_calls == if mode & 1 != 0 { 6 } else { 0 }
|
||||
values.clear()
|
||||
assert values.data.eq_kind & 16 == 0
|
||||
unsafe { values.free() }
|
||||
}
|
||||
mut shortened := new_map(int(sizeof(string)), int(sizeof(int)), map_hash_string,
|
||||
map_eq_string, length_filter_shortening_clone, map_free_string)
|
||||
length_filter_clone_calls = 0
|
||||
for i in 1 .. 5 {
|
||||
key := 'x'.repeat(i + 1)
|
||||
shortened.set(&key, &i)
|
||||
}
|
||||
assert length_filter_clone_calls == 4
|
||||
assert shortened.data.eq_kind == 1
|
||||
assert shortened.data.string_length_bits == 0
|
||||
assert shortened.data.string_first_bits == 0
|
||||
for i in 0 .. 4 {
|
||||
assert unsafe { (&string(shortened.data.key_values.key(i))).len } == i + 1
|
||||
}
|
||||
// This raw callback changes key identity; original keys must retain ordinary misses.
|
||||
assert_length_filter_read(&shortened, 'xxxxx', -1)
|
||||
unsafe { shortened.free() }
|
||||
mut changed_first := new_map(int(sizeof(string)), int(sizeof(int)), map_hash_string,
|
||||
map_eq_string, length_filter_first_byte_clone, map_free_string)
|
||||
length_filter_clone_calls = 0
|
||||
for i in 1 .. 5 {
|
||||
key := 'x'.repeat(i + 1)
|
||||
changed_first.set(&key, &i)
|
||||
}
|
||||
assert length_filter_clone_calls == 4
|
||||
assert changed_first.data.eq_kind == 1
|
||||
assert changed_first.data.string_length_bits == 0
|
||||
assert changed_first.data.string_first_bits == 0
|
||||
for i in 0 .. 4 {
|
||||
assert unsafe { (&string(changed_first.data.key_values.key(i))).str[0] } == u8(255)
|
||||
}
|
||||
assert_length_filter_read(&changed_first, 'xxxxx', -1)
|
||||
unsafe { changed_first.free() }
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_seeds_only_live_provided_dense_keys() {
|
||||
$if v3_backend ? {
|
||||
mut dense := new_dense_array(int(sizeof(string)), int(sizeof(int)), 0, 0)
|
||||
keys := ['', 'discard', 'x'.repeat(31), 'y'.repeat(32)]
|
||||
for i, key in keys {
|
||||
index := dense.expand()
|
||||
map_clone_string(dense.key(index), &key)
|
||||
unsafe { vmemcpy(dense.value(index), &i, int(sizeof(int))) }
|
||||
}
|
||||
map_free_string(dense.key(1))
|
||||
dense.delete(1)
|
||||
metas := unsafe { &u32(vcalloc_noscan(int(sizeof(u32) * (init_capicity + extra_metas_inc)))) }
|
||||
mut values := new_map_with_dense_array(int(sizeof(string)), int(sizeof(int)),
|
||||
map_hash_string, map_eq_string, map_clone_string, map_free_string, dense, metas)
|
||||
expected_bits := u64(1) | (u64(1) << 31) | (u64(1) << 32)
|
||||
assert values.data.string_length_bits == expected_bits
|
||||
assert values.data.string_first_bits == u64(1) | (u64(1) << 56) | (u64(1) << 57)
|
||||
assert values.data.rejects_string_key(&keys[1])
|
||||
values.data.count = 3
|
||||
values.data.rehash()
|
||||
for i, key in keys {
|
||||
assert_length_filter_read(&values, key, if i == 1 { -1 } else { i })
|
||||
}
|
||||
unsafe { values.free() }
|
||||
}
|
||||
}
|
||||
|
||||
fn length_filter_read_worker(values &map, keys []string, worker_id int) int {
|
||||
$if v3_backend ? {
|
||||
missing := 'absent-length'
|
||||
zero := -1
|
||||
for iteration in 0 .. 256 {
|
||||
index := (iteration + worker_id) % keys.len
|
||||
key := keys[index]
|
||||
found := values.get_check(&key)
|
||||
if found == unsafe { nil } || unsafe { *&int(found) } != index + 1
|
||||
|| !values.exists(&key)
|
||||
|| unsafe { *&int(values.get(&key, &zero)) } != index + 1 {
|
||||
return -1
|
||||
}
|
||||
if values.get_check(&missing) != unsafe { nil } || values.exists(&missing)
|
||||
|| values.get(&missing, &zero) != voidptr(&zero) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
return worker_id
|
||||
}
|
||||
|
||||
fn test_map_string_length_filter_allows_concurrent_readonly_mixed_keys() {
|
||||
$if v3_backend ? {
|
||||
mut values := new_length_filter_map()
|
||||
keys := ['alpha', 'b'.repeat(31), 'c'.repeat(32), '\x00'.repeat(64)]
|
||||
for i, key in keys {
|
||||
value := i + 1
|
||||
values.set(&key, &value)
|
||||
}
|
||||
bits := values.data.string_length_bits
|
||||
first_bits := values.data.string_first_bits
|
||||
mut threads := []thread int{}
|
||||
for worker_id in 0 .. 4 {
|
||||
threads << spawn length_filter_read_worker(&values, keys, worker_id)
|
||||
}
|
||||
for worker_id, handle in threads {
|
||||
assert handle.wait() == worker_id
|
||||
}
|
||||
assert values.data.string_length_bits == bits
|
||||
assert values.data.string_first_bits == first_bits
|
||||
// All readers have joined before changing or releasing the map.
|
||||
values.clear()
|
||||
unsafe { values.free() }
|
||||
}
|
||||
}
|
||||
+2
-13
@@ -1551,18 +1551,7 @@ fn (s string) index_multibyte(p string) int {
|
||||
|
||||
@[direct_array_access]
|
||||
fn (s string) index_pair(p string) int {
|
||||
first := unsafe { p.str[0] }
|
||||
second := unsafe { p.str[1] }
|
||||
last_start := s.len - 2
|
||||
for i := 0; i <= last_start; i++ {
|
||||
if unsafe { s.str[i] } != first {
|
||||
next := string_index_byte(unsafe { s.str + i + 1 }, last_start - i, first)
|
||||
if next < 0 { return -1 }
|
||||
i += next + 1
|
||||
}
|
||||
if unsafe { s.str[i + 1] } == second { return i }
|
||||
}
|
||||
return -1
|
||||
return string_index_pair(s.str, s.len, unsafe { p.str[0] }, unsafe { p.str[1] })
|
||||
}
|
||||
|
||||
// Needles up to this length are searched directly; longer ones use KMP, which
|
||||
@@ -1759,7 +1748,7 @@ pub fn (s string) count(substr string) int {
|
||||
// See also: [`string.index_u8`](#string.index_u8) , to get the index of the byte as well.
|
||||
@[inline]
|
||||
pub fn (s string) contains_u8(x u8) bool {
|
||||
return string_index_byte(s.str, s.len, x) >= 0
|
||||
return string_contains_byte(s.str, s.len, x)
|
||||
}
|
||||
|
||||
// contains returns `true` if the string contains `substr`.
|
||||
|
||||
@@ -120,3 +120,70 @@ fn test_byte_search_handles_adjacent_borrows_and_repeated_bytes() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_byte_contains_checks_every_short_match_position() {
|
||||
for size in 1 .. 34 {
|
||||
for start in [0, 1, 3, 7, 15, 31] {
|
||||
for byte in [u8(0), 1, 0x7f, 0x80, 0xfe, 0xff] {
|
||||
mut bytes := []u8{len: start + size + 32, init: byte ^ 1}
|
||||
view := unsafe { tos(&bytes[start], size) }
|
||||
assert !view.contains_u8(byte)
|
||||
for position in 0 .. size {
|
||||
bytes[start + position] = byte
|
||||
assert view.contains_u8(byte)
|
||||
bytes[start + position] = byte ^ 1
|
||||
}
|
||||
bytes[start + size] = byte
|
||||
assert !view.contains_u8(byte)
|
||||
if start > 0 {
|
||||
bytes[start - 1] = byte
|
||||
assert !view.contains_u8(byte)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_pair_search_respects_every_short_window_and_borrowed_boundary() {
|
||||
for first in [u8(0), 1, 0x7f, 0x80, 0xfe, 0xff] {
|
||||
for second in [u8(0), 1, 0x7f, 0x80, 0xfe, 0xff] {
|
||||
needle_bytes := [first, second]
|
||||
needle := unsafe { tos(&needle_bytes[0], 2) }
|
||||
for start in 0 .. 8 {
|
||||
for size in 0 .. 35 {
|
||||
mut bytes := []u8{len: start + size + 2, init: first ^ 1}
|
||||
view := unsafe { tos(&bytes[start], size) }
|
||||
assert view.index_(needle) == byte_search_reference_index(view, needle)
|
||||
for at in 0 .. size - 1 {
|
||||
bytes[start + at] = first
|
||||
bytes[start + at + 1] = second
|
||||
expected := byte_search_reference_index(view, needle)
|
||||
assert view.index_(needle) == expected
|
||||
assert view.contains(needle) == (expected >= 0)
|
||||
bytes[start + at] = first ^ 1
|
||||
bytes[start + at + 1] = first ^ 1
|
||||
}
|
||||
// A pair straddling the end must not become an in-span match.
|
||||
if size > 0 { bytes[start + size - 1] = first }
|
||||
bytes[start + size] = second
|
||||
assert view.index_(needle) == byte_search_reference_index(view, needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_pair_search_rejects_adjacent_mask_borrows() {
|
||||
for first in 0 .. 256 {
|
||||
for second in 0 .. 256 {
|
||||
needle_bytes := [u8(first), u8(second)]
|
||||
needle := unsafe { tos(&needle_bytes[0], 2) }
|
||||
mut bytes := []u8{len: 32, init: if index & 1 == 0 { u8(first) } else { u8(second) ^ 1 }}
|
||||
view := unsafe { tos(&bytes[0], bytes.len) }
|
||||
assert view.index_(needle) == byte_search_reference_index(view, needle)
|
||||
bytes[30] = u8(first)
|
||||
bytes[31] = u8(second)
|
||||
assert view.index_(needle) == byte_search_reference_index(view, needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-12
@@ -27,10 +27,16 @@ pub fn (mut b Builder) reuse_as_plain_u8_array() []u8 {
|
||||
return *b
|
||||
}
|
||||
|
||||
// write_ptr writes `len` bytes provided byteptr to the accumulated buffer
|
||||
@[unsafe]
|
||||
// write_ptr appends `len` bytes from `ptr` to the accumulated buffer.
|
||||
@[inline; unsafe]
|
||||
pub fn (mut b Builder) write_ptr(ptr &u8, len int) {
|
||||
if len == 0 {
|
||||
if len <= 0 || ptr == unsafe { nil } {
|
||||
return
|
||||
}
|
||||
if len <= b.cap - b.len && !b.flags.has(.is_slice) {
|
||||
// The source may borrow the builder's buffer, including unused capacity.
|
||||
unsafe { vmemmove(&u8(b.data) + b.len, ptr, len) }
|
||||
b.len += len
|
||||
return
|
||||
}
|
||||
unsafe { b.push_many(ptr, len) }
|
||||
@@ -138,17 +144,10 @@ pub fn (b &Builder) byte_at(n int) u8 {
|
||||
return unsafe { (&[]u8(b))[n] }
|
||||
}
|
||||
|
||||
// write appends the string `s` to the buffer
|
||||
// write_string appends the string `s` to the buffer.
|
||||
@[expand_simple_interpolation; inline]
|
||||
pub fn (mut b Builder) write_string(s string) {
|
||||
if s.len == 0 {
|
||||
return
|
||||
}
|
||||
unsafe { b.push_many(s.str, s.len) }
|
||||
// for c in s {
|
||||
// b.buf << c
|
||||
// }
|
||||
// b.buf << []u8(s) // TODO
|
||||
unsafe { b.write_ptr(s.str, s.len) }
|
||||
}
|
||||
|
||||
// write_string2 appends the strings `s1` and `s2` to the buffer.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import strings
|
||||
|
||||
fn test_builder_append_copy_boundaries() {
|
||||
for initial_cap in [0, 4096] {
|
||||
mut b := strings.new_builder(initial_cap)
|
||||
mut expected := []u8{}
|
||||
for size in [0, 1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 65] {
|
||||
mut payload := []u8{cap: size}
|
||||
for i in 0 .. size {
|
||||
payload << u8(i)
|
||||
}
|
||||
b.write_string(payload.bytestr())
|
||||
expected << payload
|
||||
unsafe { b.write_ptr(payload.data, payload.len) }
|
||||
expected << payload
|
||||
}
|
||||
assert b.str().bytes() == expected
|
||||
}
|
||||
}
|
||||
|
||||
fn test_builder_append_exact_capacity_then_grows() {
|
||||
mut b := strings.new_builder(6)
|
||||
b.write_string('abc')
|
||||
unsafe { b.write_ptr(c'def', 3) }
|
||||
assert b.len == 6
|
||||
assert b.cap == 6
|
||||
b.write_string('g')
|
||||
assert b.cap >= 7
|
||||
assert b.str() == 'abcdefg'
|
||||
}
|
||||
|
||||
fn test_builder_append_borrows_own_buffer() {
|
||||
for initial_cap in [6, 32] {
|
||||
mut b := strings.new_builder(initial_cap)
|
||||
b.write_string('abcdef')
|
||||
// Borrow the live bytes so both the in-capacity and growth paths append themselves.
|
||||
borrowed := unsafe { tos(&u8(b.data), b.len) }
|
||||
b.write_string(borrowed)
|
||||
assert b.len == 12
|
||||
b.ensure_cap(32)
|
||||
unsafe { b.write_ptr(&u8(b.data) + 2, 4) }
|
||||
assert b.str() == 'abcdefabcdefcdef'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_builder_append_overlapping_unused_capacity() {
|
||||
mut b := strings.new_builder(32)
|
||||
b.write_string('abcdefgh')
|
||||
// The source still points into initialized storage after reducing the live length.
|
||||
unsafe { b.len = 3 }
|
||||
unsafe { b.write_ptr(&u8(b.data) + 1, 5) }
|
||||
assert b.str() == 'abcbcdef'
|
||||
}
|
||||
|
||||
fn test_builder_append_detaches_a_slice() {
|
||||
for use_ptr in [false, true] {
|
||||
mut backing := 'abcdef'.bytes()
|
||||
mut b := strings.Builder(backing[1..5])
|
||||
// A slice with spare capacity must detach before an append writes to shared storage.
|
||||
unsafe { b.len = 2 }
|
||||
if use_ptr {
|
||||
unsafe { b.write_ptr(c'XY', 2) }
|
||||
} else {
|
||||
b.write_string('XY')
|
||||
}
|
||||
assert backing.bytestr() == 'abcdef'
|
||||
assert b.str() == 'bcXY'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_builder_append_ignores_empty_or_nil_input() {
|
||||
mut b := strings.new_builder(8)
|
||||
b.write_string('a')
|
||||
b.write_string('')
|
||||
unsafe {
|
||||
b.write_ptr(nil, 0)
|
||||
b.write_ptr(nil, 3)
|
||||
b.write_ptr(c'skip', -1)
|
||||
}
|
||||
assert b.str() == 'a'
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
module flat
|
||||
|
||||
fn test_source_type_read_cache_rechecks_ids_on_collision() {
|
||||
left_text := '&read_cache.Left'
|
||||
left := source_type_id(left_text)
|
||||
right_text := '[]read_cache.Right'
|
||||
right := source_type_id(right_text)
|
||||
left_entry := source_type_entry(source_type_table_load(), left)
|
||||
right_entry := source_type_entry(source_type_table_load(), right)
|
||||
slot := int(left & u32(source_type_read_cache_size - 1))
|
||||
// A fully published entry for another ID must never answer a colliding query.
|
||||
$if x32 {
|
||||
C.atomic_store_u32(voidptr(&g_source_type_read_entries[slot]), u32(voidptr(right_entry)))
|
||||
} $else {
|
||||
C.atomic_store_u64(voidptr(&g_source_type_read_entries[slot]), u64(voidptr(right_entry)))
|
||||
}
|
||||
assert source_type_name(left) == left_text
|
||||
assert source_type_shape(left) == left_entry.shape
|
||||
assert source_type_read_entry(left) == left_entry
|
||||
assert source_type_name(right) == right_text
|
||||
assert source_type_shape(right) == right_entry.shape
|
||||
}
|
||||
|
||||
fn test_source_type_read_cache_preserves_empty_sentinels() {
|
||||
assert source_type_name(0) == ''
|
||||
assert source_type_shape(0) == unsafe { SourceTypeShape(0) }
|
||||
}
|
||||
|
||||
fn test_source_type_read_cache_keeps_canonical_borrowed_spelling() {
|
||||
mut bytes := 'read_cache.Borrowed'.bytes()
|
||||
view := unsafe { tos(bytes.data, bytes.len) }
|
||||
id := source_type_id(view)
|
||||
assert source_type_name(id) == 'read_cache.Borrowed'
|
||||
shape := source_type_shape(id)
|
||||
bytes[0] = `R`
|
||||
changed := source_type_id(view)
|
||||
assert changed != id
|
||||
assert source_type_name(id) == 'read_cache.Borrowed'
|
||||
assert source_type_shape(id) == shape
|
||||
assert source_type_name(changed) == 'Read_cache.Borrowed'
|
||||
}
|
||||
|
||||
fn test_source_type_read_cache_retains_entry_across_growth() {
|
||||
text := '[]read_cache.BeforeGrowth'
|
||||
id := source_type_id(text)
|
||||
assert source_type_name(id) == text
|
||||
entry := source_type_read_entry(id)
|
||||
mut table := source_type_table_load()
|
||||
source_type_lock()
|
||||
table.grow()
|
||||
C.atomic_store_u32(&g_source_types_lock, 0)
|
||||
for i in 0 .. source_type_read_cache_size + 1 {
|
||||
source_type_id('read_cache.Growth_${i}')
|
||||
}
|
||||
assert source_type_name(id) == text
|
||||
assert source_type_shape(id) == entry.shape
|
||||
assert source_type_read_entry(id) == entry
|
||||
}
|
||||
|
||||
fn cache_source_type_in_disposable_scope() u32 {
|
||||
$if prealloc {
|
||||
scope := unsafe { prealloc_scope_begin() }
|
||||
id := source_type_id('[]read_cache.Disposable'.clone())
|
||||
assert source_type_name(id) == '[]read_cache.Disposable'
|
||||
assert source_type_shape(id).has(.array_type)
|
||||
unsafe {
|
||||
prealloc_scope_leave(scope)
|
||||
prealloc_scope_free_after(scope)
|
||||
prealloc_thread_cleanup()
|
||||
}
|
||||
return id
|
||||
} $else {
|
||||
id := source_type_id('[]read_cache.Disposable'.clone())
|
||||
assert source_type_name(id) == '[]read_cache.Disposable'
|
||||
assert source_type_shape(id).has(.array_type)
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
fn test_source_type_read_cache_outlives_publishing_worker_arena() {
|
||||
handle := spawn cache_source_type_in_disposable_scope()
|
||||
id := handle.wait()
|
||||
assert source_type_name(id) == '[]read_cache.Disposable'
|
||||
assert source_type_shape(id).has(.array_type)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import hash
|
||||
const source_type_chunk_bits = 10
|
||||
const source_type_chunk_size = 1 << source_type_chunk_bits
|
||||
const source_type_chunk_count = 16384
|
||||
const source_type_read_cache_size = 4096
|
||||
|
||||
// Builtin spellings are interned once; hot paths carry their numeric identities.
|
||||
pub const source_type_void = source_type_id('void')
|
||||
@@ -248,6 +249,7 @@ struct SourceTypeIndex {
|
||||
__global g_source_types &SourceTypeTable
|
||||
__global g_source_types_lock u32
|
||||
__global g_source_type_recent_entries [16384]voidptr
|
||||
__global g_source_type_read_entries [source_type_read_cache_size]voidptr
|
||||
|
||||
fn source_type_lock() {
|
||||
for {
|
||||
@@ -491,28 +493,47 @@ fn source_type_id_uncached(text string) u32 {
|
||||
return id
|
||||
}
|
||||
|
||||
// source_type_read_entry caches immutable process-owned entries by their published ID.
|
||||
@[direct_array_access; inline]
|
||||
fn source_type_read_entry(id u32) &SourceTypeEntry {
|
||||
slot := int(id & u32(source_type_read_cache_size - 1))
|
||||
cached := $if x32 {
|
||||
unsafe { &SourceTypeEntry(voidptr(C.atomic_load_u32(voidptr(&g_source_type_read_entries[slot])))) }
|
||||
} $else {
|
||||
unsafe { &SourceTypeEntry(voidptr(C.atomic_load_u64(voidptr(&g_source_type_read_entries[slot])))) }
|
||||
}
|
||||
if !isnil(cached) && cached.id == id {
|
||||
return cached
|
||||
}
|
||||
table := source_type_table_load()
|
||||
if isnil(table) || id > C.atomic_load_u32(&table.count) {
|
||||
panic('invalid source type ID')
|
||||
}
|
||||
entry := source_type_entry(table, id)
|
||||
// Only a checked published ID reaches this store. Entries never move or expire,
|
||||
// so a matching ID remains valid across index growth and disposable worker arenas.
|
||||
$if x32 {
|
||||
C.atomic_store_u32(voidptr(&g_source_type_read_entries[slot]), u32(voidptr(entry)))
|
||||
} $else {
|
||||
C.atomic_store_u64(voidptr(&g_source_type_read_entries[slot]), u64(voidptr(entry)))
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// source_type_name resolves a source spelling ID at a text boundary.
|
||||
@[inline]
|
||||
pub fn source_type_name(id u32) string {
|
||||
if id == 0 {
|
||||
return ''
|
||||
}
|
||||
table := source_type_table_load()
|
||||
if isnil(table) || id > C.atomic_load_u32(&table.count) {
|
||||
panic('invalid source type ID')
|
||||
}
|
||||
return source_type_entry(table, id).text
|
||||
return source_type_read_entry(id).text
|
||||
}
|
||||
|
||||
// source_type_shape returns syntax properties without resolving spelling bytes.
|
||||
@[inline]
|
||||
pub fn source_type_shape(id u32) SourceTypeShape {
|
||||
if id == 0 { return unsafe { SourceTypeShape(0) } }
|
||||
table := source_type_table_load()
|
||||
if isnil(table) || id > C.atomic_load_u32(&table.count) {
|
||||
panic('invalid source type ID')
|
||||
}
|
||||
return source_type_entry(table, id).shape
|
||||
return source_type_read_entry(id).shape
|
||||
}
|
||||
|
||||
// source_type_query returns context-free query results without decoding a spelling.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn argument_rvalue_node(mut a flat.FlatAst, kind flat.NodeKind, value string, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
kind: kind
|
||||
value: value
|
||||
children_start: start
|
||||
children_count: children.len
|
||||
})
|
||||
}
|
||||
|
||||
fn test_argument_address_lowering_preserves_values_locals_consts_calls_and_map_elements() {
|
||||
for argument_kind in ['local', 'const', 'call', 'map_index'] {
|
||||
for pointer_parameter in [false, true] {
|
||||
for full_call in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'consume')
|
||||
value := a.add_val(.int_literal, '7')
|
||||
map_var := a.add_val(.ident, 'table')
|
||||
map_key := a.add_val(.int_literal, '0')
|
||||
inner_callee := a.add_val(.ident, 'make_value')
|
||||
arg := match argument_kind {
|
||||
'local' { a.add_val(.ident, 'local') }
|
||||
'const' { a.add_val(.ident, 'limit') }
|
||||
'call' { argument_rvalue_node(mut a, .call, '', [inner_callee]) }
|
||||
else { argument_rvalue_node(mut a, .index, '', [map_var, map_key]) }
|
||||
}
|
||||
call := argument_rvalue_node(mut a, .call, '', [callee, arg])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
int_type := types.Type(types.int_)
|
||||
param_type := if pointer_parameter {
|
||||
types.Type(types.Pointer{ base_type: &int_type })
|
||||
} else {
|
||||
int_type
|
||||
}
|
||||
tc.fn_param_types['consume'] = [param_type]
|
||||
tc.fn_ret_types['consume'] = types.Type(types.void_)
|
||||
tc.fn_param_types['make_value'] = []types.Type{}
|
||||
tc.fn_ret_types['make_value'] = int_type
|
||||
tc.cur_scope.insert('local', int_type)
|
||||
tc.const_types['limit'] = int_type
|
||||
tc.register_synth_type(arg, int_type)
|
||||
tc.register_synth_type(value, int_type)
|
||||
tc.register_synth_type(map_key, int_type)
|
||||
tc.register_synth_type(map_var, types.Type(types.Map{ key_type: &int_type, value_type: &int_type }))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.const_vals['limit'] = value
|
||||
// Keep expression emission independent of the argument's storage shape.
|
||||
g.assert_expr_overrides[int(arg)] = 'ARG'
|
||||
if full_call {
|
||||
g.gen_call(call, a.node(call))
|
||||
} else {
|
||||
g.gen_call_args('consume', a.node(call), 1)
|
||||
}
|
||||
ct := tc.c_type(int_type)
|
||||
expected_arg := if !pointer_parameter {
|
||||
'ARG'
|
||||
} else if argument_kind == 'const' {
|
||||
'(${ct}[]){ARG}'
|
||||
} else if argument_kind == 'call' || (argument_kind == 'map_index' && !full_call) {
|
||||
'&((${ct}[]){ARG})[0]'
|
||||
} else {
|
||||
'&ARG'
|
||||
}
|
||||
expected := if full_call { 'consume(${expected_arg})' } else { expected_arg }
|
||||
assert g.sb.str() == expected, '${argument_kind}, pointer=${pointer_parameter}, full=${full_call}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1027,12 +1027,12 @@ fn (mut g FlatGen) to_fixed_size_call_fixed_type(id flat.NodeId) ?types.ArrayFix
|
||||
if node.kind != .call || node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
fn_node := g.a.child_node(&node, 0)
|
||||
fn_node := g.a.node(g.a.child(&node, 0))
|
||||
if fn_node.kind != .selector || fn_node.value != 'to_fixed_size' || fn_node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
if node.typ != 0 {
|
||||
if fixed := array_fixed_type(g.parse_node_type(&node)) {
|
||||
if fixed := array_fixed_type(*g.parse_annotation_type_reference(node.typ)) {
|
||||
return fixed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_borrowed_call_helpers_keep_stack_snapshot_across_arena_growth_and_nested_calls() {
|
||||
mut a := flat.FlatAst.new()
|
||||
inner_callee := a.add_val(.ident, 'inner')
|
||||
a.children << inner_callee
|
||||
inner := a.add_node(flat.Node{ kind: .call, children_start: 0, children_count: 1 })
|
||||
outer_callee := a.add_val(.ident, 'outer')
|
||||
a.children << outer_callee
|
||||
a.children << inner
|
||||
outer := a.add_node(flat.Node{ kind: .call, children_start: 1, children_count: 2 })
|
||||
call_snapshot := a.nodes[int(outer)]
|
||||
callee_snapshot := a.nodes[int(outer_callee)]
|
||||
for i in 0 .. 2048 {
|
||||
a.add_val(.ident, 'padding_${i}')
|
||||
}
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.fn_param_types['inner'] = []types.Type{}
|
||||
tc.fn_param_types['outer'] = [types.Type(types.int_)]
|
||||
tc.fn_ret_types['inner'] = types.Type(types.int_)
|
||||
tc.fn_ret_types['outer'] = types.Type(types.int_)
|
||||
tc.register_synth_type(inner, types.Type(types.int_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
assert g.direct_call_name_for_call_node(outer, &call_snapshot, 'outer') == 'outer'
|
||||
assert g.call_target_key(&callee_snapshot) == 'outer'
|
||||
assert !g.call_callee_is_module_selector(&call_snapshot)
|
||||
g.gen_call(outer, &call_snapshot)
|
||||
assert g.sb.str() == 'outer(inner())'
|
||||
assert call_snapshot == a.nodes[int(outer)]
|
||||
assert callee_snapshot == a.nodes[int(outer_callee)]
|
||||
}
|
||||
|
||||
fn test_borrowed_runtime_map_call_uses_stack_child_snapshots() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'new_map')
|
||||
key_size := a.add_val(.sizeof_expr, 'int')
|
||||
value_size := a.add_val(.sizeof_expr, 'string')
|
||||
a.children << callee
|
||||
a.children << key_size
|
||||
a.children << value_size
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: 0, children_count: 3 })
|
||||
call_snapshot := a.nodes[int(call)]
|
||||
callee_snapshot := a.nodes[int(callee)]
|
||||
for i in 0 .. 2048 {
|
||||
a.add_val(.ident, 'padding_${i}')
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
assert g.is_runtime_new_map_call(&call_snapshot)
|
||||
assert g.main_runtime_shadow_call_c_name(&call_snapshot, &callee_snapshot) == none
|
||||
assert call_snapshot == a.nodes[int(call)]
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_target_module_static_signature_keeps_empty_missing_and_alias_fallbacks() {
|
||||
for mode in ['empty_params', 'return_only', 'generic_only', 'return_with_arg', 'generic_with_arg',
|
||||
'wrong_static_arity', 'no_static'] {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'short.work')
|
||||
arg := a.add_val(.int_literal, '7')
|
||||
a.children << callee
|
||||
with_arg := mode in ['return_with_arg', 'generic_with_arg']
|
||||
if with_arg { a.children << arg }
|
||||
call := a.add_node(flat.Node{
|
||||
kind: .call
|
||||
children_start: 0
|
||||
children_count: if with_arg {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
})
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.imports['short'] = 'library'
|
||||
tc.fn_param_types['library.work'] = []types.Type{}
|
||||
match mode {
|
||||
'empty_params' { tc.fn_param_types['short.work'] = []types.Type{} }
|
||||
'return_only', 'return_with_arg' {
|
||||
tc.fn_ret_types['short.work'] = types.Type(types.int_)
|
||||
}
|
||||
'generic_only', 'generic_with_arg' { tc.fn_generic_params['short.work'] = ['T'] }
|
||||
'wrong_static_arity' { tc.fn_param_types['short.work'] = [types.Type(types.int_)] }
|
||||
else {}
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
resolved := g.target_module_call_name('short.work', a.node(call)) or { '' }
|
||||
expected := match mode {
|
||||
'empty_params', 'return_only' { 'short.work' }
|
||||
'wrong_static_arity', 'no_static' { 'library.work' }
|
||||
else { '' }
|
||||
}
|
||||
assert resolved == expected, mode
|
||||
}
|
||||
}
|
||||
|
||||
fn test_target_module_signature_preserves_variadic_and_synthetic_marker_rules() {
|
||||
for synthetic in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'short.work')
|
||||
arg := a.add_val(.ident, if synthetic { 'short' } else { 'value' })
|
||||
a.children << callee
|
||||
a.children << arg
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: 0, children_count: 2 })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.imports['short'] = 'library'
|
||||
tc.cur_scope.insert('short', types.Type(types.int_))
|
||||
tc.fn_param_types['short.work'] = [types.Type(types.Array{ elem_type: types.Type(types.int_) })]
|
||||
tc.fn_variadic['short.work'] = true
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
assert g.target_module_call_arg_start('short.work', a.node(call)) == if synthetic {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
resolved := g.target_module_call_name('short.work', a.node(call)) or { '' }
|
||||
assert resolved == if synthetic { 'short.work' } else { '' }
|
||||
if synthetic {
|
||||
assert !g.gen_transformed_method_ident_call(call, a.node(call), a.node(callee))
|
||||
assert g.sb.len == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_transformed_method_checks_receiver_before_discarded_c_name_resolution() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'library.read')
|
||||
arg := a.add_val(.int_literal, '7')
|
||||
a.children << callee
|
||||
a.children << arg
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: 0, children_count: 2 })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.fn_param_types['library.read'] = [types.Type(types.int_)]
|
||||
tc.fn_ret_types['library.read'] = types.Type(types.int_)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
assert !g.gen_transformed_method_ident_call(call, a.node(call), a.node(callee))
|
||||
assert g.sb.len == 0
|
||||
assert 'library.read' !in g.c_name_cache.entries
|
||||
g.gen_call(call, a.node(call))
|
||||
assert g.sb.str() == 'library__read(7)'
|
||||
}
|
||||
|
||||
fn test_transformed_method_keeps_source_and_emitted_parameter_lookup() {
|
||||
for emitted_only in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'int.read')
|
||||
arg := a.add_val(.int_literal, '7')
|
||||
a.children << callee
|
||||
a.children << arg
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: 0, children_count: 2 })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
key := if emitted_only { 'int__read' } else { 'int.read' }
|
||||
tc.fn_param_types[key] = [types.Type(types.int_)]
|
||||
tc.fn_ret_types[key] = types.Type(types.int_)
|
||||
tc.register_synth_type(arg, types.Type(types.int_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
assert g.gen_transformed_method_ident_call(call, a.node(call), a.node(callee))
|
||||
assert g.sb.str() == 'int__read(7)'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.token
|
||||
import v.types
|
||||
|
||||
fn test_call_resolution_snapshot_keeps_absent_and_present_empty_names() {
|
||||
for present in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'ordinary')
|
||||
a.children << callee
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: 0, children_count: 1 })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.fn_ret_types['ordinary'] = types.Type(types.void_)
|
||||
tc.resolved_call_names = []&types.CachedName{len: a.nodes.len}
|
||||
tc.resolved_call_set = []bool{len: a.nodes.len}
|
||||
if present {
|
||||
tc.resolved_call_names[int(call)] = types.cached_name('')
|
||||
tc.resolved_call_set[int(call)] = true
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.gen_call(call, a.node(call))
|
||||
assert g.sb.str() == 'ordinary()'
|
||||
assert (tc.resolved_call_name(call) != none) == present
|
||||
}
|
||||
}
|
||||
|
||||
fn test_nested_call_resolution_snapshots_keep_each_selector_target() {
|
||||
for sparse in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'receiver')
|
||||
a.children << base
|
||||
inner_selector := a.add_node(flat.Node{ kind: .selector, value: 'inner', children_start: 0, children_count: 1 })
|
||||
a.children << inner_selector
|
||||
inner := a.add_node(flat.Node{ kind: .call, children_start: 1, children_count: 1 })
|
||||
a.children << base
|
||||
outer_selector := a.add_node(flat.Node{ kind: .selector, value: 'outer', children_start: 2, children_count: 1 })
|
||||
a.children << outer_selector
|
||||
a.children << inner
|
||||
outer := a.add_node(flat.Node{ kind: .call, children_start: 3, children_count: 2 })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.cur_scope.insert('receiver', types.Type(types.int_))
|
||||
tc.fn_param_types['impl.inner'] = []types.Type{}
|
||||
tc.fn_param_types['impl.outer'] = [types.Type(types.int_)]
|
||||
tc.fn_ret_types['impl.inner'] = types.Type(types.int_)
|
||||
tc.fn_ret_types['impl.outer'] = types.Type(types.int_)
|
||||
tc.register_synth_type(inner, types.Type(types.int_))
|
||||
if sparse {
|
||||
tc.parallel_check_sparse = true
|
||||
tc.sparse_resolved_call_names[int(inner)] = 'impl.inner'
|
||||
tc.sparse_resolved_call_names[int(outer)] = 'impl.outer'
|
||||
} else {
|
||||
tc.resolved_call_names = []&types.CachedName{len: a.nodes.len}
|
||||
tc.resolved_call_set = []bool{len: a.nodes.len}
|
||||
tc.resolved_call_names[int(inner)] = types.cached_name('impl.inner')
|
||||
tc.resolved_call_names[int(outer)] = types.cached_name('impl.outer')
|
||||
tc.resolved_call_set[int(inner)] = true
|
||||
tc.resolved_call_set[int(outer)] = true
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.gen_call(outer, a.node(outer))
|
||||
assert g.sb.str() == 'impl__outer(impl__inner())'
|
||||
assert tc.resolved_call_name(outer) or { '' } == 'impl.outer'
|
||||
assert tc.resolved_call_name(inner) or { '' } == 'impl.inner'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_script_local_call_keeps_checker_resolution_unchanged() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut files := token.FileSet.new()
|
||||
a.source_files[1] = files.add_file('commands.vsh', 1)
|
||||
callee := a.add_val(.ident, 'ordinary')
|
||||
a.children << callee
|
||||
call := a.add_node(flat.Node{ kind: .call, pos: token.new_pos(1, 0), children_start: 0, children_count: 1 })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.cur_file = 'commands.vsh'
|
||||
tc.fn_type_files['ordinary'] = 'commands.vsh'
|
||||
tc.fn_ret_types['ordinary'] = types.Type(types.void_)
|
||||
tc.resolved_call_names = []&types.CachedName{len: a.nodes.len}
|
||||
tc.resolved_call_set = []bool{len: a.nodes.len}
|
||||
tc.resolved_call_names[int(call)] = types.cached_name('other.ordinary')
|
||||
tc.resolved_call_set[int(call)] = true
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.gen_call(call, a.node(call))
|
||||
assert g.sb.str() == 'ordinary()'
|
||||
assert tc.resolved_call_name(call) or { '' } == 'other.ordinary'
|
||||
}
|
||||
+138
-27
@@ -1235,10 +1235,15 @@ fn (g &FlatGen) parse_node_type(node &flat.Node) types.Type {
|
||||
|
||||
@[inline]
|
||||
fn (g &FlatGen) parse_node_type_reference(node &flat.Node) &types.Type {
|
||||
if flat.source_type_shape(node.typ).has(.dotted) {
|
||||
return g.tc.parse_canonical_type_cached(node.typ)
|
||||
return g.parse_annotation_type_reference(node.typ)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn (g &FlatGen) parse_annotation_type_reference(type_id u32) &types.Type {
|
||||
if flat.source_type_shape(type_id).has(.dotted) {
|
||||
return g.tc.parse_canonical_type_cached(type_id)
|
||||
}
|
||||
return g.tc.parse_type_ref(node.type_text_id())
|
||||
return g.tc.parse_type_ref(type_id)
|
||||
}
|
||||
|
||||
pub fn (g &FlatGen) c_flags() []string {
|
||||
@@ -5157,6 +5162,11 @@ fn (mut g FlatGen) collect_gen_info(no_parallel bool) {
|
||||
g.unused_param_seen = &UnusedParamSeen{}
|
||||
defer_fn_signature_registrations := !no_parallel && g.scope_parallel_workers && g.skip_generics && g.incremental_fn_names.len == 0 && par_cgen_prep_enabled()
|
||||
type_index_args, type_index_threads := g.start_type_indexes(no_parallel)
|
||||
if no_parallel && g.incremental_fn_names.len == 0 {
|
||||
// Serial body generation needs the same immutable name rejection tables
|
||||
// as the worker path. Field-position indexes still stay worker-only.
|
||||
g.type_name_short_index = new_type_name_short_index(g.tc)
|
||||
}
|
||||
g.reserve_collect_gen_info_maps(no_parallel, defer_fn_signature_registrations)
|
||||
g.precompute_export_lookups()
|
||||
if profile {
|
||||
@@ -13502,6 +13512,17 @@ fn (mut g FlatGen) gen_expr_with_expected_type(id flat.NodeId, expected_type &ty
|
||||
@[direct_array_access]
|
||||
fn (mut g FlatGen) gen_expr_with_expected_type_inner(id flat.NodeId, expected_type &types.Type) {
|
||||
node := unsafe { &g.a.nodes[int(id)] }
|
||||
// Scalar literals need no storage conversion. Keep the f32 rounding and
|
||||
// 128-bit widening paths below, including their portable representations.
|
||||
if expected_type is types.Primitive && expected_type.size <= 64
|
||||
&& (node.kind in [.int_literal, .bool_literal]
|
||||
|| (node.kind == .float_literal && !is_f32_primitive(expected_type))) {
|
||||
if g.known_expr_type_id == int(id) {
|
||||
g.known_expr_type_id = -1
|
||||
}
|
||||
g.gen_expr(id)
|
||||
return
|
||||
}
|
||||
expected := g.canonical_import_alias_type_for_node(expected_type, node)
|
||||
has_known_actual := g.known_expr_type_id == int(id)
|
||||
known_actual := g.known_expr_type
|
||||
@@ -13519,6 +13540,13 @@ fn (mut g FlatGen) gen_expr_with_expected_type_inner(id flat.NodeId, expected_ty
|
||||
if expected is types.Enum {
|
||||
g.expected_enum = expected.name_id
|
||||
}
|
||||
if expected is types.String && node.kind in [.string_literal, .string_interp]
|
||||
&& (!has_known_actual || known_actual is types.String) {
|
||||
// Raw string emission and interpolation children read the expected context.
|
||||
// Keep that context while skipping conversions for an already string value.
|
||||
g.gen_expr(id)
|
||||
return
|
||||
}
|
||||
expected_is_ierror := if node.kind == .none_expr || node.kind == .call {
|
||||
g.is_ierror_type_name(semantic_expected.source_type_id())
|
||||
} else {
|
||||
@@ -13552,22 +13580,7 @@ fn (mut g FlatGen) gen_expr_with_expected_type_inner(id flat.NodeId, expected_ty
|
||||
return
|
||||
}
|
||||
}
|
||||
mut actual := if has_known_actual { known_actual } else { *g.usable_expr_type(id) }
|
||||
if deref_type := g.source_mut_pointer_param_deref_type(id) {
|
||||
actual = deref_type
|
||||
}
|
||||
if node.kind == .ident {
|
||||
if local_type := g.local_ident_type(node.value) {
|
||||
actual = local_type
|
||||
}
|
||||
}
|
||||
if node.kind == .selector {
|
||||
if node.typ != 0 && (flat.source_type_shape(node.typ).has(.option_type) || flat.source_type_shape(node.typ).has(.result_type)) {
|
||||
actual = g.tc.parse_resolution_type(node.typ)
|
||||
} else if declared_type := g.selector_declared_type(id) {
|
||||
actual = declared_type
|
||||
}
|
||||
}
|
||||
actual := g.expected_expr_actual_type(id, node, has_known_actual, known_actual)
|
||||
if expected is types.String && actual is types.Pointer && g.pointer_stringifies_as_address(actual.base_type) && !g.type_names_match(actual.base_type, expected) {
|
||||
g.write('ptr_str(')
|
||||
g.gen_expr(id)
|
||||
@@ -13611,6 +13624,14 @@ fn (mut g FlatGen) gen_expr_with_expected_type_inner(id flat.NodeId, expected_ty
|
||||
return
|
||||
}
|
||||
}
|
||||
if expected is types.Primitive && expected.size <= 64
|
||||
&& !is_f32_primitive(expected) && semantic_actual is types.Primitive
|
||||
&& node.kind != .call {
|
||||
// Mutable-parameter reads and alias casts have been handled above.
|
||||
// Concrete scalar values need none of the aggregate conversions below.
|
||||
g.gen_expr(id)
|
||||
return
|
||||
}
|
||||
mut expected_is_shared_alias := false
|
||||
if _ := g.shared_alias_pointer_type(expected) {
|
||||
expected_is_shared_alias = true
|
||||
@@ -13713,6 +13734,28 @@ fn (mut g FlatGen) gen_expr_with_expected_type_inner(id flat.NodeId, expected_ty
|
||||
g.gen_expr(id)
|
||||
}
|
||||
|
||||
// Storage types override the checked expression type at conversion sites. Resolve
|
||||
// those first instead of computing a type that would immediately be discarded.
|
||||
@[inline]
|
||||
fn (g &FlatGen) expected_expr_actual_type(id flat.NodeId, node &flat.Node, has_known_actual bool, known_actual types.Type) types.Type {
|
||||
if node.kind == .ident {
|
||||
if local_type := g.local_ident_type(node.value) {
|
||||
return local_type
|
||||
}
|
||||
} else if node.kind == .selector {
|
||||
if node.typ != 0 && (flat.source_type_shape(node.typ).has(.option_type)
|
||||
|| flat.source_type_shape(node.typ).has(.result_type)) {
|
||||
return g.tc.parse_resolution_type(node.typ)
|
||||
}
|
||||
if declared_type := g.selector_declared_type(id) {
|
||||
return declared_type
|
||||
}
|
||||
} else if deref_type := g.source_mut_pointer_param_deref_type(id) {
|
||||
return deref_type
|
||||
}
|
||||
return if has_known_actual { known_actual } else { *g.usable_expr_type(id) }
|
||||
}
|
||||
|
||||
fn is_f32_primitive(typ &types.Type) bool {
|
||||
return typ is types.Primitive && typ.props.has(.float) && typ.size == 32
|
||||
}
|
||||
@@ -14918,6 +14961,9 @@ fn (g &FlatGen) const_storage_name(module_name string, name string) string {
|
||||
// same name, the main const keeps the bare key and the builtin const moves to
|
||||
// `builtin.name`, as in the checker (TypeChecker.const_decl_key).
|
||||
fn (mut g FlatGen) register_const_value(module_name string, file string, name string, value_id flat.NodeId) {
|
||||
if !isnil(g.const_short_index) {
|
||||
g.const_short_index.call_names_ready = false
|
||||
}
|
||||
mut key := g.const_storage_name(module_name, name)
|
||||
if key == name && !name.contains('.') {
|
||||
if owner := g.const_modules[name] {
|
||||
@@ -15091,18 +15137,31 @@ fn (mut g FlatGen) precompute_const_short_index() {
|
||||
mut idx := g.const_short_index
|
||||
idx.entries = g.build_unique_const_ref_names()
|
||||
idx.built = true
|
||||
if isnil(g.tc) {
|
||||
return
|
||||
}
|
||||
idx.call_names = [64]u64{}
|
||||
for name, _ in g.const_vals { idx.mark_call_name(name) }
|
||||
for name, _ in g.tc.const_exprs { idx.mark_call_name(name) }
|
||||
for name, _ in g.tc.const_types { idx.mark_call_name(name) }
|
||||
for name, _ in g.tc.const_suffixes { idx.mark_call_name(name) }
|
||||
idx.call_const_vals_count = g.const_vals.len
|
||||
idx.call_const_exprs_count = g.tc.const_exprs.len
|
||||
idx.call_const_types_count = g.tc.const_types.len
|
||||
idx.call_const_suffixes_count = g.tc.const_suffixes.len
|
||||
idx.call_names_ready = true
|
||||
}
|
||||
|
||||
// const_ref_name_from_node converts const ref name from node data for c.
|
||||
fn (g &FlatGen) const_ref_name_from_node(node flat.Node) string {
|
||||
fn (g &FlatGen) const_ref_name_from_node(node &flat.Node) string {
|
||||
if node.kind == .paren && node.children_count > 0 {
|
||||
return g.const_ref_name_from_node(g.a.child_node(&node, 0))
|
||||
return g.const_ref_name_from_node(g.a.node(g.a.child(node, 0)))
|
||||
}
|
||||
if node.kind == .ident {
|
||||
return g.const_ref_name(node.value)
|
||||
}
|
||||
if node.kind == .selector && node.children_count > 0 {
|
||||
base := g.a.child_node(&node, 0)
|
||||
base := g.a.node(g.a.child(node, 0))
|
||||
if base.kind == .ident {
|
||||
if g.selector_base_is_local_value(base.value)
|
||||
|| g.current_module_global_type_for_ident(base.value) != none {
|
||||
@@ -15111,7 +15170,7 @@ fn (g &FlatGen) const_ref_name_from_node(node flat.Node) string {
|
||||
// Prefer the import aliases of the file that owns the selector: global
|
||||
// initializers can be emitted while another file is current.
|
||||
mut resolved_base := base.value
|
||||
if module_name := g.import_alias_module_for_file(base.value, g.node_source_file(&node)) {
|
||||
if module_name := g.import_alias_module_for_file(base.value, g.node_source_file(node)) {
|
||||
resolved_base = module_name
|
||||
} else if module_name := g.selector_base_module_for_member(base.value, node.value) {
|
||||
resolved_base = module_name
|
||||
@@ -15183,7 +15242,7 @@ fn (g &FlatGen) const_ref_name_from_node_cached_for_collect(node flat.Node, uniq
|
||||
if node.children_count == 0 {
|
||||
return ''
|
||||
}
|
||||
return g.const_ref_name_from_node_cached_for_collect(g.a.child_node(&node, 0), unique_index, mut cache)
|
||||
return g.const_ref_name_from_node_cached_for_collect(g.a.node(g.a.child(&node, 0)), unique_index, mut cache)
|
||||
}
|
||||
if node.kind == .ident {
|
||||
if !cgen_name_may_be_const_ref(node.value, unique_index, g.const_vals) {
|
||||
@@ -15201,7 +15260,7 @@ fn (g &FlatGen) const_ref_name_from_node_cached_for_collect(node flat.Node, uniq
|
||||
if node.children_count == 0 {
|
||||
return ''
|
||||
}
|
||||
base := g.a.child_node(&node, 0)
|
||||
base := g.a.node(g.a.child(&node, 0))
|
||||
if base.kind != .ident {
|
||||
return ''
|
||||
}
|
||||
@@ -16772,6 +16831,39 @@ fn (g &FlatGen) assoc_infix_chain_len(node flat.Node) int {
|
||||
return depth
|
||||
}
|
||||
|
||||
// checked_boolean_operand recognizes kinds whose usable type is the checked
|
||||
// type directly, without local storage or generated-selector reinterpretation.
|
||||
fn (g &FlatGen) checked_boolean_operand(id flat.NodeId) bool {
|
||||
node := g.a.nodes[int(id)]
|
||||
if node.kind !in [.bool_literal, .infix] {
|
||||
return false
|
||||
}
|
||||
typ := g.tc.expr_type_reference(id) or { return false }
|
||||
return typ is types.Primitive && typ.props == .boolean && typ.size == 0
|
||||
}
|
||||
|
||||
// gen_boolean_infix keeps the ordinary operand parentheses and generation
|
||||
// context once both operands are known to have the primitive boolean type.
|
||||
fn (mut g FlatGen) gen_boolean_infix(node &flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId) {
|
||||
lhs := g.a.nodes[int(lhs_id)]
|
||||
rhs := g.a.nodes[int(rhs_id)]
|
||||
if lhs.kind == .infix && !infix_can_skip_child_parens(node.op, lhs.op) {
|
||||
g.write('(')
|
||||
g.gen_expr_with_possible_enum_type(lhs_id, types.Type(types.bool_))
|
||||
g.write(')')
|
||||
} else {
|
||||
g.gen_expr_with_possible_enum_type(lhs_id, types.Type(types.bool_))
|
||||
}
|
||||
g.write(' ${g.op_str(node.op)} ')
|
||||
if rhs.kind == .infix && !infix_can_skip_child_parens(node.op, rhs.op) {
|
||||
g.write('(')
|
||||
g.gen_expr_with_possible_enum_type(rhs_id, types.Type(types.bool_))
|
||||
g.write(')')
|
||||
} else {
|
||||
g.gen_expr_with_possible_enum_type(rhs_id, types.Type(types.bool_))
|
||||
}
|
||||
}
|
||||
|
||||
// gen_assoc_infix_chain emits a left-nested `||`/`&&` chain iteratively, producing the
|
||||
// same flat `a || b || c …` C as the recursive path but without growing the stack per
|
||||
// link (a big match's condition chain can be hundreds deep).
|
||||
@@ -17140,9 +17232,21 @@ fn (mut g FlatGen) gen_nonliteral_expr(id flat.NodeId, node &flat.Node) {
|
||||
}
|
||||
lhs_id := g.a.child(node, 0)
|
||||
rhs_id := g.a.child(node, 1)
|
||||
is_logical := node.op in [.logical_and, .logical_or]
|
||||
if is_logical && g.checked_boolean_operand(lhs_id) && g.checked_boolean_operand(rhs_id)
|
||||
&& !g.expr_is_in_translated_file(id) {
|
||||
g.gen_boolean_infix(node, lhs_id, rhs_id)
|
||||
return
|
||||
}
|
||||
old_expected_enum := g.expected_enum
|
||||
lhs_type := g.usable_expr_type(lhs_id)
|
||||
rhs_type := g.usable_expr_type(rhs_id)
|
||||
if is_logical && lhs_type is types.Primitive && lhs_type.props == .boolean && lhs_type.size == 0
|
||||
&& rhs_type is types.Primitive && rhs_type.props == .boolean && rhs_type.size == 0
|
||||
&& !g.expr_is_in_translated_file(id) {
|
||||
g.gen_boolean_infix(node, lhs_id, rhs_id)
|
||||
return
|
||||
}
|
||||
if g.gen_int128_infix(id, node, lhs_id, rhs_id, lhs_type, rhs_type) {
|
||||
g.expected_enum = old_expected_enum
|
||||
return
|
||||
@@ -17225,7 +17329,7 @@ fn (mut g FlatGen) gen_nonliteral_expr(id flat.NodeId, node &flat.Node) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if g.gen_safe_integer_division(node, lhs_id, rhs_id, g.usable_expr_type(id)) {
|
||||
if g.gen_safe_integer_division(id, node, lhs_id, rhs_id) {
|
||||
g.expected_enum = old_expected_enum
|
||||
return
|
||||
}
|
||||
@@ -27858,10 +27962,11 @@ fn integer_cast_const_fits(value i64, src CheckedIntKind, dst CheckedIntKind) bo
|
||||
return limit >= 63 || u64(value) < (u64(1) << limit)
|
||||
}
|
||||
|
||||
fn (mut g FlatGen) gen_safe_integer_division(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, result_type types.Type) bool {
|
||||
fn (mut g FlatGen) gen_safe_integer_division(id flat.NodeId, node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId) bool {
|
||||
if !g.has_builtins || g.static_c_initializer || node.op !in [.div, .mod] {
|
||||
return false
|
||||
}
|
||||
result_type := g.usable_expr_type(id)
|
||||
checked_integer_bounds(result_type) or { return false }
|
||||
c_type := g.translated_numeric_c_type(lhs_id, result_type)
|
||||
if c_type.len == 0 {
|
||||
@@ -28159,6 +28264,12 @@ fn (mut g FlatGen) write(s string) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if s.len == 1 {
|
||||
byte := s[0]
|
||||
g.sb.write_u8(byte)
|
||||
g.line_start = byte == `\n`
|
||||
return
|
||||
}
|
||||
// Append bytes directly instead of copying another string header into the builder.
|
||||
unsafe { g.sb.write_ptr(s.str, s.len) }
|
||||
g.line_start = s[s.len - 1] == `\n`
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_const_call_name_filter_includes_every_lookup_table() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.const_vals['alpha.value_only'] = flat.NodeId(0)
|
||||
tc.const_exprs['beta.expr_only'] = flat.NodeId(0)
|
||||
tc.const_types['gamma.type_only'] = types.Type(types.int_)
|
||||
tc.const_suffixes['suffix_only'] = 'delta.primary'
|
||||
assert !g.const_call_name_is_absent(flat.Node{ kind: .ident, value: 'not_built' })
|
||||
g.precompute_const_short_index()
|
||||
for name in ['value_only', 'expr_only', 'type_only', 'suffix_only'] {
|
||||
for kind in [flat.NodeKind.ident, .selector] {
|
||||
assert !g.const_call_name_is_absent(flat.Node{ kind: kind, value: name })
|
||||
}
|
||||
}
|
||||
assert g.const_call_name_is_absent(flat.Node{ kind: .ident, value: 'definitely_missing' })
|
||||
for name in ['alpha.missing', 'alpha__missing'] {
|
||||
assert g.const_call_name_is_absent(flat.Node{ kind: .ident, value: name })
|
||||
}
|
||||
assert !g.const_call_name_is_absent(flat.Node{ kind: .index, value: 'missing' })
|
||||
}
|
||||
|
||||
fn test_const_call_name_filter_keeps_growth_and_registration_fallbacks() {
|
||||
for table in 0 .. 4 {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.precompute_const_short_index()
|
||||
node := flat.Node{ kind: .ident, value: 'new_const' }
|
||||
assert g.const_call_name_is_absent(node)
|
||||
match table {
|
||||
0 { g.const_vals['new_const'] = flat.NodeId(0) }
|
||||
1 { tc.const_exprs['new_const'] = flat.NodeId(0) }
|
||||
2 { tc.const_types['new_const'] = types.Type(types.int_) }
|
||||
else { tc.const_suffixes['new_const'] = 'primary' }
|
||||
}
|
||||
assert !g.const_call_name_is_absent(node)
|
||||
g.precompute_const_short_index()
|
||||
assert !g.const_call_name_is_absent(node)
|
||||
}
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.register_const_value('main', '', 'existing', flat.NodeId(0))
|
||||
g.precompute_const_short_index()
|
||||
assert g.const_short_index.call_names_ready
|
||||
g.register_const_value('main', '', 'existing', flat.NodeId(1))
|
||||
assert !g.const_short_index.call_names_ready
|
||||
}
|
||||
|
||||
fn test_const_call_name_filter_preserves_function_constants_and_local_shadowing() {
|
||||
mut a := flat.FlatAst.new()
|
||||
target := a.add_val(.ident, 'target')
|
||||
alias := a.add_val(.ident, 'callback')
|
||||
base := a.add_val(.ident, 'short')
|
||||
a.children << base
|
||||
selector := a.add_node(flat.Node{ kind: .selector, value: 'callback', children_start: 0, children_count: 1 })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.imports['short'] = 'library'
|
||||
tc.fn_ret_types['target'] = types.Type(types.int_)
|
||||
tc.const_types['library.callback'] = types.Type(types.FnType{})
|
||||
tc.const_exprs['library.callback'] = target
|
||||
tc.const_suffixes['callback'] = 'library.callback'
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.const_vals['library.callback'] = target
|
||||
g.const_modules['library.callback'] = 'library'
|
||||
g.precompute_const_short_index()
|
||||
assert g.call_target_name(alias) == 'target'
|
||||
assert g.call_target_name(selector) == 'target'
|
||||
// A same-spelled function declaration wins over a module constant.
|
||||
tc.fn_ret_types['callback'] = types.Type(types.int_)
|
||||
assert g.call_target_name(alias) == 'callback'
|
||||
tc.fn_ret_types.delete('callback')
|
||||
tc.cur_scope = types.new_scope(tc.file_scope)
|
||||
tc.cur_scope.insert('callback', types.Type(types.FnType{}))
|
||||
assert g.call_target_name(alias) == 'callback'
|
||||
assert g.const_fn_call_target_name(a.node(alias)) == none
|
||||
}
|
||||
|
||||
fn test_const_call_name_suffix_matches_nonoverlapping_encoded_qualification() {
|
||||
for name in ['', 'plain', '_bar', 'foo___bar', 'foo_____bar', 'a.b__c', 'a__b.c', 'callback__part',
|
||||
'末尾.foo___字段', 'a.', '__', '___', 'a.___'] {
|
||||
assert const_call_name_suffix(name) == name.replace('__', '.').all_after_last('.')
|
||||
}
|
||||
mut names := ['']
|
||||
for _ in 0 .. 6 {
|
||||
mut next := []string{cap: names.len * 3}
|
||||
for prefix in names {
|
||||
for suffix in ['_', '.', 'a'] {
|
||||
name := prefix + suffix
|
||||
assert const_call_name_suffix(name) == name.replace('__', '.').all_after_last('.')
|
||||
next << name
|
||||
}
|
||||
}
|
||||
names = next.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn test_const_call_name_filter_keeps_qualified_and_encoded_suffixes_in_every_table() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.const_vals['module.foo___value'] = flat.NodeId(0)
|
||||
tc.const_exprs['module.foo_____expr'] = flat.NodeId(0)
|
||||
tc.const_types['module.callback__part'] = types.Type(types.int_)
|
||||
tc.const_suffixes['other.alias___suffix'] = 'unrelated.target'
|
||||
g.precompute_const_short_index()
|
||||
for names in [['foo___value', 'module.foo._value', '_value'],
|
||||
['foo_____expr', 'module.foo.._expr', '_expr'],
|
||||
['callback__part', 'module.callback.part', 'part'],
|
||||
['alias___suffix', 'other.alias._suffix', '_suffix']] {
|
||||
for name in names {
|
||||
for kind in [flat.NodeKind.ident, .selector] {
|
||||
assert !g.const_call_name_is_absent(flat.Node{ kind: kind, value: name })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_const_call_name_filter_keeps_encoded_function_constant_resolution() {
|
||||
for spelling in ['library.foo._callback', 'library__foo___callback'] {
|
||||
mut a := flat.FlatAst.new()
|
||||
target := a.add_val(.ident, 'target')
|
||||
alias := a.add_val(.ident, spelling)
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.fn_ret_types['target'] = types.Type(types.int_)
|
||||
tc.const_exprs['library.foo._callback'] = target
|
||||
tc.const_types['library.foo._callback'] = types.Type(types.FnType{})
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.const_vals['library.foo._callback'] = target
|
||||
g.precompute_const_short_index()
|
||||
assert g.call_target_name(alias) == 'target'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_const_call_name_filter_keeps_overlapping_underscore_fallbacks() {
|
||||
for pair in [
|
||||
['module___callback', ''],
|
||||
['module_____callback', ''],
|
||||
['module_______callback', ''],
|
||||
['module__callback', 'module'],
|
||||
['module___callback', 'module_'],
|
||||
['module_____callback', 'module___'],
|
||||
['module_______callback', 'module_____'],
|
||||
] {
|
||||
mut a := flat.FlatAst.new()
|
||||
target := a.add_val(.ident, 'target')
|
||||
alias := a.add_val(.ident, pair[0])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.fn_ret_types['target'] = types.Type(types.int_)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.const_vals['callback'] = target
|
||||
if pair[1].len > 0 {
|
||||
g.const_modules['callback'] = pair[1]
|
||||
}
|
||||
unfiltered := g.call_target_name(alias)
|
||||
assert unfiltered == 'target'
|
||||
g.precompute_const_short_index()
|
||||
assert g.call_target_name(alias) == unfiltered
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn expected_actual_node(mut a flat.FlatAst, kind flat.NodeKind, op flat.Op, value string, typ string, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
kind: kind
|
||||
op: op
|
||||
value: value
|
||||
typ: flat.source_type_id(typ)
|
||||
children_start: start
|
||||
children_count: children.len
|
||||
})
|
||||
}
|
||||
|
||||
fn test_expected_actual_local_storage_overrides_checked_and_known_types() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_node(flat.Node{ kind: .ident, value: 'value', typ: flat.source_type_id('string') })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.file_scope = &types.Scope{}
|
||||
tc.cur_scope = &types.Scope{ parent: tc.file_scope }
|
||||
int_type := types.Type(types.int_)
|
||||
pointer := types.Type(types.Pointer{ base_type: int_type })
|
||||
tc.cur_scope.insert('value', pointer)
|
||||
tc.register_synth_type(id, types.Type(types.string_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
for known in [false, true] {
|
||||
assert g.expected_expr_actual_type(id, a.node(id), known, types.Type(types.bool_)) == pointer
|
||||
g.known_expr_type_id = if known { int(id) } else { -1 }
|
||||
g.known_expr_type = types.Type(types.bool_)
|
||||
g.assert_expr_overrides[int(id)] = 'value'
|
||||
assert g.expr_to_string_with_expected_type(id, int_type) == '*value'
|
||||
assert g.known_expr_type_id == -1
|
||||
}
|
||||
g.cur_param_types['value'] = types.new_type_reference(int_type)
|
||||
assert g.expected_expr_actual_type(id, a.node(id), true, pointer) == int_type
|
||||
}
|
||||
|
||||
fn test_expected_actual_declared_selector_storage_overrides_checked_and_known_types() {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'holder')
|
||||
field := expected_actual_node(mut a, .selector, .none, 'value', '', [base])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
int_type := types.Type(types.int_)
|
||||
pointer := types.Type(types.Pointer{ base_type: int_type })
|
||||
tc.structs['Holder'] = [types.StructField{ name: 'value', typ: pointer }]
|
||||
tc.register_synth_type(field, types.Type(types.string_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cur_param_types['holder'] = tc.parse_type(flat.source_type_id('Holder'))
|
||||
for known in [false, true] {
|
||||
assert g.expected_expr_actual_type(field, a.node(field), known, int_type) == pointer
|
||||
g.known_expr_type_id = if known { int(field) } else { -1 }
|
||||
g.known_expr_type = int_type
|
||||
g.assert_expr_overrides[int(field)] = 'holder.value'
|
||||
assert g.expr_to_string_with_expected_type(field, int_type) == '*holder.value'
|
||||
assert g.known_expr_type_id == -1
|
||||
}
|
||||
}
|
||||
|
||||
fn test_expected_actual_optional_selector_annotation_wins_over_storage() {
|
||||
for annotation in ['?int', '!int'] {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'holder')
|
||||
field := expected_actual_node(mut a, .selector, .none, 'value', annotation, [base])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['Holder'] = [types.StructField{ name: 'value', typ: types.Type(types.string_) }]
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cur_param_types['holder'] = tc.parse_type(flat.source_type_id('Holder'))
|
||||
expected := tc.parse_resolution_type(flat.source_type_id(annotation))
|
||||
for known in [false, true] {
|
||||
assert g.expected_expr_actual_type(field, a.node(field), known, types.Type(types.bool_)) == expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_expected_actual_source_mut_pointer_deref_overrides_known_types_through_wrappers() {
|
||||
mut a := flat.FlatAst.new()
|
||||
param := a.add_val(.ident, 'slot')
|
||||
deref := expected_actual_node(mut a, .prefix, .mul, source_mut_pointer_deref_marker, '', [param])
|
||||
paren := expected_actual_node(mut a, .paren, .none, '', '', [deref])
|
||||
block := expected_actual_node(mut a, .block, .none, '', '', [paren])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
int_type := types.Type(types.int_)
|
||||
pointer := types.Type(types.Pointer{ base_type: types.Type(types.Pointer{ base_type: int_type }) })
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cur_param_types['slot'] = types.new_type_reference(pointer)
|
||||
g.cur_mut_params['slot'] = true
|
||||
g.cur_mut_pointer_params['slot'] = true
|
||||
g.cur_mut_param_owners['slot'] = tc.cur_scope.insert_with_owner('slot', pointer)
|
||||
for id in [deref, paren, block] {
|
||||
for known in [false, true] {
|
||||
assert g.expected_expr_actual_type(id, a.node(id), known, types.Type(types.string_)) == int_type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_expected_actual_unknown_storage_keeps_known_and_checker_fallbacks() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.ident, 'unbound')
|
||||
base := a.add_val(.ident, 'unknown_base')
|
||||
field := expected_actual_node(mut a, .selector, .none, 'field', '', [base])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
int_type := types.Type(types.int_)
|
||||
for node_id in [id, field] { tc.register_synth_type(node_id, int_type) }
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
for node_id in [id, field] {
|
||||
assert g.expected_expr_actual_type(node_id, a.node(node_id), true, types.Type(types.string_)) == types.Type(types.string_)
|
||||
assert g.expected_expr_actual_type(node_id, a.node(node_id), false, types.Type(types.string_)) == int_type
|
||||
}
|
||||
g.known_expr_type_id = int(field)
|
||||
g.known_expr_type = types.Type(types.string_)
|
||||
g.assert_expr_overrides[int(id)] = 'unbound'
|
||||
old_expected := types.Type(types.bool_)
|
||||
old_enum := flat.source_type_id('PreviousEnum')
|
||||
g.expected_expr_type = old_expected
|
||||
g.expected_enum = old_enum
|
||||
assert g.expr_to_string_with_expected_type(id, int_type) == 'unbound'
|
||||
assert g.known_expr_type_id == int(field)
|
||||
assert g.expected_expr_type == old_expected
|
||||
assert g.expected_enum == old_enum
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.token
|
||||
import v.types
|
||||
|
||||
fn expected_scalar_alias(typ types.Type) types.Type {
|
||||
return types.Alias{
|
||||
name_id: flat.source_type_id('ScalarAlias')
|
||||
base_type: typ
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_scalar_output(mut g FlatGen, id flat.NodeId, expected types.Type, actual types.Type, known bool) string {
|
||||
old_expected := types.Type(types.string_)
|
||||
old_enum := flat.source_type_id('PreviousEnum')
|
||||
g.expected_expr_type = old_expected
|
||||
g.expected_enum = old_enum
|
||||
g.known_expr_type = actual
|
||||
g.known_expr_type_id = if known { int(id) } else { -1 }
|
||||
output := g.expr_to_string_with_expected_type(id, expected)
|
||||
assert g.expected_expr_type == old_expected
|
||||
assert g.expected_enum == old_enum
|
||||
assert g.known_expr_type_id == -1
|
||||
return output
|
||||
}
|
||||
|
||||
fn expected_scalar_node(mut a flat.FlatAst, kind flat.NodeKind, op flat.Op, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
kind: kind
|
||||
op: op
|
||||
children_start: start
|
||||
children_count: children.len
|
||||
})
|
||||
}
|
||||
|
||||
fn test_expected_scalar_literals_preserve_fallback_output_and_context() {
|
||||
for kind, values in {
|
||||
flat.NodeKind.int_literal: ['0', '1_000', '0o7_7', '18446744073709551615']
|
||||
flat.NodeKind.float_literal: ['0.0', '1_000.5', '1.0e+1_0']
|
||||
flat.NodeKind.bool_literal: ['true', 'false']
|
||||
} {
|
||||
for value in values {
|
||||
actual := if kind == .float_literal {
|
||||
types.Type(types.f64_)
|
||||
} else if kind == .bool_literal {
|
||||
types.Type(types.bool_)
|
||||
} else {
|
||||
types.Type(types.u64_)
|
||||
}
|
||||
expected_types := if kind == .bool_literal {
|
||||
[types.Type(types.bool_)]
|
||||
} else if kind == .float_literal {
|
||||
[types.Type(types.f64_)]
|
||||
} else {
|
||||
[types.Type(types.int_), types.Type(types.u64_), types.Type(types.f32_),
|
||||
types.Type(types.f64_)]
|
||||
}
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(kind, value)
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.register_synth_type(id, actual)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
for expected in expected_types {
|
||||
for known in [false, true] {
|
||||
fallback := expected_scalar_output(mut g, id, expected_scalar_alias(expected), actual, known)
|
||||
assert expected_scalar_output(mut g, id, expected, actual, known) == fallback, value
|
||||
}
|
||||
}
|
||||
for replacement in ['assert_literal', 'callback_literal'] {
|
||||
g.assert_expr_overrides.clear()
|
||||
g.callback_target_overrides.clear()
|
||||
if replacement == 'assert_literal' {
|
||||
g.assert_expr_overrides[int(id)] = replacement
|
||||
} else {
|
||||
g.callback_target_overrides[int(id)] = replacement
|
||||
}
|
||||
assert expected_scalar_output(mut g, id, actual, actual, true) == replacement
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_expected_scalar_expressions_preserve_fallback_and_unrelated_known_type() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.int_literal, '3')
|
||||
right := a.add_val(.int_literal, '4')
|
||||
value := a.add_val(.ident, 'value')
|
||||
sum := expected_scalar_node(mut a, .infix, .plus, [left, right])
|
||||
paren := expected_scalar_node(mut a, .paren, .none, [sum])
|
||||
negative := expected_scalar_node(mut a, .prefix, .minus, [paren])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
int_type := types.Type(types.int_)
|
||||
tc.cur_scope.insert('value', int_type)
|
||||
for id in [left, right, value, sum, paren, negative] {
|
||||
tc.register_synth_type(id, int_type)
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
for id in [value, sum, paren, negative] {
|
||||
for known in [false, true] {
|
||||
fallback := expected_scalar_output(mut g, id, expected_scalar_alias(int_type), int_type, known)
|
||||
assert expected_scalar_output(mut g, id, int_type, int_type, known) == fallback
|
||||
}
|
||||
}
|
||||
g.known_expr_type_id = int(right)
|
||||
g.known_expr_type = types.Type(types.u64_)
|
||||
assert g.expr_to_string_with_expected_type(left, int_type) == '3'
|
||||
assert g.known_expr_type_id == int(right)
|
||||
assert g.known_expr_type == types.Type(types.u64_)
|
||||
}
|
||||
|
||||
fn test_expected_scalar_shortcuts_keep_f32_rounding_and_int128_widening() {
|
||||
mut a := flat.FlatAst.new()
|
||||
fraction := a.add_val(.float_literal, '1.0000000596046448')
|
||||
negative := expected_scalar_node(mut a, .prefix, .minus, [fraction])
|
||||
integer := a.add_val(.int_literal, '7')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.register_synth_type(fraction, types.Type(types.f64_))
|
||||
tc.register_synth_type(negative, types.Type(types.f64_))
|
||||
tc.register_synth_type(integer, types.Type(types.int_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
assert expected_scalar_output(mut g, fraction, types.Type(types.f32_), types.Type(types.f64_), true) == '(float)(1.0000000596046448)'
|
||||
assert expected_scalar_output(mut g, negative, types.Type(types.f32_), types.Type(types.f64_), true) == '-(float)(1.0000000596046448)'
|
||||
for portable in [false, true] {
|
||||
g.compile_defines = if portable { ['v3_no_native_int128'] } else { []string{} }
|
||||
assert expected_scalar_output(mut g, integer, types.Type(types.i128_), types.Type(types.int_), true) == '__v_i128_from_i64((i64)(7))'
|
||||
assert expected_scalar_output(mut g, integer, types.Type(types.u128_), types.Type(types.int_), true) == '__v_u128_from_i64((i64)(7))'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_expected_scalar_call_keeps_declared_pointer_return_and_mut_parameter_read() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'read_pointer')
|
||||
call := expected_scalar_node(mut a, .call, .none, [callee])
|
||||
param := a.add_val(.ident, 'value')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
int_type := types.Type(types.int_)
|
||||
pointer_type := types.Type(types.Pointer{ base_type: int_type })
|
||||
tc.fn_ret_types['read_pointer'] = pointer_type
|
||||
tc.register_synth_type(call, int_type)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.assert_expr_overrides[int(call)] = 'read_pointer()'
|
||||
assert expected_scalar_output(mut g, call, int_type, int_type, true) == '*read_pointer()'
|
||||
g.cur_param_types['value'] = types.new_type_reference(pointer_type)
|
||||
g.cur_mut_params['value'] = true
|
||||
g.cur_mut_param_owners['value'] = tc.cur_scope.insert_with_owner('value', pointer_type)
|
||||
assert expected_scalar_output(mut g, param, int_type, int_type, true) == '*value'
|
||||
}
|
||||
|
||||
fn test_expected_scalar_translated_literals_keep_boolean_and_int_normalization() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut files := token.FileSet.new()
|
||||
a.source_files[1] = files.add_file('translated_scalar.v', 1)
|
||||
wide := a.add_node(flat.Node{ kind: .int_literal, value: '256', pos: token.new_pos(1, 0) })
|
||||
fraction := a.add_node(flat.Node{ kind: .float_literal, value: '0.5', pos: token.new_pos(1, 0) })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.translated_files['translated_scalar.v'] = true
|
||||
tc.register_synth_type(wide, types.Type(types.int_))
|
||||
tc.register_synth_type(fraction, types.Type(types.f64_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
assert expected_scalar_output(mut g, wide, types.Type(types.bool_), types.Type(types.int_), true) == '((256) != 0)'
|
||||
assert expected_scalar_output(mut g, fraction, types.Type(types.bool_), types.Type(types.f64_), true) == '((0.5) != 0)'
|
||||
assert expected_scalar_output(mut g, wide, types.Type(types.int_), types.Type(types.int_), true) == '((i32)(256))'
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn expected_string_alias() types.Type {
|
||||
return types.Alias{
|
||||
name_id: flat.source_type_id('StringAlias')
|
||||
base_type: types.new_type_reference(types.Type(types.string_))
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_string_node(mut a flat.FlatAst, kind flat.NodeKind, value string, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
kind: kind
|
||||
value: value
|
||||
children_start: start
|
||||
children_count: children.len
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_string_output(mut g FlatGen, id flat.NodeId, expected types.Type, known types.Type, has_known bool) string {
|
||||
g.known_expr_type_id = if has_known { int(id) } else { -1 }
|
||||
g.known_expr_type = known
|
||||
return g.expr_to_string_with_expected_type(id, expected)
|
||||
}
|
||||
|
||||
fn test_expected_string_literal_matches_conversion_fallback_and_restores_context() {
|
||||
for raw in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_node(flat.Node{
|
||||
kind: .string_literal
|
||||
value: 'hello'
|
||||
typ: flat.source_type_id(if raw { 'raw:string' } else { 'string' })
|
||||
})
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
old_expected := types.Type(types.Pointer{ base_type: types.Type(types.u8_) })
|
||||
old_enum := flat.source_type_id('PreviousEnum')
|
||||
g.expected_expr_type = old_expected
|
||||
g.expected_enum = old_enum
|
||||
for has_known in [false, true] {
|
||||
actual := types.Type(types.string_)
|
||||
result := expected_string_output(mut g, id, actual, actual, has_known)
|
||||
fallback := expected_string_output(mut g, id, expected_string_alias(), actual, has_known)
|
||||
assert result == '_str_0'
|
||||
assert result == fallback
|
||||
assert g.known_expr_type_id == -1
|
||||
assert g.expected_expr_type == old_expected
|
||||
assert g.expected_enum == old_enum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_expected_string_interpolation_and_lowered_join_keep_child_context() {
|
||||
for join in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_node(flat.Node{ kind: .string_literal, value: 'hello', typ: flat.source_type_id('raw:string') })
|
||||
right := a.add_val(.string_literal, 'world')
|
||||
id := expected_string_node(mut a, .string_interp, if join { '__v3_string_join' } else { '' }, [
|
||||
left,
|
||||
right,
|
||||
])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
old_expected := types.Type(types.Pointer{ base_type: types.Type(types.u8_) })
|
||||
g.expected_expr_type = old_expected
|
||||
for has_known in [false, true] {
|
||||
g.tmp_count = 0
|
||||
result := expected_string_output(mut g, id, types.Type(types.string_), types.Type(types.string_), has_known)
|
||||
g.tmp_count = 0
|
||||
fallback := expected_string_output(mut g, id, expected_string_alias(), types.Type(types.string_), has_known)
|
||||
assert result == fallback
|
||||
assert result.contains('_str_0') && result.contains('_str_1')
|
||||
assert !result.contains('"hello"')
|
||||
assert g.known_expr_type_id == -1
|
||||
assert g.expected_expr_type == old_expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_expected_string_conflicting_known_pointer_keeps_conversion() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.string_literal, 'hello')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
known := types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
result := expected_string_output(mut g, id, types.Type(types.string_), known, true)
|
||||
assert result == 'ptr_str(_str_0)'
|
||||
assert g.known_expr_type_id == -1
|
||||
}
|
||||
|
||||
fn test_expected_string_preserves_expression_overrides_and_unrelated_known_id() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.string_literal, 'hello')
|
||||
other := a.add_val(.int_literal, '7')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.assert_expr_overrides[int(id)] = 'replacement'
|
||||
g.known_expr_type_id = int(other)
|
||||
g.known_expr_type = types.Type(types.int_)
|
||||
assert g.expr_to_string_with_expected_type(id, types.Type(types.string_)) == 'replacement'
|
||||
assert g.known_expr_type_id == int(other)
|
||||
g.assert_expr_overrides.clear()
|
||||
g.callback_target_overrides[int(id)] = 'callback_replacement'
|
||||
assert expected_string_output(mut g, id, types.Type(types.string_), types.Type(types.string_), true) == 'callback_replacement'
|
||||
assert g.known_expr_type_id == -1
|
||||
}
|
||||
+134
-72
@@ -1947,9 +1947,9 @@ fn (mut g FlatGen) direct_call_name_for_call(id flat.NodeId, name string) string
|
||||
return g.direct_call_name(name)
|
||||
}
|
||||
|
||||
fn (mut g FlatGen) direct_call_name_for_call_node(id flat.NodeId, node flat.Node, name string) string {
|
||||
fn (mut g FlatGen) direct_call_name_for_call_node(id flat.NodeId, node &flat.Node, name string) string {
|
||||
if node.children_count > 0 {
|
||||
fn_node := g.a.child_node(&node, 0)
|
||||
fn_node := g.a.child_node(node, 0)
|
||||
if shadow_name := g.main_runtime_shadow_call_c_name(node, fn_node) {
|
||||
return shadow_name
|
||||
}
|
||||
@@ -7143,16 +7143,16 @@ fn (mut g FlatGen) gen_optional_error_from_call(ct string, node flat.Node) {
|
||||
g.write('}')
|
||||
}
|
||||
|
||||
fn (g &FlatGen) is_runtime_new_map_call(node flat.Node) bool {
|
||||
fn (g &FlatGen) is_runtime_new_map_call(node &flat.Node) bool {
|
||||
if node.children_count < 3 {
|
||||
return false
|
||||
}
|
||||
key_size := g.a.child_node(&node, 1)
|
||||
value_size := g.a.child_node(&node, 2)
|
||||
key_size := g.a.child_node(node, 1)
|
||||
value_size := g.a.child_node(node, 2)
|
||||
return key_size.kind == .sizeof_expr && value_size.kind == .sizeof_expr
|
||||
}
|
||||
|
||||
fn (g &FlatGen) main_runtime_shadow_call_c_name(node flat.Node, fn_node flat.Node) ?string {
|
||||
fn (g &FlatGen) main_runtime_shadow_call_c_name(node &flat.Node, fn_node &flat.Node) ?string {
|
||||
if fn_node.kind != .ident || g.is_runtime_new_map_call(node) {
|
||||
return none
|
||||
}
|
||||
@@ -7290,7 +7290,7 @@ fn (mut g FlatGen) gen_owned_capture_closure_create(id flat.NodeId, node flat.No
|
||||
return true
|
||||
}
|
||||
|
||||
fn (g &FlatGen) trace_call_name(fn_node flat.Node, fn_name string, target_name string, resolved_target_name string) ?string {
|
||||
fn (g &FlatGen) trace_call_name(fn_node &flat.Node, fn_name string, target_name string, resolved_target_name string) ?string {
|
||||
if !g.trace_calls || g.inside_trace_call {
|
||||
return none
|
||||
}
|
||||
@@ -7789,6 +7789,26 @@ fn (mut g FlatGen) gen_builtin_panic_call(node flat.Node) {
|
||||
g.write(')')
|
||||
}
|
||||
|
||||
// call_ident_cast_type_name preserves bare-type priority without repeating its
|
||||
// declaration probes or qualifying local function values.
|
||||
@[inline]
|
||||
fn (g &FlatGen) call_ident_cast_type_name(name string, is_local_fn_value bool) ?string {
|
||||
if is_local_fn_value || !g.ident_may_name_type(name) {
|
||||
return none
|
||||
}
|
||||
if g.tc.type_aliases.contains(name) || name in g.tc.structs || name in g.tc.enum_names
|
||||
|| g.tc.sum_types.contains(name) || name in g.tc.interface_names {
|
||||
return name
|
||||
}
|
||||
qname := g.tc.qualify_name(name)
|
||||
if qname != name && (g.tc.type_aliases.contains(qname) || qname in g.tc.structs
|
||||
|| qname in g.tc.enum_names || g.tc.sum_types.contains(qname)
|
||||
|| qname in g.tc.interface_names) {
|
||||
return qname
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// gen_call emits call output for c.
|
||||
@[direct_array_access]
|
||||
fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
@@ -7803,7 +7823,8 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
mut fn_node := g.a.child_node(node, 0)
|
||||
callee_name := g.call_target_name(g.a.child(node, 0))
|
||||
mut target_name := callee_name
|
||||
mut resolved_target_name := g.tc.resolved_call_name(id) or { '' }
|
||||
checker_resolved_name := g.tc.resolved_call_name(id)
|
||||
mut resolved_target_name := checker_resolved_name or { '' }
|
||||
if fn_node.kind == .ident && node.pos.is_valid() && g.tc.cur_module == 'main'
|
||||
&& g.tc.cur_file.ends_with('.vsh') {
|
||||
if g.file_declares_fn(id, fn_node.value) {
|
||||
@@ -8242,7 +8263,7 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
g.write(')')
|
||||
return
|
||||
}
|
||||
if resolved := g.tc.resolved_call_name(id) {
|
||||
if resolved := checker_resolved_name {
|
||||
if arg_start := g.resolved_selector_module_arg_start(resolved, node) {
|
||||
emitted_resolved := g.direct_call_name_for_call_node(id, node, resolved)
|
||||
g.write(emitted_resolved)
|
||||
@@ -8822,24 +8843,9 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
fn_id := g.a.child(node, 0)
|
||||
fn_ident := g.a.nodes[int(fn_id)]
|
||||
if fn_ident.kind == .ident {
|
||||
// Most callees are functions; only a type name can be a cast here.
|
||||
may_name_type := g.ident_may_name_type(fn_ident.value)
|
||||
qname := if may_name_type { g.tc.qualify_name(fn_ident.value) } else { '' }
|
||||
is_local_fn_value := g.current_param_type(fn_ident.value) != none
|
||||
|| g.cur_scope_has_local_name(fn_ident.value)
|
||||
if may_name_type && !is_local_fn_value && (g.tc.type_aliases.contains(fn_ident.value) || g.tc.type_aliases.contains(qname)
|
||||
|| fn_ident.value in g.tc.structs || qname in g.tc.structs
|
||||
|| fn_ident.value in g.tc.enum_names || qname in g.tc.enum_names
|
||||
|| g.tc.sum_types.contains(fn_ident.value) || g.tc.sum_types.contains(qname)
|
||||
|| fn_ident.value in g.tc.interface_names || qname in g.tc.interface_names) {
|
||||
type_name := if g.tc.type_aliases.contains(fn_ident.value)
|
||||
|| fn_ident.value in g.tc.structs || fn_ident.value in g.tc.enum_names
|
||||
|| g.tc.sum_types.contains(fn_ident.value)
|
||||
|| fn_ident.value in g.tc.interface_names {
|
||||
fn_ident.value
|
||||
} else {
|
||||
qname
|
||||
}
|
||||
if type_name := g.call_ident_cast_type_name(fn_ident.value, is_local_fn_value) {
|
||||
target_type := g.tc.parse_type(flat.source_type_id(type_name))
|
||||
if target_type is types.Interface && node.children_count > 1 {
|
||||
g.gen_expr_with_expected_type(g.a.child(node, 1), target_type)
|
||||
@@ -8951,12 +8957,13 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
}
|
||||
callee_is_local_fn_value := uses_fn_value_param_types
|
||||
&& g.local_fn_value_callee(g.a.child(node, 0))
|
||||
callee_has_named_decl := fn_node.kind == .ident
|
||||
may_need_fn_value_params := !uses_fn_value_param_types && param_types.len == 0
|
||||
&& !is_method
|
||||
callee_has_named_decl := may_need_fn_value_params && fn_node.kind == .ident
|
||||
&& (fn_decl_module_key(g.tc.cur_module, fn_node.value) in g.fn_decl_param_types
|
||||
|| actual_fn in g.fn_decl_param_types
|
||||
|| emitted_callee_name in g.fn_decl_param_types)
|
||||
may_recover_fn_value_params := !uses_fn_value_param_types && param_types.len == 0
|
||||
&& !is_method && !callee_has_named_decl
|
||||
may_recover_fn_value_params := may_need_fn_value_params && !callee_has_named_decl
|
||||
has_optional_params := params_have_optional_result(param_types)
|
||||
// Only optional arguments consume this ABI choice. Preserve the lookup
|
||||
// when an unresolved function value can still supply its parameters below.
|
||||
@@ -9078,7 +9085,7 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
arg_start = 1
|
||||
}
|
||||
} else if node.children_count == param_types.len + 2 {
|
||||
resolved_call := g.tc.resolved_call_name(id) or { '' }
|
||||
resolved_call := checker_resolved_name or { '' }
|
||||
if resolved_call.contains('.') {
|
||||
resolved_base := resolved_call.all_before_last('.')
|
||||
first_arg := g.a.child_node(node, 1)
|
||||
@@ -9394,16 +9401,14 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
}
|
||||
}
|
||||
}
|
||||
is_rvalue := arg_node.kind in [.call, .struct_init, .cast_expr, .assoc]
|
||||
|| (arg_node.kind == .index && arg_node.value == 'range')
|
||||
|| g.arg_is_const_ident(arg_node)
|
||||
if needs_addr && g.arg_is_const_ident(arg_node) {
|
||||
value_type := g.addressed_const_arg_value_type(arg_id, param_types[arg_idx])
|
||||
ct := g.tc.c_type(value_type)
|
||||
g.write('(${ct}[]){')
|
||||
g.gen_expr_with_expected_type(arg_id, value_type)
|
||||
g.write('}')
|
||||
} else if needs_addr && is_rvalue {
|
||||
} else if needs_addr && (arg_node.kind in [.call, .struct_init, .cast_expr, .assoc]
|
||||
|| (arg_node.kind == .index && arg_node.value == 'range')) {
|
||||
pt := param_types[arg_idx]
|
||||
ct := g.tc.c_type(types.unwrap_pointer(pt))
|
||||
if g.c_typedef_nil_call(arg_id) {
|
||||
@@ -9505,7 +9510,7 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node &flat.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g FlatGen) gen_c_va_macro_call(node flat.Node, target_name string, resolved_name string) bool {
|
||||
fn (mut g FlatGen) gen_c_va_macro_call(node &flat.Node, target_name string, resolved_name string) bool {
|
||||
name := if resolved_name in ['C.va_arg', 'C.va_start', 'C.va_end', 'C.va_copy'] {
|
||||
resolved_name
|
||||
} else {
|
||||
@@ -9520,7 +9525,7 @@ fn (mut g FlatGen) gen_c_va_macro_call(node flat.Node, target_name string, resol
|
||||
g.write('0')
|
||||
return true
|
||||
}
|
||||
type_arg_name_source_id := g.generic_call_type_arg_name(g.a.child(&node, 1))
|
||||
type_arg_name_source_id := g.generic_call_type_arg_name(g.a.child(node, 1))
|
||||
|
||||
target_type := g.tc.parse_type(type_arg_name_source_id)
|
||||
mut target_ct := g.tc.c_type(target_type)
|
||||
@@ -9528,7 +9533,7 @@ fn (mut g FlatGen) gen_c_va_macro_call(node flat.Node, target_name string, resol
|
||||
target_ct = g.resolve_fn_ptr_type(target_ct)
|
||||
}
|
||||
g.write('${macro_name}(')
|
||||
arg_id := g.a.child(&node, 2)
|
||||
arg_id := g.a.child(node, 2)
|
||||
if !g.gen_c_va_list_macro_arg_direct(1, arg_id, name) {
|
||||
g.gen_expr(arg_id)
|
||||
}
|
||||
@@ -9540,7 +9545,7 @@ fn (mut g FlatGen) gen_c_va_macro_call(node flat.Node, target_name string, resol
|
||||
if i > 1 {
|
||||
g.write(', ')
|
||||
}
|
||||
arg_id := g.a.child(&node, i)
|
||||
arg_id := g.a.child(node, i)
|
||||
if !g.gen_c_va_list_macro_arg_direct(i - 1, arg_id, name) {
|
||||
g.gen_expr(arg_id)
|
||||
}
|
||||
@@ -10402,6 +10407,9 @@ fn (g &FlatGen) call_target_name(id flat.NodeId) string {
|
||||
node := g.a.nodes[int(id)]
|
||||
match node.kind {
|
||||
.ident {
|
||||
if g.const_call_name_is_absent(node) {
|
||||
return node.value
|
||||
}
|
||||
// A bare call target resolves to a function in the current module before a
|
||||
// same-spelled imported or module constant. For example, builtin.f32_max()
|
||||
// must not be rewritten to the math.internal.f32_max constant.
|
||||
@@ -10442,8 +10450,60 @@ fn (g &FlatGen) call_target_name(id flat.NodeId) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Match replace('__', '.').all_after_last('.') without allocating transformed names.
|
||||
// Consume underscore pairs from the left so an odd trailing underscore is preserved.
|
||||
@[inline]
|
||||
fn const_call_name_suffix(name string) string {
|
||||
mut start := 0
|
||||
mut i := 0
|
||||
for i < name.len {
|
||||
if name[i] == `.` {
|
||||
start = i + 1
|
||||
} else if name[i] == `_` && i + 1 < name.len && name[i + 1] == `_` {
|
||||
i++
|
||||
start = i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
return name[start..]
|
||||
}
|
||||
|
||||
fn (mut idx ConstShortIndex) mark_call_name(name string) {
|
||||
slot := global_name_filter_slot(const_call_name_suffix(name))
|
||||
idx.call_names[slot >> 6] |= u64(1) << u32(slot & 63)
|
||||
}
|
||||
|
||||
// Normal generation freezes the const tables before sharing this index with workers.
|
||||
// Focused helpers and newly registered constants keep the complete lookup path.
|
||||
@[inline]
|
||||
fn (g &FlatGen) const_call_name_is_absent(node &flat.Node) bool {
|
||||
if node.kind !in [.ident, .selector] || isnil(g.const_short_index) {
|
||||
return false
|
||||
}
|
||||
idx := g.const_short_index
|
||||
if !idx.call_names_ready || idx.call_const_vals_count != g.const_vals.len
|
||||
|| idx.call_const_exprs_count != g.tc.const_exprs.len
|
||||
|| idx.call_const_types_count != g.tc.const_types.len
|
||||
|| idx.call_const_suffixes_count != g.tc.const_suffixes.len {
|
||||
return false
|
||||
}
|
||||
suffix := const_call_name_suffix(node.value)
|
||||
// Rightmost overlapping underscore pairs can strip this leading underscore.
|
||||
if suffix.len > 0 && suffix[0] == `_` {
|
||||
return false
|
||||
}
|
||||
slot := global_name_filter_slot(suffix)
|
||||
if idx.call_names[slot >> 6] & (u64(1) << u32(slot & 63)) != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn (g &FlatGen) const_fn_call_target_name(node flat.Node) ?string {
|
||||
fn (g &FlatGen) const_fn_call_target_name(node &flat.Node) ?string {
|
||||
if g.const_call_name_is_absent(node) {
|
||||
return none
|
||||
}
|
||||
// Parameters and local callbacks keep their lexical binding, even if another
|
||||
// module publishes a function constant with the same name.
|
||||
if node.kind == .ident && g.selector_base_is_local_value(node.value) {
|
||||
@@ -10490,7 +10550,7 @@ fn (g &FlatGen) const_fn_call_type(node flat.Node, key string, expr_id flat.Node
|
||||
return g.tc.const_types[key] or { g.tc.resolve_type(expr_id) }
|
||||
}
|
||||
|
||||
fn (g &FlatGen) const_key_for_call_target(node flat.Node) ?string {
|
||||
fn (g &FlatGen) const_key_for_call_target(node &flat.Node) ?string {
|
||||
name := g.call_target_key(node)
|
||||
if name.len == 0 {
|
||||
return none
|
||||
@@ -10506,7 +10566,7 @@ fn (g &FlatGen) const_key_for_call_target(node flat.Node) ?string {
|
||||
return none
|
||||
}
|
||||
|
||||
fn (g &FlatGen) call_target_key(node flat.Node) string {
|
||||
fn (g &FlatGen) call_target_key(node &flat.Node) string {
|
||||
match node.kind {
|
||||
.ident {
|
||||
if node.value.len == 0 {
|
||||
@@ -10524,7 +10584,7 @@ fn (g &FlatGen) call_target_key(node flat.Node) string {
|
||||
if node.children_count == 0 {
|
||||
return node.value
|
||||
}
|
||||
base := g.a.child_node(&node, 0)
|
||||
base := g.a.child_node(node, 0)
|
||||
if base.kind != .ident {
|
||||
return ''
|
||||
}
|
||||
@@ -10537,7 +10597,7 @@ fn (g &FlatGen) call_target_key(node flat.Node) string {
|
||||
}
|
||||
.index {
|
||||
if node.children_count > 0 {
|
||||
return g.call_target_key(g.a.child_node(&node, 0))
|
||||
return g.call_target_key(g.a.child_node(node, 0))
|
||||
}
|
||||
return node.value
|
||||
}
|
||||
@@ -14531,16 +14591,16 @@ fn (mut g FlatGen) gen_guarded_anon_self_call_stmt(node flat.Node) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
fn (g &FlatGen) resolved_selector_module_arg_start(resolved string, node flat.Node) ?int {
|
||||
fn (g &FlatGen) resolved_selector_module_arg_start(resolved string, node &flat.Node) ?int {
|
||||
if !resolved.contains('.') || node.children_count < 2 {
|
||||
return none
|
||||
}
|
||||
callee := g.a.child_node(&node, 0)
|
||||
callee := g.a.child_node(node, 0)
|
||||
if callee.kind != .selector || callee.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
base := g.a.child_node(callee, 0)
|
||||
first_arg := g.a.child_node(&node, 1)
|
||||
first_arg := g.a.child_node(node, 1)
|
||||
if base.kind != .ident || !g.selector_module_base_arg_matches(first_arg, base.value) {
|
||||
return none
|
||||
}
|
||||
@@ -14566,11 +14626,11 @@ fn (g &FlatGen) selector_module_base_arg_matches(arg flat.Node, base string) boo
|
||||
return false
|
||||
}
|
||||
|
||||
fn (g &FlatGen) selector_call_can_emit_direct(resolved string, node flat.Node) bool {
|
||||
fn (g &FlatGen) selector_call_can_emit_direct(resolved string, node &flat.Node) bool {
|
||||
if resolved.len == 0 || node.children_count == 0 {
|
||||
return false
|
||||
}
|
||||
fn_node := g.a.child_node(&node, 0)
|
||||
fn_node := g.a.child_node(node, 0)
|
||||
if fn_node.kind != .selector {
|
||||
return false
|
||||
}
|
||||
@@ -14756,17 +14816,17 @@ fn (g &FlatGen) target_module_call_name(target string, node flat.Node) ?string {
|
||||
}
|
||||
}
|
||||
static_name := target
|
||||
if static_name in g.tc.fn_param_types || static_name in g.tc.fn_ret_types
|
||||
|| static_name in g.tc.fn_generic_params {
|
||||
params := g.tc.fn_param_types[static_name] or {
|
||||
if static_name in g.tc.fn_ret_types && node.children_count == arg_start {
|
||||
return static_name
|
||||
}
|
||||
return none
|
||||
}
|
||||
if params := g.tc.fn_param_types[static_name] {
|
||||
if g.module_call_arg_count_matches(static_name, params, node.children_count - arg_start) {
|
||||
return static_name
|
||||
}
|
||||
} else if static_name in g.tc.fn_ret_types {
|
||||
if node.children_count == arg_start {
|
||||
return static_name
|
||||
}
|
||||
return none
|
||||
} else if static_name in g.tc.fn_generic_params {
|
||||
return none
|
||||
}
|
||||
mod_name := g.selector_base_module(base) or {
|
||||
if base == g.tc.cur_module {
|
||||
@@ -14932,16 +14992,16 @@ fn (mut g FlatGen) gen_mut_param_value_call_arg(arg_id flat.NodeId, arg_node fla
|
||||
}
|
||||
|
||||
// gen_call_args emits call args output for c.
|
||||
fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) {
|
||||
fn (mut g FlatGen) gen_call_args(fn_name string, node &flat.Node, start int) {
|
||||
callee_name := if node.children_count > 0 {
|
||||
g.call_target_name(g.a.child(&node, 0))
|
||||
g.call_target_name(g.a.child(node, 0))
|
||||
} else {
|
||||
''
|
||||
}
|
||||
callee_fn_value_params := if start == 1 && node.children_count > 0
|
||||
&& !fn_name.starts_with('C.') && !callee_name.starts_with('C.')
|
||||
&& !g.call_callee_is_module_selector(node) {
|
||||
g.fn_value_call_param_types(g.a.child(&node, 0))
|
||||
g.fn_value_call_param_types(g.a.child(node, 0))
|
||||
} else {
|
||||
none
|
||||
}
|
||||
@@ -14950,7 +15010,7 @@ fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) {
|
||||
|
||||
// The caller retains the callee lookup across call lowering; the AST and lexical scope
|
||||
// are unchanged until argument emission starts.
|
||||
fn (mut g FlatGen) gen_call_args_for_callee(fn_name string, node flat.Node, start int, callee_name string, callee_fn_value_params ?[]types.Type) {
|
||||
fn (mut g FlatGen) gen_call_args_for_callee(fn_name string, node &flat.Node, start int, callee_name string, callee_fn_value_params ?[]types.Type) {
|
||||
is_c_call := fn_name.starts_with('C.') || callee_name.starts_with('C.')
|
||||
mut param_types := g.param_types_for(fn_name, fn_name.all_after_last('.'))
|
||||
callee_is_module_selector := g.call_callee_is_module_selector(node)
|
||||
@@ -14977,7 +15037,7 @@ fn (mut g FlatGen) gen_call_args_for_callee(fn_name string, node flat.Node, star
|
||||
|| g.call_uses_concrete_optional_params(callee_name)
|
||||
|| g.call_uses_concrete_optional_params(g.direct_call_name(callee_name))
|
||||
|| (node.children_count > 0
|
||||
&& g.call_callee_uses_specialized_generic_abi(g.a.child(&node, 0))))
|
||||
&& g.call_callee_uses_specialized_generic_abi(g.a.child(node, 0))))
|
||||
c_variadic_prefix := if is_c_call {
|
||||
g.c_fn_abi_variadic_prefix(fn_name, callee_name, param_types)
|
||||
} else {
|
||||
@@ -15007,7 +15067,7 @@ fn (mut g FlatGen) gen_call_args_for_callee(fn_name string, node flat.Node, star
|
||||
is_variadic := variadic_idx >= 0 && num_args > param_types.len
|
||||
for i in start .. node.children_count {
|
||||
arg_idx := i - start
|
||||
arg_id := g.a.child(&node, i)
|
||||
arg_id := g.a.child(node, i)
|
||||
arg_node := g.a.nodes[int(arg_id)]
|
||||
if (param_types.len > 0 || uses_fn_value_param_types) && !is_native_variadic_fn
|
||||
&& variadic_idx < 0 && arg_idx >= typed_param_count {
|
||||
@@ -15118,7 +15178,7 @@ fn (mut g FlatGen) gen_call_args_for_callee(fn_name string, node flat.Node, star
|
||||
}
|
||||
}
|
||||
if arg_idx == 1 && fn_name in ['array_push', 'array__push']
|
||||
&& g.gen_shared_array_push_arg(node.value, g.a.child(&node, start), arg_id) {
|
||||
&& g.gen_shared_array_push_arg(node.value, g.a.child(node, start), arg_id) {
|
||||
continue
|
||||
}
|
||||
if arg_idx == 0 && call_name_is_isnil(fn_name, callee_name)
|
||||
@@ -15365,17 +15425,15 @@ fn (mut g FlatGen) gen_call_args_for_callee(fn_name string, node flat.Node, star
|
||||
if arg_idx < typed_param_count && g.gen_interface_pointer_arg(arg_id, param_types[arg_idx]) {
|
||||
continue
|
||||
}
|
||||
is_rvalue := arg_node.kind in [.call, .struct_init, .cast_expr, .assoc]
|
||||
|| (arg_node.kind == .index && arg_node.value == 'range')
|
||||
|| (!arg_node.is_mut && g.map_index_value_is_rvalue(arg_id))
|
||||
|| g.arg_is_const_ident(arg_node)
|
||||
if needs_addr && g.arg_is_const_ident(arg_node) {
|
||||
value_type := g.addressed_const_arg_value_type(arg_id, param_types[arg_idx])
|
||||
ct := g.tc.c_type(value_type)
|
||||
g.write('(${ct}[]){')
|
||||
g.gen_expr_with_expected_type(arg_id, value_type)
|
||||
g.write('}')
|
||||
} else if needs_addr && is_rvalue {
|
||||
} else if needs_addr && (arg_node.kind in [.call, .struct_init, .cast_expr, .assoc]
|
||||
|| (arg_node.kind == .index && arg_node.value == 'range')
|
||||
|| (!arg_node.is_mut && g.map_index_value_is_rvalue(arg_id))) {
|
||||
pt := param_types[arg_idx]
|
||||
ct := g.tc.c_type(types.unwrap_pointer(pt))
|
||||
g.write('&((${ct}[]){')
|
||||
@@ -15562,11 +15620,11 @@ fn (mut g FlatGen) gen_variadic_array_args(node flat.Node, start int, elem_type
|
||||
g.write('})')
|
||||
}
|
||||
|
||||
fn (g &FlatGen) call_callee_is_module_selector(node flat.Node) bool {
|
||||
fn (g &FlatGen) call_callee_is_module_selector(node &flat.Node) bool {
|
||||
if node.children_count == 0 {
|
||||
return false
|
||||
}
|
||||
callee := g.a.child_node(&node, 0)
|
||||
callee := g.a.child_node(node, 0)
|
||||
if callee.kind != .selector || callee.children_count == 0 {
|
||||
return false
|
||||
}
|
||||
@@ -15847,19 +15905,20 @@ fn (mut g FlatGen) gen_transformed_method_ident_call(id flat.NodeId, node flat.N
|
||||
// A transformed module selector can retain its module ident as the first child.
|
||||
// Let module-call generation discard that pseudo-argument before considering a
|
||||
// same-named mutable receiver method.
|
||||
if _ := g.target_module_call_name(fn_node.value, node) {
|
||||
if g.target_module_call_arg_start(fn_node.value, node) == 2 {
|
||||
if g.target_module_call_arg_start(fn_node.value, node) == 2 {
|
||||
if _ := g.target_module_call_name(fn_node.value, node) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
mut receiver_id := g.a.child(&node, 1)
|
||||
emitted_name := g.direct_call_name_for_call_node(id, node, fn_node.value)
|
||||
mut emitted_name := ''
|
||||
method_short := c_short_name_view(fn_node.value)
|
||||
// Look up the resolved V method name first. Operator overloads use their
|
||||
// operator token in V, but C-name sanitization can collide with an ordinary
|
||||
// method (for example `%` and `mod`).
|
||||
mut params := g.param_types_for(fn_node.value, method_short)
|
||||
if params.len == 0 {
|
||||
emitted_name = g.direct_call_name_for_call_node(id, node, fn_node.value)
|
||||
params = g.param_types_for(emitted_name, emitted_name)
|
||||
}
|
||||
if params.len == 0 {
|
||||
@@ -15871,6 +15930,9 @@ fn (mut g FlatGen) gen_transformed_method_ident_call(id flat.NodeId, node flat.N
|
||||
if receiver_owner.len == 0 || expected_receiver_name != receiver_owner {
|
||||
return false
|
||||
}
|
||||
if emitted_name.len == 0 {
|
||||
emitted_name = g.direct_call_name_for_call_node(id, node, fn_node.value)
|
||||
}
|
||||
// Reflected method selection can retain an embedded-field selector from the
|
||||
// checker even after the concrete call target resolves to a method on the
|
||||
// complete program context. Recover that outer receiver from the selector;
|
||||
|
||||
@@ -96,6 +96,9 @@ const int128_shift_ops = [flat.Op.left_shift, .right_shift, .right_shift_unsigne
|
||||
// its existing path.
|
||||
fn (mut g FlatGen) gen_int128_infix(id flat.NodeId, node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, rhs_type types.Type) bool {
|
||||
op := node.op
|
||||
if op !in int128_arith_ops && op !in int128_comparison_ops && op !in int128_shift_ops {
|
||||
return false
|
||||
}
|
||||
lhs_signed := int128_signedness(lhs_type)
|
||||
rhs_signed := int128_signedness(rhs_type)
|
||||
if op in int128_shift_ops {
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.token
|
||||
import v.types
|
||||
|
||||
fn lazy_cgen_node(mut a flat.FlatAst, kind flat.NodeKind, op flat.Op, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
kind: kind
|
||||
op: op
|
||||
children_start: start
|
||||
children_count: children.len
|
||||
})
|
||||
}
|
||||
|
||||
fn test_checked_boolean_metadata_keeps_storage_and_unknown_types_on_fallback() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.bool_literal, 'true')
|
||||
right := a.add_val(.bool_literal, 'false')
|
||||
comparison := lazy_cgen_node(mut a, .infix, .eq, [left, right])
|
||||
negation := lazy_cgen_node(mut a, .prefix, .not, [left])
|
||||
ident := a.add_val(.ident, 'flag')
|
||||
selector := lazy_cgen_node(mut a, .selector, .none, [ident])
|
||||
call := lazy_cgen_node(mut a, .call, .none, [ident])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
for id in [left, comparison, negation, ident, selector, call] {
|
||||
a.nodes[int(id)].typ = flat.source_type_bool
|
||||
assert !g.checked_boolean_operand(id)
|
||||
tc.register_synth_type(id, types.Type(types.bool_))
|
||||
assert g.checked_boolean_operand(id) == (id == comparison)
|
||||
}
|
||||
for typ in [types.Type(types.int_), types.Type(types.f32_), types.Type(types.i128_),
|
||||
types.Type(types.Unknown{}), types.Type(types.Struct{ name_id: flat.source_type_id('Flag') })] {
|
||||
tc.register_synth_type(comparison, typ)
|
||||
assert !g.checked_boolean_operand(comparison)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_boolean_infix_keeps_parentheses_overrides_and_expected_context() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.bool_literal, 'true')
|
||||
right := a.add_val(.bool_literal, 'false')
|
||||
comparison := lazy_cgen_node(mut a, .infix, .ne, [left, right])
|
||||
choice := lazy_cgen_node(mut a, .infix, .logical_or, [left, right])
|
||||
conjunction := lazy_cgen_node(mut a, .infix, .logical_and, [comparison, choice])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
for id in [left, right, comparison, choice, conjunction] {
|
||||
tc.register_synth_type(id, types.Type(types.bool_))
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.expected_enum = flat.source_type_id('OuterEnum')
|
||||
g.expected_expr_type = types.Type(types.f32_)
|
||||
assert g.expr_to_string(conjunction) == '(true != false) && (true || false)'
|
||||
g.assert_expr_overrides[int(comparison)] = 'left_override'
|
||||
g.callback_target_overrides[int(choice)] = 'right_override'
|
||||
assert g.expr_to_string(conjunction) == '(left_override) && (right_override)'
|
||||
assert g.expected_enum == flat.source_type_id('OuterEnum')
|
||||
assert g.expected_expr_type == types.Type(types.f32_)
|
||||
// Checked boolean infixes need no usable-type memo probes.
|
||||
g.memo_usable_expr_types = true
|
||||
g.begin_usable_expr_type_memo()
|
||||
assert g.expr_to_string(conjunction) == '(left_override) && (right_override)'
|
||||
for id in [comparison, choice] {
|
||||
assert g.usable_expr_type_memo.gens[int(id) & usable_expr_type_memo_mask] != g.usable_expr_type_memo.generation
|
||||
}
|
||||
}
|
||||
|
||||
fn test_boolean_infix_resolves_unannotated_identifiers_and_stale_annotations() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.ident, 'left')
|
||||
right := a.add_val(.ident, 'right')
|
||||
conjunction := lazy_cgen_node(mut a, .infix, .logical_and, [left, right])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cur_param_types['left'] = types.Type(types.bool_)
|
||||
g.cur_param_types['right'] = types.Type(types.bool_)
|
||||
assert g.expr_to_string(conjunction) == 'left && right'
|
||||
// A flat bool annotation must not override the authoritative checked type.
|
||||
first := lazy_cgen_node(mut a, .infix, .eq, [left, right])
|
||||
second := lazy_cgen_node(mut a, .infix, .eq, [left, right])
|
||||
outer := lazy_cgen_node(mut a, .infix, .logical_and, [first, second])
|
||||
a.nodes[int(first)].typ = flat.source_type_bool
|
||||
tc.register_synth_type(first, types.Type(types.Struct{ name_id: flat.source_type_id('CustomFlag') }))
|
||||
tc.register_synth_type(second, types.Type(types.bool_))
|
||||
g.assert_expr_overrides[int(first)] = 'custom_flag'
|
||||
g.assert_expr_overrides[int(second)] = 'boolean_flag'
|
||||
assert g.expr_to_string(outer) == 'custom_flag && boolean_flag'
|
||||
}
|
||||
|
||||
fn test_boolean_infix_translated_and_unknown_metadata_still_use_normal_resolution() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.bool_literal, 'true')
|
||||
right := a.add_val(.bool_literal, 'false')
|
||||
conjunction := lazy_cgen_node(mut a, .infix, .logical_and, [left, right])
|
||||
mut files := token.FileSet.new()
|
||||
a.source_files[1] = files.add_file('translated_boolean.v', 1)
|
||||
a.nodes[int(conjunction)].pos = token.new_pos(1, 0)
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.translated_files['translated_boolean.v'] = true
|
||||
for id in [left, right, conjunction] {
|
||||
tc.register_synth_type(id, types.Type(types.bool_))
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.memo_usable_expr_types = true
|
||||
g.begin_usable_expr_type_memo()
|
||||
assert g.expr_to_string(conjunction) == 'true && false'
|
||||
for id in [left, right] {
|
||||
assert g.usable_expr_type_memo.gens[int(id) & usable_expr_type_memo_mask] == g.usable_expr_type_memo.generation
|
||||
}
|
||||
g.end_usable_expr_type_memo()
|
||||
tc.translated_files.clear()
|
||||
tc.register_synth_type(left, types.Type(types.Unknown{}))
|
||||
tc.register_synth_type(right, types.Type(types.Unknown{}))
|
||||
g.begin_usable_expr_type_memo()
|
||||
assert g.expr_to_string(conjunction) == 'true && false'
|
||||
for id in [left, right] {
|
||||
assert g.usable_expr_type_memo.gens[int(id) & usable_expr_type_memo_mask] == g.usable_expr_type_memo.generation
|
||||
}
|
||||
}
|
||||
|
||||
fn test_discarded_infix_handlers_do_not_resolve_the_result_type() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.int_literal, '7')
|
||||
right := a.add_val(.int_literal, '2')
|
||||
result := lazy_cgen_node(mut a, .infix, .plus, [left, right])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.register_synth_type(result, types.Type(types.int_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.memo_usable_expr_types = true
|
||||
g.begin_usable_expr_type_memo()
|
||||
for builtins in [false, true] {
|
||||
g.has_builtins = builtins
|
||||
assert !g.gen_safe_integer_division(result, a.nodes[int(result)], left, right)
|
||||
}
|
||||
a.nodes[int(result)].op = .div
|
||||
g.static_c_initializer = true
|
||||
assert !g.gen_safe_integer_division(result, a.nodes[int(result)], left, right)
|
||||
for op in [flat.Op.logical_and, .logical_or, .power, .arrow] {
|
||||
a.nodes[int(result)].op = op
|
||||
assert !g.gen_int128_infix(result, a.nodes[int(result)], left, right, types.Type(types.int_), types.Type(types.int_))
|
||||
}
|
||||
assert g.usable_expr_type_memo.gens[int(result) & usable_expr_type_memo_mask] != g.usable_expr_type_memo.generation
|
||||
}
|
||||
|
||||
fn test_lazy_division_and_int128_handlers_keep_supported_operations() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.int_literal, '7')
|
||||
right := a.add_val(.int_literal, '2')
|
||||
result := lazy_cgen_node(mut a, .infix, .div, [left, right])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
for id in [left, right, result] {
|
||||
tc.register_synth_type(id, types.Type(types.int_))
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.has_builtins = true
|
||||
division := g.expr_to_string(result)
|
||||
assert division.contains('division by zero')
|
||||
assert division.contains('v_panic(')
|
||||
a.nodes[int(result)].op = .mod
|
||||
assert g.expr_to_string(result).contains('modulo by zero')
|
||||
for portable in [false, true] {
|
||||
g.compile_defines = if portable { ['v3_no_native_int128'] } else { []string{} }
|
||||
g.has_builtins = false
|
||||
tc.register_synth_type(result, types.Type(types.i128_))
|
||||
a.nodes[int(result)].op = .plus
|
||||
assert g.expr_to_string(result) == '__v_i128_add(__v_i128_from_i64((i64)(7)), __v_i128_from_i64((i64)(2)))'
|
||||
a.nodes[int(result)].op = .div
|
||||
assert g.expr_to_string(result) == '__v_i128_div(__v_i128_from_i64((i64)(7)), __v_i128_from_i64((i64)(2)))'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_prefix_type_query_keeps_pointer_annotations_and_avoids_unused_child_queries() {
|
||||
mut a := flat.FlatAst.new()
|
||||
child := a.add_val(.ident, 'value')
|
||||
negation := lazy_cgen_node(mut a, .prefix, .not, [child])
|
||||
address := lazy_cgen_node(mut a, .prefix, .amp, [child])
|
||||
deref := lazy_cgen_node(mut a, .prefix, .mul, [address])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.register_synth_type(negation, types.Type(types.bool_))
|
||||
tc.register_synth_type(child, types.Type(types.int_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.memo_usable_expr_types = true
|
||||
g.begin_usable_expr_type_memo()
|
||||
assert *g.usable_expr_type(negation) == types.Type(types.bool_)
|
||||
assert g.usable_expr_type_memo.gens[int(child) & usable_expr_type_memo_mask] != g.usable_expr_type_memo.generation
|
||||
assert g.usable_expr_type(address).name() == '&int'
|
||||
assert *g.usable_expr_type(deref) == types.Type(types.int_)
|
||||
g.end_usable_expr_type_memo()
|
||||
a.nodes[int(deref)].typ = flat.source_type_id('&bool')
|
||||
g.begin_usable_expr_type_memo()
|
||||
assert g.usable_expr_type(deref).name() == '&bool'
|
||||
assert g.usable_expr_type_memo.gens[int(address) & usable_expr_type_memo_mask] != g.usable_expr_type_memo.generation
|
||||
}
|
||||
|
||||
fn test_ierror_alias_tracking_keeps_array_get_alias_propagation() {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'borrowed')
|
||||
callee := a.add_val(.ident, 'array__get')
|
||||
call := lazy_cgen_node(mut a, .call, .none, [callee, base])
|
||||
paren := lazy_cgen_node(mut a, .paren, .none, [call])
|
||||
deref := lazy_cgen_node(mut a, .prefix, .mul, [paren])
|
||||
lhs := a.add_val(.ident, 'alias')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_scope = types.new_scope(tc.cur_scope)
|
||||
tc.cur_scope.insert('borrowed', types.Type(types.int_))
|
||||
tc.cur_scope.insert('alias', types.Type(types.int_))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.ierror_stack_pointer_aliases << map[string]bool{}
|
||||
g.declare_ierror_pointer_alias('borrowed', true)
|
||||
for id in [call, paren, deref] {
|
||||
assert g.ierror_array_get_pointer_alias_needs_copy(a.nodes[int(id)])
|
||||
assert g.ierror_pointer_payload_expr_needs_heap_copy(a.nodes[int(id)])
|
||||
g.track_ierror_stack_pointer_alias(a.nodes[int(lhs)], a.nodes[int(id)])
|
||||
assert g.ierror_pointer_alias_needs_copy('alias')
|
||||
g.assign_ierror_pointer_alias('alias', false)
|
||||
g.track_ierror_stack_pointer_alias_assign(a.nodes[int(lhs)], a.nodes[int(id)])
|
||||
assert g.ierror_pointer_alias_needs_copy('alias')
|
||||
}
|
||||
}
|
||||
+27
-3
@@ -113,8 +113,14 @@ fn (mut c CNameCache) remember(name string, value string) {
|
||||
@[heap]
|
||||
struct ConstShortIndex {
|
||||
mut:
|
||||
built bool
|
||||
entries map[string]string
|
||||
built bool
|
||||
entries map[string]string
|
||||
call_names_ready bool
|
||||
call_names [64]u64
|
||||
call_const_vals_count int
|
||||
call_const_exprs_count int
|
||||
call_const_types_count int
|
||||
call_const_suffixes_count int
|
||||
}
|
||||
|
||||
// FnNameFactCache memoizes per-fn-name conclusions (1 = yes, -1 = no) that
|
||||
@@ -285,14 +291,21 @@ fn (mut c ContextStringLookupCache) select_context(file string, module_name stri
|
||||
c.last_valid = false
|
||||
}
|
||||
|
||||
struct SourceTypeLookupRecentEntry {
|
||||
name u32
|
||||
value u32
|
||||
valid bool
|
||||
}
|
||||
|
||||
// ContextSourceTypeLookupCache memoizes source-spelling lookups within one file and module.
|
||||
// Zero values record misses; last_valid distinguishes an empty cache from a cached miss.
|
||||
// Zero values record misses; validity distinguishes an empty cache from a cached miss.
|
||||
@[heap]
|
||||
struct ContextSourceTypeLookupCache {
|
||||
mut:
|
||||
file string = '\x00'
|
||||
module string
|
||||
entries map[u32]u32
|
||||
recent [64]SourceTypeLookupRecentEntry
|
||||
last_name u32
|
||||
last_value u32
|
||||
last_valid bool
|
||||
@@ -301,7 +314,16 @@ mut:
|
||||
@[inline]
|
||||
fn (mut c ContextSourceTypeLookupCache) get(name u32) ?u32 {
|
||||
if c.last_valid && c.last_name == name { return c.last_value }
|
||||
slot := int(name & 63)
|
||||
entry := c.recent[slot]
|
||||
if entry.valid && entry.name == name {
|
||||
c.last_name = name
|
||||
c.last_value = entry.value
|
||||
c.last_valid = true
|
||||
return entry.value
|
||||
}
|
||||
if cached := c.entries[name] {
|
||||
c.recent[slot] = SourceTypeLookupRecentEntry{ name: name, value: cached, valid: true }
|
||||
c.last_name = name
|
||||
c.last_value = cached
|
||||
c.last_valid = true
|
||||
@@ -313,6 +335,7 @@ fn (mut c ContextSourceTypeLookupCache) get(name u32) ?u32 {
|
||||
@[inline]
|
||||
fn (mut c ContextSourceTypeLookupCache) put(name u32, value u32) {
|
||||
c.entries[name] = value
|
||||
c.recent[int(name & 63)] = SourceTypeLookupRecentEntry{ name: name, value: value, valid: true }
|
||||
c.last_name = name
|
||||
c.last_value = value
|
||||
c.last_valid = true
|
||||
@@ -328,6 +351,7 @@ fn (mut c ContextSourceTypeLookupCache) select_context(file string, module_name
|
||||
c.file = file
|
||||
c.module = module_name
|
||||
c.entries = map[u32]u32{}
|
||||
c.recent = [64]SourceTypeLookupRecentEntry{}
|
||||
c.last_name = 0
|
||||
c.last_value = 0
|
||||
c.last_valid = false
|
||||
|
||||
@@ -1553,6 +1553,7 @@ fn ordinary_local_ident_output(name string, mode string, fast bool) string {
|
||||
tc.cur_scope = scope
|
||||
typ := if mode == 'void' { types.Type(types.Void{}) } else { types.Type(types.int_) }
|
||||
owner := scope.insert_with_owner(name, typ)
|
||||
other_owner := scope.insert_with_owner('other_local', types.Type(types.int_))
|
||||
if mode == 'void' {
|
||||
tc.fn_param_types[name] = []types.Type{}
|
||||
tc.fn_ret_types[name] = types.Type(types.int_)
|
||||
@@ -1565,6 +1566,10 @@ fn ordinary_local_ident_output(name string, mode string, fast bool) string {
|
||||
'deref' { g.local_implicit_deref_by_owner[owner.storage_key()] = true }
|
||||
'shadow' { g.shadowed_global_locals[owner.storage_key()] = true }
|
||||
'cfn' { g.cur_c_fn_calls[g.cname(name)] = true }
|
||||
'other_shared' { g.local_shared_storage_by_owner[other_owner.storage_key()] = true }
|
||||
'other_deref' { g.local_implicit_deref_by_owner[other_owner.storage_key()] = true }
|
||||
'other_shadow' { g.shadowed_global_locals[other_owner.storage_key()] = true }
|
||||
'other_cfn' { g.cur_c_fn_calls['other_c_call'] = true }
|
||||
'suffix' { tc.c_globals['C.${name}'] = types.Type(types.int_) }
|
||||
'typedef' { g.inlined_c_typedef_names[g.cname(name)] = true }
|
||||
'global' { g.global_modules[name] = 'other' }
|
||||
@@ -1579,13 +1584,49 @@ fn ordinary_local_ident_output(name string, mode string, fast bool) string {
|
||||
|
||||
fn test_ordinary_local_ident_keeps_storage_rename_and_placeholder_output() {
|
||||
for name in ['value', 'abs', 'array', 'argv', 'int_str', 'pkg.value', '__v3_internal_symbol_local'] {
|
||||
for mode in ['', 'shared', 'deref', 'shadow', 'cfn', 'suffix', 'typedef', 'global', 'void'] {
|
||||
for mode in ['', 'shared', 'deref', 'shadow', 'cfn', 'other_shared', 'other_deref',
|
||||
'other_shadow', 'other_cfn', 'suffix', 'typedef', 'global', 'void'] {
|
||||
legacy := ordinary_local_ident_output(name, mode, false)
|
||||
assert ordinary_local_ident_output(name, mode, true) == legacy, '${name} / ${mode}: ${legacy}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_ordinary_local_ident_ignores_outer_binding_storage_and_unrelated_c_calls() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.ident, 'value')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut outer_scope := types.new_scope(unsafe { nil })
|
||||
outer := outer_scope.insert_with_owner('value', types.Type(types.int_))
|
||||
mut inner_scope := types.new_scope(outer_scope)
|
||||
inner := inner_scope.insert_with_owner('value', types.Type(types.int_))
|
||||
tc.cur_scope = inner_scope
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.local_shared_storage_by_owner[outer.storage_key()] = true
|
||||
g.local_implicit_deref_by_owner[outer.storage_key()] = true
|
||||
g.shadowed_global_locals[outer.storage_key()] = true
|
||||
g.cur_c_fn_calls['other_c_call'] = true
|
||||
g.precompute_local_global_suffix_names()
|
||||
g.precompute_c_typedef_shadow_names()
|
||||
g.precompute_local_name_exceptions()
|
||||
assert g.ordinary_local_cname('value', inner)? == 'value'
|
||||
assert g.ordinary_local_cname('value', outer) == none
|
||||
g.gen_nonliteral_expr(id, a.node(id))
|
||||
assert g.sb.str() == 'value'
|
||||
// A recorded C call reserves its emitted name even when the map value is false.
|
||||
g.cur_c_fn_calls['value'] = false
|
||||
assert g.ordinary_local_cname('value', inner) == none
|
||||
g.cur_c_fn_calls.delete('value')
|
||||
g.shadowed_global_locals[inner.storage_key()] = false
|
||||
assert g.ordinary_local_cname('value', inner) == none
|
||||
g.shadowed_global_locals.delete(inner.storage_key())
|
||||
g.local_shared_storage_by_owner[inner.storage_key()] = false
|
||||
g.local_implicit_deref_by_owner[inner.storage_key()] = false
|
||||
assert g.ordinary_local_cname('value', inner)? == 'value'
|
||||
}
|
||||
|
||||
fn test_local_name_exceptions_preserve_raw_and_emitted_names_and_rebuilds() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.token
|
||||
import v.types
|
||||
|
||||
fn add_snapshot_query_node(mut a flat.FlatAst, kind flat.NodeKind, value string, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
kind: kind
|
||||
value: value
|
||||
children_start: i32(start)
|
||||
children_count: flat.child_count(children.len)
|
||||
})
|
||||
}
|
||||
|
||||
fn grow_snapshot_query_arena(mut a flat.FlatAst) {
|
||||
for _ in 0 .. 2048 {
|
||||
a.add_val(.int_literal, '0')
|
||||
}
|
||||
}
|
||||
|
||||
fn test_snapshot_closure_search_keeps_first_nested_match_after_arena_growth() {
|
||||
mut a := flat.FlatAst.new()
|
||||
first := a.add_val(.ident, 'main.__anon_fn_first')
|
||||
second := a.add_val(.ident, 'main.__anon_fn_second')
|
||||
nested := add_snapshot_query_node(mut a, .paren, '', [first])
|
||||
root := add_snapshot_query_node(mut a, .call, '', [nested, second])
|
||||
snapshot := a.nodes[int(root)]
|
||||
grow_snapshot_query_arena(mut a)
|
||||
a.nodes[int(root)].children_count = 0
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
assert g.runtime_closure_lifted_c_name(snapshot) or { '' } == g.cname('main.__anon_fn_first')
|
||||
assert g.runtime_closure_lifted_c_name(a.node(root)) == none
|
||||
}
|
||||
|
||||
fn test_snapshot_const_selector_keeps_owning_file_import_and_local_shadow() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut files := token.FileSet.new()
|
||||
a.source_files[1] = files.add_file('owner.v', 1)
|
||||
base := a.add_val(.ident, 'dep')
|
||||
selector := add_snapshot_query_node(mut a, .selector, 'limit', [base])
|
||||
a.nodes[int(selector)].pos = token.new_pos(1, 0)
|
||||
snapshot := a.nodes[int(selector)]
|
||||
grow_snapshot_query_arena(mut a)
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.cur_file = 'other.v'
|
||||
tc.file_imports['owner.v\ndep'] = 'library'
|
||||
tc.file_imports['other.v\ndep'] = 'other'
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.const_vals['library.limit'] = flat.NodeId(0)
|
||||
g.const_vals['other.limit'] = flat.NodeId(0)
|
||||
assert g.const_ref_name_from_node(snapshot) == 'library.limit'
|
||||
g.cur_param_types['dep'] = &types.Type(types.int_)
|
||||
assert g.const_ref_name_from_node(snapshot) == ''
|
||||
mut cache := map[string]string{}
|
||||
assert g.const_ref_name_from_node_cached_for_collect(snapshot, map[string]string{}, mut cache) == ''
|
||||
}
|
||||
|
||||
fn test_snapshot_source_file_walk_keeps_descendant_order_and_fallback() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut files := token.FileSet.new()
|
||||
a.source_files[1] = files.add_file('first.v', 1)
|
||||
a.source_files[2] = files.add_file('second.v', 1)
|
||||
first := a.add_node(flat.Node{ kind: .ident, pos: token.new_pos(1, 0) })
|
||||
second := a.add_node(flat.Node{ kind: .ident, pos: token.new_pos(2, 0) })
|
||||
nested := add_snapshot_query_node(mut a, .paren, '', [second])
|
||||
root := add_snapshot_query_node(mut a, .call, '', [first, nested])
|
||||
snapshot := a.nodes[int(root)]
|
||||
grow_snapshot_query_arena(mut a)
|
||||
a.nodes[int(root)].children_count = 0
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_file = 'checker.v'
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cur_fn_source_file = 'function.v'
|
||||
assert g.node_source_file(&snapshot) == 'second.v'
|
||||
assert g.node_source_file(a.node(root)) == 'function.v'
|
||||
}
|
||||
|
||||
fn test_fixed_size_query_keeps_annotation_and_rejects_ordinary_calls() {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'values')
|
||||
selector := add_snapshot_query_node(mut a, .selector, 'to_fixed_size', [base])
|
||||
call := add_snapshot_query_node(mut a, .call, '', [selector])
|
||||
a.nodes[int(call)].typ = flat.source_type_id('[2]int')
|
||||
ordinary_callee := a.add_val(.ident, 'ordinary')
|
||||
ordinary := add_snapshot_query_node(mut a, .call, '', [ordinary_callee])
|
||||
grow_snapshot_query_arena(mut a)
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
fixed := g.to_fixed_size_call_fixed_type(call) or { panic('missing fixed array') }
|
||||
assert fixed.len == 2
|
||||
assert fixed.elem_type.name() == 'int'
|
||||
assert g.to_fixed_size_call_fixed_type(ordinary) == none
|
||||
}
|
||||
@@ -18,14 +18,16 @@ enum CgenModePool {
|
||||
}
|
||||
|
||||
struct CgenModeOutput {
|
||||
code string
|
||||
literals []string
|
||||
fn_names []string
|
||||
scoped bool
|
||||
parallel bool
|
||||
pool_present bool
|
||||
pool_size int
|
||||
pool_tasks u64
|
||||
code string
|
||||
literals []string
|
||||
fn_names []string
|
||||
scoped bool
|
||||
parallel bool
|
||||
pool_present bool
|
||||
pool_size int
|
||||
pool_tasks u64
|
||||
type_names_present bool
|
||||
field_index_present bool
|
||||
mut:
|
||||
launches u64
|
||||
launch_errors u64
|
||||
@@ -114,14 +116,16 @@ fn cgen_mode_generate(path string, pool_mode CgenModePool, no_parallel bool) Cge
|
||||
g.set_scope_parallel_workers(true)
|
||||
code := g.gen_with_used_options(a, used, &tc, no_parallel)
|
||||
mut result := CgenModeOutput{
|
||||
code: code
|
||||
literals: g.str_lits.clone()
|
||||
fn_names: g.used_fn_names.clone()
|
||||
scoped: g.scope_parallel_workers
|
||||
parallel: g.parallel_used
|
||||
pool_present: !isnil(a.worker_pool)
|
||||
pool_size: a.worker_count()
|
||||
pool_tasks: a.worker_tasks_run()
|
||||
code: code
|
||||
literals: g.str_lits.clone()
|
||||
fn_names: g.used_fn_names.clone()
|
||||
scoped: g.scope_parallel_workers
|
||||
parallel: g.parallel_used
|
||||
pool_present: !isnil(a.worker_pool)
|
||||
pool_size: a.worker_count()
|
||||
pool_tasks: a.worker_tasks_run()
|
||||
type_names_present: !isnil(g.type_name_short_index)
|
||||
field_index_present: !isnil(g.struct_field_index_base)
|
||||
}
|
||||
if !isnil(a.worker_pool) {
|
||||
stats := a.worker_pool.stats()
|
||||
@@ -202,6 +206,10 @@ fn test_one_job_codegen_keeps_a_lazy_pool_and_matches_explicit_serial() {
|
||||
defer { os.rm(path) or {} }
|
||||
serial := cgen_mode_generate(path, .lazy, true)
|
||||
default_mode := cgen_mode_generate(path, .lazy, false)
|
||||
assert serial.type_names_present
|
||||
assert default_mode.type_names_present
|
||||
assert !serial.field_index_present
|
||||
assert !default_mode.field_index_present
|
||||
assert default_mode.code == serial.code
|
||||
assert default_mode.literals == serial.literals
|
||||
assert default_mode.fn_names == serial.fn_names
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_serial_collection_builds_complete_name_index_without_field_index() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['models.Record'] = []types.StructField{}
|
||||
tc.type_aliases.set_id('models.Alias', flat.source_type_int)
|
||||
tc.enum_names['models.State'] = true
|
||||
tc.flag_enums['models.Flags'] = true
|
||||
tc.sum_types.set_ids('models.Choice', [flat.source_type_int, flat.source_type_string])
|
||||
tc.interface_names['models.Reader'] = true
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
assert isnil(g.type_name_short_index)
|
||||
assert g.ident_may_name_type('missing')
|
||||
g.collect_gen_info(true)
|
||||
assert !isnil(g.type_name_short_index)
|
||||
assert isnil(g.struct_field_index_base)
|
||||
for name in ['Record', 'Alias', 'State', 'Choice', 'Reader'] {
|
||||
assert g.ident_may_name_type(name)
|
||||
assert g.ident_may_name_type('other.${name}')
|
||||
}
|
||||
assert g.name_may_be_type_alias('Alias')
|
||||
assert !g.name_may_be_type_alias('Record')
|
||||
for name in ['State', 'Flags', 'Alias'] {
|
||||
assert g.name_may_reach_enum(name)
|
||||
}
|
||||
assert !g.ident_may_name_type('missing')
|
||||
assert !g.name_may_reach_enum('Record')
|
||||
assert g.ident_may_name_type('Unknown[int]')
|
||||
assert g.name_may_be_type_alias('Unknown[int]')
|
||||
assert g.name_may_reach_enum('Unknown[int]')
|
||||
// A later full collection rebuilds membership.
|
||||
tc.structs['later.Added'] = []types.StructField{}
|
||||
g.collect_gen_info(true)
|
||||
assert g.ident_may_name_type('Added')
|
||||
// Incremental collection keeps the existing conservative fallback.
|
||||
mut incremental := FlatGen.new()
|
||||
incremental.a = &a
|
||||
incremental.tc = &tc
|
||||
incremental.incremental_fn_names['only_new_body'] = true
|
||||
incremental.collect_gen_info(true)
|
||||
assert isnil(incremental.type_name_short_index)
|
||||
assert incremental.ident_may_name_type('missing')
|
||||
}
|
||||
|
||||
fn test_value_c_type_alias_filter_preserves_c_spelling_main_prefix_and_long_keys() {
|
||||
for prefix in ['', 'main.'] {
|
||||
for suffix in ['Value', 'Value' + 'x'.repeat(600)] {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.type_aliases.set_id('${prefix}model__${suffix}', flat.source_type_f64)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
value := types.Type(types.Struct{ name_id: flat.source_type_id('model.${suffix}') })
|
||||
before := g.value_c_type(&value)
|
||||
expected := types.Type(types.f64_)
|
||||
assert before == g.tc.c_type(&expected)
|
||||
g.type_name_short_index = new_type_name_short_index(&tc)
|
||||
assert g.name_may_be_type_alias('model__${suffix}')
|
||||
assert g.value_c_type(&value) == before
|
||||
plain := types.Type(types.Struct{ name_id: flat.source_type_id('model.Other') })
|
||||
assert !g.name_may_be_type_alias('model__Other')
|
||||
filtered := g.value_c_type(&plain)
|
||||
g.type_name_short_index = unsafe { nil }
|
||||
assert g.value_c_type(&plain) == filtered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cast_name_resolution_keeps_bare_priority_qualification_and_local_shadowing() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.cur_module = 'sample'
|
||||
tc.structs['Record'] = []types.StructField{}
|
||||
tc.type_aliases.set_id('sample.Record', flat.source_type_int)
|
||||
tc.type_aliases.set_id('sample.Alias', flat.source_type_int)
|
||||
tc.enum_names['sample.State'] = true
|
||||
tc.sum_types.set_ids('sample.Choice', [flat.source_type_int, flat.source_type_string])
|
||||
tc.interface_names['sample.Reader'] = true
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
for with_index in [false, true] {
|
||||
if with_index {
|
||||
g.type_name_short_index = new_type_name_short_index(&tc)
|
||||
}
|
||||
assert g.call_ident_cast_type_name('Record', false) or { '' } == 'Record'
|
||||
for name in ['Alias', 'State', 'Choice', 'Reader'] {
|
||||
assert g.call_ident_cast_type_name(name, false) or { '' } == 'sample.${name}'
|
||||
assert g.call_ident_cast_type_name('sample.${name}', false) or { '' } == 'sample.${name}'
|
||||
}
|
||||
assert g.call_ident_cast_type_name('absent', false) == none
|
||||
for name in ['Record', 'Alias', 'State', 'Choice', 'Reader'] {
|
||||
assert g.call_ident_cast_type_name(name, true) == none
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
module c
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_source_type_recent_cache_preserves_zero_keys_and_cached_misses() {
|
||||
mut cache := ContextSourceTypeLookupCache{}
|
||||
assert cache.get(0) == none
|
||||
assert cache.get(1) == none
|
||||
cache.put(0, 0)
|
||||
cache.put(1, 42)
|
||||
assert cache.get(0)? == 0
|
||||
assert cache.get(1)? == 42
|
||||
cache.put(1, 0)
|
||||
cache.put(2, 3)
|
||||
assert cache.get(1)? == 0
|
||||
assert cache.get(3) == none
|
||||
cache.put(0, 19)
|
||||
cache.put(2, 3)
|
||||
assert cache.get(0)? == 19
|
||||
}
|
||||
|
||||
fn test_source_type_recent_cache_collisions_repopulate_from_the_full_map() {
|
||||
mut cache := ContextSourceTypeLookupCache{}
|
||||
for i in 0 .. 512 {
|
||||
cache.put(u32(i), u32(i * 3))
|
||||
}
|
||||
for i in 0 .. 512 {
|
||||
assert cache.get(u32(i))? == u32(i * 3)
|
||||
slot := int(u32(i) & 63)
|
||||
assert cache.recent[slot].valid
|
||||
assert cache.recent[slot].name == u32(i)
|
||||
}
|
||||
for _ in 0 .. 4 {
|
||||
for name in [u32(0), 64, 128, 192, 256, 320, 384, 448] {
|
||||
assert cache.get(name)? == name * 3
|
||||
}
|
||||
}
|
||||
cache.put(64, 0)
|
||||
assert cache.get(0)? == 0
|
||||
assert cache.get(64)? == 0
|
||||
assert cache.get(512) == none
|
||||
}
|
||||
|
||||
fn test_source_type_recent_cache_context_changes_clear_all_cache_layers() {
|
||||
mut cache := ContextSourceTypeLookupCache{}
|
||||
cache.select_context('first.v', 'first')
|
||||
cache.put(0, 0)
|
||||
cache.put(1, 42)
|
||||
cache.select_context('first.v'.clone(), 'first'.clone())
|
||||
assert cache.get(0)? == 0
|
||||
assert cache.get(1)? == 42
|
||||
for context in [['second.v', 'first'], ['second.v', 'second'], ['first.v', 'first']] {
|
||||
cache.select_context(context[0], context[1])
|
||||
assert !cache.last_valid
|
||||
assert cache.entries.len == 0
|
||||
for entry in cache.recent {
|
||||
assert !entry.valid
|
||||
}
|
||||
assert cache.get(0) == none
|
||||
assert cache.get(1) == none
|
||||
cache.put(0, 0)
|
||||
cache.put(1, 43)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_source_type_recent_cache_workers_and_scratch_batches_are_private() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.enum_selector_cache.select_context('same.v', 'main')
|
||||
g.enum_selector_cache.put(1, 2)
|
||||
mut worker := g.new_parallel_worker(1)
|
||||
assert worker.enum_selector_cache != g.enum_selector_cache
|
||||
assert worker.enum_selector_cache.get(1) == none
|
||||
worker.enum_selector_cache.select_context('same.v', 'main')
|
||||
worker.enum_selector_cache.put(1, 3)
|
||||
assert g.enum_selector_cache.get(1)? == 2
|
||||
assert worker.enum_selector_cache.get(1)? == 3
|
||||
own := g.enum_selector_cache
|
||||
saved := g.begin_scratch_lookup_caches()
|
||||
assert g.enum_selector_cache != own
|
||||
assert g.enum_selector_cache.get(1) == none
|
||||
g.enum_selector_cache.select_context('same.v', 'main')
|
||||
g.enum_selector_cache.put(1, 4)
|
||||
g.restore_scratch_lookup_caches(saved)
|
||||
assert g.enum_selector_cache == own
|
||||
assert g.enum_selector_cache.get(1)? == 2
|
||||
g.reset_context_lookup_caches()
|
||||
assert g.enum_selector_cache != own
|
||||
assert g.enum_selector_cache.get(1) == none
|
||||
}
|
||||
+37
-27
@@ -6471,7 +6471,7 @@ fn (g &FlatGen) body_resolve_type_reference(id flat.NodeId) &types.Type {
|
||||
if memo.resolve_gens[slot] == memo.generation && memo.resolve_ids[slot] == idx {
|
||||
return memo.resolve_values[slot]
|
||||
}
|
||||
result := g.tc.intern_type_reference(g.tc.resolve_type(id))
|
||||
result := g.tc.resolve_type_reference(id)
|
||||
if !cgen_type_contains_unknown(result) {
|
||||
memo.resolve_ids[slot] = idx
|
||||
memo.resolve_gens[slot] = memo.generation
|
||||
@@ -6480,7 +6480,7 @@ fn (g &FlatGen) body_resolve_type_reference(id flat.NodeId) &types.Type {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return g.tc.intern_type_reference(g.tc.resolve_type(id))
|
||||
return g.tc.resolve_type_reference(id)
|
||||
}
|
||||
|
||||
// cgen_type_contains_unknown reports an unknown type anywhere in `typ`.
|
||||
@@ -7158,16 +7158,16 @@ fn (g &FlatGen) usable_expr_type_reference_uncached(id flat.NodeId) &types.Type
|
||||
return annotated
|
||||
}
|
||||
}
|
||||
child_type := g.usable_expr_type(g.a.child(node, 0))
|
||||
if node.op == .amp {
|
||||
return g.tc.intern_type_reference(types.Type(types.Pointer{
|
||||
base_type: child_type
|
||||
}))
|
||||
}
|
||||
if node.op == .mul {
|
||||
if node.op in [.amp, .mul] {
|
||||
child_type := g.usable_expr_type(g.a.child(node, 0))
|
||||
if node.op == .amp {
|
||||
return g.tc.intern_type_reference(types.Type(types.Pointer{
|
||||
base_type: child_type
|
||||
}))
|
||||
}
|
||||
pointer := unalias_type(child_type)
|
||||
if pointer is types.Pointer {
|
||||
return g.tc.intern_type_reference(pointer.base_type)
|
||||
return pointer.base_type
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7208,7 +7208,7 @@ fn (g &FlatGen) usable_expr_type_reference_uncached(id flat.NodeId) &types.Type
|
||||
// `shared` local.
|
||||
if typ is types.Pointer && g.local_shared_storage_by_owner.len > 0
|
||||
&& g.local_storage_is_shared(node.value) {
|
||||
return g.tc.intern_type_reference(typ.base_type)
|
||||
return typ.base_type
|
||||
}
|
||||
return typ
|
||||
}
|
||||
@@ -7326,13 +7326,13 @@ fn (g &FlatGen) usable_expr_type_reference_uncached(id flat.NodeId) &types.Type
|
||||
}
|
||||
}
|
||||
if base_type is types.Array {
|
||||
return g.tc.intern_type_reference(base_type.elem_type)
|
||||
return base_type.elem_type
|
||||
}
|
||||
if base_type is types.ArrayFixed {
|
||||
return g.tc.intern_type_reference(base_type.elem_type)
|
||||
return base_type.elem_type
|
||||
}
|
||||
if base_type is types.Map {
|
||||
return g.tc.intern_type_reference(base_type.value_type)
|
||||
return base_type.value_type
|
||||
}
|
||||
if base_type is types.String {
|
||||
return g.tc.intern_type_reference(types.builtin_type_value('u8'))
|
||||
@@ -7982,7 +7982,7 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
|
||||
g.current_decl_is_mut = old_decl_is_mut
|
||||
}
|
||||
if node.value == '__v3_zeroed_stack_value_decl'
|
||||
&& g.a.child_node(&node, 0).value !in g.defer_capture_types {
|
||||
&& g.a.node(g.a.child(&node, 0)).value !in g.defer_capture_types {
|
||||
g.gen_staging_value_decl(node)
|
||||
return
|
||||
}
|
||||
@@ -8231,7 +8231,7 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
|
||||
mut init_cap := '0'
|
||||
mut init_val := ''
|
||||
for j in 0 .. rhs.children_count {
|
||||
child := g.a.child_node(&rhs, j)
|
||||
child := g.a.node(g.a.child(&rhs, j))
|
||||
if child.kind == .field_init {
|
||||
if child.value == 'len' {
|
||||
init_len = g.expr_to_string(g.a.child(child, 0))
|
||||
@@ -8362,7 +8362,7 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
|
||||
if rhs.kind == .call && lhs.typ_text().starts_with('(') && lhs.typ_text().contains(',') {
|
||||
mut declared_ret := g.declared_call_return_type(rhs_id)
|
||||
if rhs.children_count > 0 {
|
||||
callee := g.a.child_node(&rhs, 0)
|
||||
callee := g.a.node(g.a.child(&rhs, 0))
|
||||
if specialized_ret := g.specialized_selector_method_call_return_type(rhs, callee) {
|
||||
declared_ret = specialized_ret
|
||||
}
|
||||
@@ -8372,7 +8372,7 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
|
||||
}
|
||||
}
|
||||
if rhs.kind == .call && rhs.children_count > 0 {
|
||||
callee := g.a.child_node(&rhs, 0)
|
||||
callee := g.a.node(g.a.child(&rhs, 0))
|
||||
if callee.kind == .selector {
|
||||
if ret := g.selector_fn_field_return_type(callee) {
|
||||
v_type = ret
|
||||
@@ -8570,8 +8570,13 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
|
||||
semantic_v_type := unalias_type(v_type)
|
||||
// Ownership replacement temporaries carry the destination type. Box before
|
||||
// the concrete payload type is lost, preserving the later drop and store.
|
||||
boxed_optional := g.optional_interface_assignment_boxes_payload(semantic_v_type,
|
||||
optional_result_unalias_type(g.usable_expr_type(rhs_id)), rhs_id) or { '' }
|
||||
boxed_optional := if semantic_v_type is types.OptionType
|
||||
&& cgen_unalias_type(optional_result_wrapped_base(semantic_v_type)) is types.Interface {
|
||||
g.optional_interface_assignment_boxes_payload(semantic_v_type,
|
||||
optional_result_unalias_type(g.usable_expr_type(rhs_id)), rhs_id) or { '' }
|
||||
} else {
|
||||
''
|
||||
}
|
||||
ct0 := if rhs.kind == .struct_init
|
||||
&& flat.source_type_shape(g.struct_init_effective_type_name(rhs_id, rhs)).has(.channel_type) {
|
||||
'chan'
|
||||
@@ -8814,7 +8819,6 @@ fn (mut g FlatGen) track_ierror_stack_pointer_alias(lhs flat.Node, rhs flat.Node
|
||||
return
|
||||
}
|
||||
needs_copy := g.ierror_pointer_payload_expr_needs_heap_copy(rhs)
|
||||
|| g.ierror_array_get_pointer_alias_needs_copy(rhs)
|
||||
g.declare_ierror_pointer_alias(lhs.value, needs_copy)
|
||||
}
|
||||
|
||||
@@ -8849,7 +8853,7 @@ fn (g &FlatGen) runtime_closure_lifted_c_name(node flat.Node) ?string {
|
||||
return g.cname(node.value)
|
||||
}
|
||||
for i in 0 .. node.children_count {
|
||||
if c_name := g.runtime_closure_lifted_c_name(g.a.child_node(&node, i)) {
|
||||
if c_name := g.runtime_closure_lifted_c_name(g.a.node(g.a.child(&node, i))) {
|
||||
return c_name
|
||||
}
|
||||
}
|
||||
@@ -8897,8 +8901,7 @@ fn (mut g FlatGen) track_ierror_stack_pointer_alias_assign(lhs flat.Node, rhs fl
|
||||
if lhs.kind != .ident || lhs.value.len == 0 {
|
||||
return
|
||||
}
|
||||
g.assign_ierror_pointer_alias(lhs.value, g.ierror_pointer_payload_expr_needs_heap_copy(rhs)
|
||||
|| g.ierror_array_get_pointer_alias_needs_copy(rhs))
|
||||
g.assign_ierror_pointer_alias(lhs.value, g.ierror_pointer_payload_expr_needs_heap_copy(rhs))
|
||||
}
|
||||
|
||||
fn (mut g FlatGen) track_local_pointer_alias_assign(lhs flat.Node, rhs_id flat.NodeId) {
|
||||
@@ -10417,14 +10420,21 @@ fn mark_global_name_filter(mut bits [64]u64, name string) {
|
||||
// spelling override. Void bindings remain on the function-placeholder path.
|
||||
@[inline]
|
||||
fn (g &FlatGen) ordinary_local_cname(name string, owner types.ScopeBindingOwner) ?string {
|
||||
if isnil(g.local_name_exceptions) || g.local_shared_storage_by_owner.len > 0
|
||||
|| g.local_implicit_deref_by_owner.len > 0 || g.shadowed_global_locals.len > 0
|
||||
|| g.cur_c_fn_calls.len > 0 || local_name_shadows_c_runtime(name) {
|
||||
if isnil(g.local_name_exceptions) || local_name_shadows_c_runtime(name) {
|
||||
return none
|
||||
}
|
||||
typ := owner.bound_type() or { return none }
|
||||
if typ is types.Void { return none }
|
||||
key := owner.storage_key()
|
||||
if (g.local_shared_storage_by_owner.len > 0 && g.local_shared_storage_by_owner[key])
|
||||
|| (g.local_implicit_deref_by_owner.len > 0 && g.local_implicit_deref_by_owner[key])
|
||||
|| (g.shadowed_global_locals.len > 0 && key in g.shadowed_global_locals) {
|
||||
return none
|
||||
}
|
||||
cname := g.cname(name)
|
||||
if g.cur_c_fn_calls.len > 0 && cname in g.cur_c_fn_calls {
|
||||
return none
|
||||
}
|
||||
// Some source names acquire a reserved-word prefix. Check the emitted
|
||||
// spelling too, so a typedef named v_abs still protects a local named abs.
|
||||
if name in g.local_name_exceptions.names
|
||||
|
||||
@@ -224,11 +224,14 @@ fn (mut g FlatGen) value_c_type(t &types.Type) string {
|
||||
return qualified + pointer_suffix
|
||||
}
|
||||
}
|
||||
if target_source_id := g.tc.type_aliases.type_id(ct) {
|
||||
return g.tc.c_type(unalias_type(g.tc.parse_type(target_source_id)))
|
||||
}
|
||||
if target_source_id := g.tc.type_aliases.type_id('main.${ct}') {
|
||||
return g.tc.c_type(unalias_type(g.tc.parse_type(target_source_id)))
|
||||
// Both alias keys have the same final component, including C spellings.
|
||||
if g.name_may_be_type_alias(ct) {
|
||||
if target_source_id := g.tc.type_aliases.type_id(ct) {
|
||||
return g.tc.c_type(unalias_type(g.tc.parse_type(target_source_id)))
|
||||
}
|
||||
if target_source_id := main_type_alias_id(g.tc.type_aliases, ct) {
|
||||
return g.tc.c_type(unalias_type(g.tc.parse_type(target_source_id)))
|
||||
}
|
||||
}
|
||||
return ct
|
||||
}
|
||||
@@ -928,7 +931,7 @@ fn (g &FlatGen) node_source_file(node &flat.Node) string {
|
||||
}
|
||||
}
|
||||
for i in 0 .. child.children_count {
|
||||
pending << g.a.child_node(&child, i)
|
||||
pending << g.a.node(g.a.child(&child, i))
|
||||
}
|
||||
}
|
||||
if g.cur_fn_source_file.len > 0 {
|
||||
|
||||
@@ -299,3 +299,88 @@ fn test_body_resolve_reference_checks_the_full_id_on_collisions() {
|
||||
assert g.body_resolve_type_reference(flat.NodeId(usable_expr_type_memo_slots)) is types.String
|
||||
}
|
||||
}
|
||||
|
||||
fn test_usable_expr_type_keeps_owned_container_child_references() {
|
||||
for canonical in [false, true] {
|
||||
for container_kind in 0 .. 4 {
|
||||
mut a := flat.FlatAst.new()
|
||||
value := a.add_val(.ident, 'value')
|
||||
a.children << value
|
||||
access := a.add_node(flat.Node{
|
||||
kind: if container_kind == 0 { .prefix } else { .index }
|
||||
op: .mul
|
||||
children_start: 0
|
||||
children_count: 1
|
||||
})
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
// A raw heap-owned child need not already have interner identity.
|
||||
child := types.new_type_reference(types.Type(types.String{}))
|
||||
container := match container_kind {
|
||||
0 { types.Type(types.Pointer{ base_type: child }) }
|
||||
1 { types.Type(types.Array{ elem_type: child }) }
|
||||
2 { types.Type(types.ArrayFixed{ elem_type: child, len: 3 }) }
|
||||
else {
|
||||
types.Type(types.Map{ key_type: types.new_type_reference(types.Type(types.int_)), value_type: child })
|
||||
}
|
||||
}
|
||||
owned := if canonical {
|
||||
tc.intern_type_reference(container)
|
||||
} else {
|
||||
types.new_type_reference(container)
|
||||
}
|
||||
expected := match owned {
|
||||
types.Pointer { owned.base_type }
|
||||
types.Array { owned.elem_type }
|
||||
types.ArrayFixed { owned.elem_type }
|
||||
types.Map { owned.value_type }
|
||||
else { panic('expected container') }
|
||||
}
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cur_param_types['value'] = owned
|
||||
g.memo_usable_expr_types = true
|
||||
g.begin_usable_expr_type_memo()
|
||||
before := g.usable_expr_type(access)
|
||||
assert voidptr(before) == voidptr(expected)
|
||||
assert *before == types.Type(types.String{})
|
||||
g.cur_param_types['value'] = tc.intern_type_reference(types.Type(types.Array{ elem_type: types.new_type_reference(types.Type(types.bool_)) }))
|
||||
for i in 0 .. 1024 {
|
||||
tc.intern_type_reference(types.Type(types.ArrayFixed{ elem_type: child, len: i + 1 }))
|
||||
}
|
||||
assert voidptr(g.usable_expr_type(access)) == voidptr(expected)
|
||||
assert *before == types.Type(types.String{})
|
||||
g.end_usable_expr_type_memo()
|
||||
g.begin_usable_expr_type_memo()
|
||||
if container_kind != 0 {
|
||||
assert *g.usable_expr_type(access) == types.Type(types.bool_)
|
||||
}
|
||||
assert *before == types.Type(types.String{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_usable_expr_type_shared_parameter_keeps_owned_value_reference() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.ident, 'value')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
child := types.new_type_reference(types.Type(types.String{}))
|
||||
parameter := types.new_type_reference(types.Type(types.Pointer{ base_type: child }))
|
||||
mut g := FlatGen.new()
|
||||
g.a = &a
|
||||
g.tc = &tc
|
||||
g.cur_param_types['value'] = parameter
|
||||
g.push_scope()
|
||||
owner := tc.cur_scope.insert_with_owner('value', *parameter)
|
||||
g.declare_local_shared_storage(owner, true)
|
||||
g.memo_usable_expr_types = true
|
||||
g.begin_usable_expr_type_memo()
|
||||
before := g.usable_expr_type(id)
|
||||
assert voidptr(before) == voidptr(child)
|
||||
g.end_usable_expr_type_memo()
|
||||
g.declare_local_shared_storage(owner, false)
|
||||
g.begin_usable_expr_type_memo()
|
||||
assert voidptr(g.usable_expr_type(id)) == voidptr(parameter)
|
||||
assert *before == types.Type(types.String{})
|
||||
g.pop_scope()
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
module c
|
||||
|
||||
import strings
|
||||
|
||||
fn write_byte_reference(mut g FlatGen, s string) {
|
||||
if g.line_start {
|
||||
g.write_indent()
|
||||
}
|
||||
if s.len == 0 {
|
||||
if g.indent > 0 {
|
||||
g.line_start = false
|
||||
}
|
||||
return
|
||||
}
|
||||
unsafe { g.sb.write_ptr(s.str, s.len) }
|
||||
g.line_start = s[s.len - 1] == `\n`
|
||||
}
|
||||
|
||||
fn test_single_byte_emission_matches_reference_for_all_bytes_and_indentation() {
|
||||
for capacity in [0, 1, 32] {
|
||||
for indent in [0, 1, 3] {
|
||||
for line_start in [false, true] {
|
||||
for value in 0 .. 256 {
|
||||
text := [u8(value)].bytestr()
|
||||
mut actual := FlatGen{ sb: strings.new_builder(capacity), indent: indent, line_start: line_start }
|
||||
mut expected := FlatGen{ sb: strings.new_builder(capacity), indent: indent, line_start: line_start }
|
||||
actual.write(text)
|
||||
write_byte_reference(mut expected, text)
|
||||
assert actual.sb == expected.sb
|
||||
assert actual.line_start == expected.line_start
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_single_byte_emission_preserves_mixed_writes_and_repeated_growth() {
|
||||
mut actual := FlatGen{ sb: strings.new_builder(0), indent: 2, line_start: true }
|
||||
mut expected := FlatGen{ sb: strings.new_builder(0), indent: 2, line_start: true }
|
||||
for _ in 0 .. 128 {
|
||||
for text in ['', '(', ')', 'longer generated identifier', '\x00', '\n', '字段', 'a\x00b'] {
|
||||
actual.write(text)
|
||||
write_byte_reference(mut expected, text)
|
||||
assert actual.sb == expected.sb
|
||||
assert actual.line_start == expected.line_start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_single_byte_emission_keeps_borrowed_bytes_in_existing_capacity() {
|
||||
mut g := FlatGen{ sb: strings.new_builder(32) }
|
||||
g.sb.write_string('x\n')
|
||||
first := unsafe { (&u8(g.sb.data)).vstring_with_len(1) }
|
||||
g.write(first)
|
||||
assert g.sb == 'x\nx'.bytes()
|
||||
assert !g.line_start
|
||||
last := unsafe { (&u8(g.sb.data) + 1).vstring_with_len(1) }
|
||||
g.write(last)
|
||||
assert g.sb == 'x\nx\n'.bytes()
|
||||
assert g.line_start
|
||||
g.line_start = false
|
||||
unsafe { *(&u8(g.sb.data) + g.sb.len) = `z` }
|
||||
unused := unsafe { (&u8(g.sb.data) + g.sb.len).vstring_with_len(1) }
|
||||
g.write(unused)
|
||||
assert g.sb == 'x\nx\nz'.bytes()
|
||||
assert !g.line_start
|
||||
}
|
||||
|
||||
fn test_single_byte_emission_unshares_slice_builders() {
|
||||
mut backing := []u8{len: 32, init: `q`}
|
||||
mut g := FlatGen{ sb: strings.Builder(backing[0..1]) }
|
||||
g.write('x')
|
||||
assert g.sb == 'qx'.bytes()
|
||||
assert backing[1] == `q`
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
module markused
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_contextual_alias_lookup_preserves_order_wrappers_and_current_alias_registry() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
collector := CallCollector{ a: &a, tc: &tc }
|
||||
imports := {
|
||||
'a': 'dep'
|
||||
}
|
||||
for name in ['dep.Value', 'a.Value', 'owner.dep.Value', 'owner.a.Value'] {
|
||||
tc.type_aliases.set(name, 'int')
|
||||
}
|
||||
for expected in ['dep.Value', 'a.Value', 'owner.dep.Value', 'owner.a.Value'] {
|
||||
actual := collector.contextual_alias_type_name(flat.source_type_id(' []&?a.Value '), 'owner', imports) or {
|
||||
panic('an alias candidate must remain')
|
||||
}
|
||||
assert flat.source_type_name(actual) == '[]&?' + expected
|
||||
tc.type_aliases.delete(expected)
|
||||
}
|
||||
assert collector.contextual_alias_type_name(flat.source_type_id('[]&?a.Value'), 'owner', imports) == none
|
||||
}
|
||||
|
||||
fn test_contextual_alias_lookup_keeps_builtin_and_main_names_unqualified() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
collector := CallCollector{ a: &a, tc: &tc }
|
||||
tc.type_aliases.set('owner.Value', 'int')
|
||||
tc.type_aliases.set('builtin.Value', 'int')
|
||||
tc.type_aliases.set('main.Value', 'int')
|
||||
for cur_module in ['main', 'builtin', ''] {
|
||||
assert collector.contextual_alias_type_name(flat.source_type_id('Value'), cur_module, map[string]string{}) == none
|
||||
}
|
||||
assert flat.source_type_name(collector.contextual_alias_type_name(flat.source_type_id('Value'), 'owner', map[string]string{})?) == 'owner.Value'
|
||||
}
|
||||
+22
-30
@@ -855,6 +855,7 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
|
||||
mut body_index := map[int]int{}
|
||||
mut body_results := []BodyCalls{}
|
||||
if body_ids.len >= min_eager_markused_bodies
|
||||
&& (!use_prepared || markused_can_parallel_precollect(&collector))
|
||||
&& (!tc.skips_library_bodies || a.worker_count() > 0) {
|
||||
mut checked_ids := []int{}
|
||||
mut checked_modules := []string{}
|
||||
@@ -1951,7 +1952,7 @@ fn markused_reachable_modules(a &flat.FlatAst, tc &types.TypeChecker) map[string
|
||||
mut has_user_root := false
|
||||
file_modules := markused_top_level_file_modules(a, tc)
|
||||
for file_idx in tc.top_level_idx {
|
||||
file_node := a.nodes[file_idx]
|
||||
file_node := &a.nodes[file_idx]
|
||||
if file_node.kind != .file {
|
||||
continue
|
||||
}
|
||||
@@ -1969,7 +1970,7 @@ fn markused_reachable_modules(a &flat.FlatAst, tc &types.TypeChecker) map[string
|
||||
mut imports := module_imports[module_name] or { []string{} }
|
||||
if file_idx >= a.user_code_start || !markused_file_is_test(file_node.value) {
|
||||
for i in 0 .. file_node.children_count {
|
||||
child := a.child_node(&file_node, i)
|
||||
child := a.child_node(file_node, i)
|
||||
if child.kind == .import_decl && child.value.len > 0 {
|
||||
imports << child.value
|
||||
}
|
||||
@@ -5633,7 +5634,7 @@ fn (c &CallCollector) collect_body_with_generic_detection(node &flat.Node, cur_m
|
||||
inferred_visible_idents = markused_visible_local_idents(c.a, node, visibility_names)
|
||||
}
|
||||
} else {
|
||||
_, local_types, local_ident_types, inferred_visible_idents = c.local_value_info_for_names(node, cur_module, imports, local_values, has_inference_binders)
|
||||
_, local_types, local_ident_types, inferred_visible_idents = c.local_value_info_for_names(node, cur_module, imports, local_values, visibility_names, has_inference_binders)
|
||||
}
|
||||
if receiver_name.len > 0 && receiver_struct != 0 {
|
||||
local_types.set_id(receiver_name, receiver_struct)
|
||||
@@ -6583,16 +6584,16 @@ fn markused_direct_sum_type(typ types.Type) bool {
|
||||
@[direct_array_access]
|
||||
fn (c &CallCollector) local_value_info_with_visibility(node &flat.Node, cur_module string, imports map[string]string) (map[string]bool, flat.TypeTextBindings, flat.NodeTypeTextBindings, map[int]bool) {
|
||||
names, has_inference_binders := c.local_value_names(node)
|
||||
return c.local_value_info_for_names(node, cur_module, imports, names, has_inference_binders)
|
||||
visibility_names := c.local_value_visibility_names(names, cur_module, imports)
|
||||
return c.local_value_info_for_names(node, cur_module, imports, names, visibility_names, has_inference_binders)
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn (c &CallCollector) local_value_info_for_names(node &flat.Node, cur_module string, imports map[string]string, names map[string]bool, has_inference_binders bool) (map[string]bool, flat.TypeTextBindings, flat.NodeTypeTextBindings, map[int]bool) {
|
||||
fn (c &CallCollector) local_value_info_for_names(node &flat.Node, cur_module string, imports map[string]string, names map[string]bool, visibility_names map[string]bool, has_inference_binders bool) (map[string]bool, flat.TypeTextBindings, flat.NodeTypeTextBindings, map[int]bool) {
|
||||
mut type_names := c.new_local_types()
|
||||
mut ident_types := c.new_ident_types()
|
||||
// Scope visibility only distinguishes local names that can also name a
|
||||
// function or constant. Other bodies do not need a second binding walk.
|
||||
visibility_names := c.local_value_visibility_names(names, cur_module, imports)
|
||||
// function or constant. The caller already identified those names.
|
||||
visible_local_idents := if visibility_names.len > 0 {
|
||||
markused_visible_local_idents(c.a, node, visibility_names)
|
||||
} else {
|
||||
@@ -7029,13 +7030,7 @@ fn (c &CallCollector) contextual_alias_type_name(type_text_id u32, cur_module st
|
||||
return flat.source_type_id(prefix + inner)
|
||||
}
|
||||
}
|
||||
resolved := markused_resolve_imported_source_type(clean_id, imports)
|
||||
for candidate in markused_alias_type_candidates(resolved, clean_id, cur_module) {
|
||||
if c.tc.type_aliases.contains(flat.source_type_name(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return none
|
||||
return c.alias_type_name_from_bytes(clean, clean_id, cur_module, imports)
|
||||
}
|
||||
|
||||
fn markused_fn_signature_name_candidates(name string, cur_module string) []string {
|
||||
@@ -7963,45 +7958,42 @@ fn (c &CallCollector) alias_type_from_callee(callee &flat.Node, cur_module strin
|
||||
}
|
||||
|
||||
fn (c &CallCollector) alias_type_from_name(name_id u32, cur_module string, imports map[string]string) ?types.Type {
|
||||
return c.alias_type_from_name_bytes(flat.source_type_name(name_id), name_id, cur_module, imports)
|
||||
alias_id := c.alias_type_name_from_bytes(flat.source_type_name(name_id), name_id, cur_module, imports) or { return none }
|
||||
return c.tc.parse_canonical_type(alias_id)
|
||||
}
|
||||
|
||||
// alias_type_from_name_text checks the spellings of markused_alias_type_candidates,
|
||||
// in the same order, for a type alias. Only a spelling that names one gets a
|
||||
// source type ID: most names here are callees, and interning all of their
|
||||
// candidate spellings filled the process-wide type text table.
|
||||
// alias_type_from_name_text parses only an alias found by its source spelling.
|
||||
fn (c &CallCollector) alias_type_from_name_text(name string, cur_module string, imports map[string]string) ?types.Type {
|
||||
return c.alias_type_from_name_bytes(name, 0, cur_module, imports)
|
||||
alias_id := c.alias_type_name_from_bytes(name, 0, cur_module, imports) or { return none }
|
||||
return c.tc.parse_canonical_type(alias_id)
|
||||
}
|
||||
|
||||
fn (c &CallCollector) alias_type_from_name_bytes(name string, name_id u32, cur_module string, imports map[string]string) ?types.Type {
|
||||
// alias_type_name_from_bytes checks candidate spellings in their original order
|
||||
// and interns only a spelling registered as a type alias.
|
||||
fn (c &CallCollector) alias_type_name_from_bytes(name string, name_id u32, cur_module string, imports map[string]string) ?u32 {
|
||||
if c.tc.type_aliases.ids.len == 0 {
|
||||
return none
|
||||
}
|
||||
resolved := markused_resolve_imported_name(name, imports)
|
||||
if resolved.len > 0 && c.tc.type_aliases.contains(resolved) {
|
||||
return c.tc.parse_canonical_type(if name_id != 0 && resolved == name {
|
||||
return if name_id != 0 && resolved == name {
|
||||
name_id
|
||||
} else {
|
||||
flat.source_type_id(resolved)
|
||||
})
|
||||
}
|
||||
}
|
||||
if name != resolved && name.len > 0 && c.tc.type_aliases.contains(name) {
|
||||
return c.tc.parse_canonical_type(if name_id != 0 {
|
||||
name_id
|
||||
} else {
|
||||
flat.source_type_id(name)
|
||||
})
|
||||
return if name_id != 0 { name_id } else { flat.source_type_id(name) }
|
||||
}
|
||||
if cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' {
|
||||
qualified := '${cur_module}.${resolved}'
|
||||
if c.tc.type_aliases.contains(qualified) {
|
||||
return c.tc.parse_canonical_type(flat.source_type_id(qualified))
|
||||
return flat.source_type_id(qualified)
|
||||
}
|
||||
if name != resolved {
|
||||
qualified_name := '${cur_module}.${name}'
|
||||
if c.tc.type_aliases.contains(qualified_name) {
|
||||
return c.tc.parse_canonical_type(flat.source_type_id(qualified_name))
|
||||
return flat.source_type_id(qualified_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
module markused
|
||||
|
||||
fn markused_can_parallel_precollect(_ &CallCollector) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// precollect_body_calls falls back to the serial analysis when v3 is built
|
||||
// with the internal `v3_no_parallel` define.
|
||||
fn precollect_body_calls(collector CallCollector, body_ids []int, body_modules []string, body_import_contexts []int) []BodyCalls {
|
||||
|
||||
@@ -156,6 +156,12 @@ fn clone_body_calls(value BodyCalls) BodyCalls {
|
||||
}
|
||||
}
|
||||
|
||||
// Only eager-collect prepared bodies when helpers can offset scanning unreachable bodies.
|
||||
fn markused_can_parallel_precollect(collector &CallCollector) bool {
|
||||
return collector.a.worker_count() > 0
|
||||
|| (collector.tc.scoped_parallel_workers_enabled() && runtime.nr_jobs() > 1)
|
||||
}
|
||||
|
||||
// precollect_body_calls analyzes every fn_decl body, across threads when there
|
||||
// is enough work.
|
||||
fn precollect_body_calls(collector CallCollector, body_ids []int, body_modules []string, body_import_contexts []int) []BodyCalls {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
module markused
|
||||
|
||||
import os
|
||||
import v.parser
|
||||
import v.pref
|
||||
import v.types
|
||||
|
||||
fn test_serial_prepared_reachability_matches_eager_body_collection() {
|
||||
root := os.join_path(os.vtmp_dir(), 'markused_serial_lazy_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
main_path := os.join_path(root, 'main.v')
|
||||
dep_path := os.join_path(root, 'dependency.v')
|
||||
os.write_file(main_path, 'module main
|
||||
import dependency
|
||||
fn main() { _ := dependency.entry() }
|
||||
')!
|
||||
mut source := 'module dependency
|
||||
const answer = const_helper()
|
||||
fn const_helper() int { return 7 }
|
||||
fn default_helper() int { return answer }
|
||||
struct Item { value int = default_helper() }
|
||||
fn (item Item) read() int { return item.value }
|
||||
fn middle() int {
|
||||
item := Item{}
|
||||
return item.read()
|
||||
}
|
||||
fn dispatch(callback fn () int) int { return callback() }
|
||||
pub fn entry() int { return dispatch(middle) }
|
||||
fn unreachable_helper() int { return 101 }
|
||||
'
|
||||
// Cross the eager threshold with bodies whose implicit calls and const refs
|
||||
// must stay unreachable, despite having been scanned by the eager control.
|
||||
for i in 0 .. min_eager_markused_bodies {
|
||||
source += 'fn unused_${i}() int { return unreachable_helper() + answer }\n'
|
||||
}
|
||||
os.write_file(dep_path, source)!
|
||||
mut p := parser.Parser.new(pref.new_preferences())
|
||||
mut a := p.parse_files([main_path, dep_path])
|
||||
assert p.diagnostics.len == 0, p.diagnostics.str()
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.check_semantics()
|
||||
assert tc.errors.len == 0, tc.errors.str()
|
||||
assert a.worker_count() == 0
|
||||
collector := CallCollector{ a: a, tc: &tc }
|
||||
assert !markused_can_parallel_precollect(&collector)
|
||||
mut prepared := prepare_markused_declarations(a, &tc, true)
|
||||
defer { prepared.release() }
|
||||
assert prepared.ready && prepared.body_ids.len > min_eager_markused_bodies
|
||||
lazy := mark_used_without_generic_detection_prepared(a, &tc, mut prepared)
|
||||
if output := os.getenv_opt('MARKUSED_SERIAL_LAZY_RECEIPT') {
|
||||
os.write_file(output, lazy.keys().sorted().join('\n'))!
|
||||
}
|
||||
for name in ['entry', 'dispatch', 'middle', 'Item.read', 'default_helper', 'const_helper'] {
|
||||
assert lazy['dependency.${name}'], name
|
||||
}
|
||||
for name in ['unreachable_helper', 'unused_0', 'unused_${min_eager_markused_bodies - 1}'] {
|
||||
assert !lazy['dependency.${name}'], name
|
||||
}
|
||||
$if !v3_no_parallel ? {
|
||||
a.ensure_workers(2)
|
||||
defer { a.close_workers() }
|
||||
assert markused_can_parallel_precollect(&collector)
|
||||
parallel := mark_used_without_generic_detection_prepared(a, &tc, mut prepared)
|
||||
assert parallel == lazy
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
module scanner
|
||||
|
||||
// A byte lookup avoids repeating the ASCII identifier ranges for every tail byte.
|
||||
const identifier_tail_bytes = new_identifier_tail_bytes()
|
||||
|
||||
fn new_identifier_tail_bytes() [256]bool {
|
||||
mut bytes := [256]bool{}
|
||||
for i in 0 .. 128 {
|
||||
c := u8(i)
|
||||
bytes[i] = (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)
|
||||
|| (c >= `0` && c <= `9`) || c == `_`
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
module scanner
|
||||
|
||||
import v.pref
|
||||
import v.token
|
||||
|
||||
fn test_identifier_tail_preserves_every_byte_boundary() {
|
||||
for i in 0 .. 256 {
|
||||
c := u8(i)
|
||||
source := [u8(`a`), c, `Z`].bytestr()
|
||||
mut files := token.FileSet.new()
|
||||
file := files.add_file('identifier_tail.v', source.len)
|
||||
mut s := new_scanner(&pref.Preferences{}, .normal)
|
||||
s.init(file, source)
|
||||
assert s.scan() == .name
|
||||
tail := (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)
|
||||
|| (c >= `0` && c <= `9`) || c == `_`
|
||||
assert s.lit == if tail { source } else { 'a' }, 'byte ${i}'
|
||||
assert s.pos == 0
|
||||
assert s.offset == if tail { 3 } else { 1 }, 'byte ${i}'
|
||||
assert s.diagnostics.len == 0, 'byte ${i}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_identifier_tail_keeps_prefixes_keywords_and_semicolons() {
|
||||
source := "_a0Z abc123 return_value @type r'raw' c'x' js'js'\nreturn"
|
||||
mut files := token.FileSet.new()
|
||||
file := files.add_file('identifier_tokens.v', source.len)
|
||||
mut s := new_scanner(&pref.Preferences{ backend: 'fastc' }, .normal)
|
||||
s.init(file, source)
|
||||
for expected in [token.Token.name, .name, .name, .name, .string, .char, .string, .semicolon,
|
||||
.key_return, .semicolon, .eof] {
|
||||
assert s.scan() == expected
|
||||
}
|
||||
assert s.diagnostics.len == 0
|
||||
}
|
||||
@@ -440,9 +440,7 @@ pub fn (mut s Scanner) scan() token.Token {
|
||||
src := s.src
|
||||
mut off := s.offset
|
||||
for off < src.len {
|
||||
c3 := src[off]
|
||||
if (c3 >= `a` && c3 <= `z`) || (c3 >= `A` && c3 <= `Z`)
|
||||
|| (c3 >= `0` && c3 <= `9`) || c3 == `_` {
|
||||
if identifier_tail_bytes[src[off]] {
|
||||
off++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
module main
|
||||
|
||||
struct Entry {
|
||||
value int
|
||||
}
|
||||
|
||||
struct Tree {
|
||||
entries []Entry
|
||||
}
|
||||
|
||||
struct Payload {
|
||||
pointer &int
|
||||
}
|
||||
|
||||
struct Holder {
|
||||
inner Payload
|
||||
}
|
||||
|
||||
fn extract(copy Payload) &int { return copy.pointer }
|
||||
|
||||
fn (holder &Holder) copy_payload() &int { return extract(holder.inner) }
|
||||
|
||||
fn payload_snapshot(value int) &int {
|
||||
local := value
|
||||
holder := Holder{ inner: Payload{ pointer: &local } }
|
||||
return holder.copy_payload()
|
||||
}
|
||||
|
||||
fn (tree &Tree) index(node &Entry) int { return node.value }
|
||||
|
||||
fn (tree &Tree) child_node(node &Entry) &Entry {
|
||||
id := tree.index(node)
|
||||
return unsafe { &tree.entries[id] }
|
||||
}
|
||||
|
||||
fn (tree &Tree) type_reference(node &Entry) &Entry {
|
||||
if node.value == 0 { return unsafe { &tree.entries[0] } }
|
||||
return unsafe { &tree.entries[1] }
|
||||
}
|
||||
|
||||
fn (tree &Tree) copied_payload(node &Entry) &Entry {
|
||||
_ = node.value
|
||||
return tree.child_node(node)
|
||||
}
|
||||
|
||||
fn identity(node &Entry) &Entry { return node }
|
||||
|
||||
fn inline_address(node &Entry) &int { return &node.value }
|
||||
|
||||
fn snapshot_lookup(tree &Tree, node Entry) &Entry { return tree.child_node(&node) }
|
||||
|
||||
fn snapshot_type(tree &Tree, node Entry) &Entry { return tree.type_reference(&node) }
|
||||
|
||||
fn snapshot_identity(node Entry) &Entry { return identity(&node) }
|
||||
|
||||
struct AuditPayload {
|
||||
mut:
|
||||
pointer &int
|
||||
}
|
||||
|
||||
struct AuditEnvelope {
|
||||
mut:
|
||||
inner AuditPayload
|
||||
}
|
||||
|
||||
struct AuditSink {
|
||||
mut:
|
||||
pointer &int = unsafe { nil }
|
||||
}
|
||||
|
||||
fn audit_extract(copy AuditPayload) &int { return copy.pointer }
|
||||
|
||||
fn audit_extract_optional(copy AuditPayload) ?&int { return copy.pointer }
|
||||
|
||||
fn audit_extract_result(copy AuditPayload) !&int { return copy.pointer }
|
||||
|
||||
fn audit_storage(input &AuditEnvelope) &int { return audit_extract(input.inner) }
|
||||
|
||||
fn audit_storage_optional(input &AuditEnvelope) ?&int { return audit_extract_optional(input.inner) }
|
||||
|
||||
fn audit_storage_result(input &AuditEnvelope) !&int { return audit_extract_result(input.inner) }
|
||||
|
||||
fn audit_storage_field(input &AuditEnvelope) &&int { return &input.inner.pointer }
|
||||
|
||||
fn audit_store_payload(input &AuditEnvelope, mut sink AuditSink) &int {
|
||||
sink.pointer = input.inner.pointer
|
||||
return sink.pointer
|
||||
}
|
||||
|
||||
fn audit_direct(value int) &int {
|
||||
local := value
|
||||
payload := AuditEnvelope{ inner: AuditPayload{ pointer: &local } }
|
||||
return audit_storage(&payload)
|
||||
}
|
||||
|
||||
fn audit_payload_assignment(value int) &int {
|
||||
local := value
|
||||
mut payload := AuditEnvelope{ inner: AuditPayload{ pointer: unsafe { nil } } }
|
||||
payload.inner.pointer = &local
|
||||
return audit_storage(&payload)
|
||||
}
|
||||
|
||||
fn audit_optional(value int) ?&int {
|
||||
local := value
|
||||
payload := AuditEnvelope{ inner: AuditPayload{ pointer: &local } }
|
||||
return audit_storage_optional(&payload)
|
||||
}
|
||||
|
||||
fn audit_result(value int) !&int {
|
||||
local := value
|
||||
payload := AuditEnvelope{ inner: AuditPayload{ pointer: &local } }
|
||||
return audit_storage_result(&payload)
|
||||
}
|
||||
|
||||
fn audit_field(value int) &&int {
|
||||
local := value
|
||||
payload := AuditEnvelope{ inner: AuditPayload{ pointer: &local } }
|
||||
return audit_storage_field(&payload)
|
||||
}
|
||||
|
||||
fn audit_mutable_store(value int, mut sink AuditSink) &int {
|
||||
local := value
|
||||
payload := AuditEnvelope{ inner: AuditPayload{ pointer: &local } }
|
||||
return audit_store_payload(&payload, mut sink)
|
||||
}
|
||||
|
||||
struct AuditCallback {
|
||||
audit_storage fn (&AuditEnvelope) &AuditEnvelope
|
||||
}
|
||||
|
||||
fn audit_callback_identity(input &AuditEnvelope) &AuditEnvelope { return input }
|
||||
|
||||
fn audit_callback_escape(value int) &AuditEnvelope {
|
||||
local := value
|
||||
payload := AuditEnvelope{ inner: AuditPayload{ pointer: &local } }
|
||||
callback := AuditCallback{ audit_storage: audit_callback_identity }
|
||||
return callback.audit_storage(&payload)
|
||||
}
|
||||
|
||||
fn stack_churn(value int) int {
|
||||
mut values := [256]int{}
|
||||
for i in 0 .. values.len { values[i] = value + i }
|
||||
return values[127]
|
||||
}
|
||||
|
||||
fn test_storage_borrow_keeps_escaping_payloads_alive() {
|
||||
tree := Tree{ entries: [Entry{ value: 0 }, Entry{ value: 1 }] }
|
||||
for i in 0 .. 200 {
|
||||
direct := audit_direct(i)
|
||||
optional := audit_optional(i) or { panic('missing optional') }
|
||||
result := audit_result(i) or { panic(err) }
|
||||
field := audit_field(i)
|
||||
mut sink := AuditSink{}
|
||||
stored := audit_mutable_store(i, mut sink)
|
||||
written := audit_payload_assignment(i)
|
||||
callback := audit_callback_escape(i)
|
||||
copied := payload_snapshot(i)
|
||||
identity_value := snapshot_identity(Entry{ value: i })
|
||||
assert stack_churn(i) == i + 127
|
||||
assert *direct == i
|
||||
assert *optional == i
|
||||
assert *result == i
|
||||
assert **field == i
|
||||
assert *stored == i
|
||||
assert *sink.pointer == i
|
||||
assert *written == i
|
||||
assert *callback.inner.pointer == i
|
||||
assert *copied == i
|
||||
assert identity_value.value == i
|
||||
assert snapshot_lookup(&tree, Entry{ value: 1 }).value == 1
|
||||
assert snapshot_type(&tree, Entry{ value: 0 }).value == 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn builtin_shape_call_name(a &flat.FlatAst, id flat.NodeId) string {
|
||||
node := a.nodes[int(id)]
|
||||
assert node.kind == .call
|
||||
return a.nodes[int(a.child(&node, 0))].value
|
||||
}
|
||||
|
||||
fn test_builtin_call_shape_keeps_ordinary_bare_and_indexed_function_calls() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.validating_generic_spec = true
|
||||
for name in ['ordinary', 'has', 'free', 'clone', 'type_name', 'from', 'from_string'] {
|
||||
for arity in 0 .. 3 {
|
||||
mut args := []flat.NodeId{}
|
||||
for i in 0 .. arity {
|
||||
args << t.make_int_literal(i)
|
||||
}
|
||||
call := t.make_call_expr_typed(t.make_ident(name), args, flat.source_type_int)
|
||||
node := a.nodes[int(call)]
|
||||
nodes_before := a.nodes.len
|
||||
pending_before := t.pending_stmts.len
|
||||
assert t.try_lower_builtin_call(call, node) == none
|
||||
assert a.nodes.len == nodes_before
|
||||
assert t.pending_stmts.len == pending_before
|
||||
}
|
||||
}
|
||||
indexed := t.make_index(t.make_ident('ordinary'), t.make_ident('int'), u32(0))
|
||||
call := t.make_call_expr_typed(indexed, [t.make_int_literal(1)], flat.source_type_int)
|
||||
assert t.try_lower_builtin_call(call, a.nodes[int(call)]) == none
|
||||
}
|
||||
|
||||
fn test_builtin_call_shape_preserves_primitive_named_and_generic_casts() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.begin_sparse_transform_node_caches(0)
|
||||
tc.structs['Target'] = []types.StructField{}
|
||||
tc.structs['Box'] = []types.StructField{}
|
||||
tc.structs['Box[int]'] = []types.StructField{}
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
for target in ['int', 'Target', 'Box[int]'] {
|
||||
call := t.make_call_expr_typed(t.make_ident(target), [t.make_int_literal(1)],
|
||||
flat.source_type_id(target))
|
||||
if target == 'Target' {
|
||||
tc.sparse_resolved_call_names[int(call)] = target
|
||||
}
|
||||
lowered := t.try_lower_builtin_call(call, a.nodes[int(call)]) or {
|
||||
panic('cast ${target} was skipped')
|
||||
}
|
||||
assert a.nodes[int(lowered)].kind == .cast_expr
|
||||
assert a.nodes[int(lowered)].source_value_id() == flat.source_type_id(target)
|
||||
}
|
||||
// A local with the type's spelling must keep the resolved-name cast guard.
|
||||
t.set_var_type('Target', flat.source_type_id('fn (int) int'))
|
||||
shadowed := t.make_call_expr_typed(t.make_ident('Target'), [t.make_int_literal(2)],
|
||||
flat.source_type_int)
|
||||
tc.sparse_resolved_call_names[int(shadowed)] = 'Target'
|
||||
assert t.try_lower_builtin_call(shadowed, a.nodes[int(shadowed)]) == none
|
||||
}
|
||||
|
||||
fn test_builtin_call_shape_preserves_bare_and_qualified_flag_default_value() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
for qualified in [false, true] {
|
||||
callee := if qualified {
|
||||
t.make_selector(t.make_ident('builtin'), 'flag_default_value', u32(0))
|
||||
} else {
|
||||
t.make_ident('flag_default_value')
|
||||
}
|
||||
arg := t.make_string_literal('value')
|
||||
call := t.make_call_expr_typed(callee, [arg], flat.source_type_string)
|
||||
lowered := t.try_lower_builtin_call(call, a.nodes[int(call)]) or {
|
||||
panic('flag_default_value was skipped')
|
||||
}
|
||||
assert builtin_shape_call_name(&a, lowered) == 'string__plus'
|
||||
outer := a.nodes[int(lowered)]
|
||||
inner := a.child(&outer, 1)
|
||||
assert builtin_shape_call_name(&a, inner) == 'string__plus'
|
||||
inner_node := a.nodes[int(inner)]
|
||||
escaped := a.child(&inner_node, 2)
|
||||
assert builtin_shape_call_name(&a, escaped) == 'escape_default_string'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_builtin_call_shape_preserves_static_assoc_with_generic_index_wrapper() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.structs['Box'] = StructInfo{ name: flat.source_type_id('Box'), module: 'main' }
|
||||
static_name := flat.encode_static_type_method_name('Box', 'make')
|
||||
t.fn_ret_types.set_id(static_name, flat.source_type_int)
|
||||
for generic in [false, true] {
|
||||
selector := t.make_selector(t.make_ident('Box'), 'make', u32(0))
|
||||
callee := if generic {
|
||||
t.make_index(selector, t.make_ident('int'), u32(0))
|
||||
} else {
|
||||
selector
|
||||
}
|
||||
call := t.make_call_expr_typed(callee, []flat.NodeId{}, flat.source_type_int)
|
||||
lowered := t.try_lower_builtin_call(call, a.nodes[int(call)]) or {
|
||||
panic('static associated call was skipped')
|
||||
}
|
||||
assert builtin_shape_call_name(&a, lowered) == static_name
|
||||
assert a.nodes[int(lowered)].children_count == 1
|
||||
assert a.nodes[int(lowered)].call_type_args() == if generic {
|
||||
[flat.source_type_int]
|
||||
} else {
|
||||
[]u32{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_builtin_call_shape_preserves_selector_folding_and_c_interop() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
selector := t.make_selector(t.make_string_literal('aBc'), 'to_upper', flat.source_type_string)
|
||||
call := t.make_call_expr_typed(selector, []flat.NodeId{}, flat.source_type_string)
|
||||
lowered := t.try_lower_builtin_call(call, a.nodes[int(call)]) or {
|
||||
panic('literal selector call was skipped')
|
||||
}
|
||||
assert a.nodes[int(lowered)].kind == .string_literal
|
||||
assert a.nodes[int(lowered)].value == 'ABC'
|
||||
c_callee := t.make_selector(t.make_ident('C'), 'printf', u32(0))
|
||||
c_call := t.make_call_expr_typed(c_callee, []flat.NodeId{}, flat.source_type_int)
|
||||
assert t.try_lower_builtin_call(c_call, a.nodes[int(c_call)]) == none
|
||||
}
|
||||
|
||||
fn test_builtin_call_shape_preserves_flag_enum_zero() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.enum_names['Flags'] = true
|
||||
tc.flag_enums['Flags'] = true
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
selector := t.make_selector(t.make_ident('Flags'), 'zero', u32(0))
|
||||
call := t.make_call_expr_typed(selector, []flat.NodeId{}, flat.source_type_id('Flags'))
|
||||
lowered := t.try_lower_builtin_call(call, a.nodes[int(call)]) or {
|
||||
panic('flag enum zero was skipped')
|
||||
}
|
||||
assert a.nodes[int(lowered)].kind == .cast_expr
|
||||
assert a.nodes[int(lowered)].source_value_id() == flat.source_type_id('Flags')
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
module transform
|
||||
|
||||
// Frozen key sets permit negative constant lookups without constructing
|
||||
// qualified names. Bits never retain source or disposable worker strings.
|
||||
struct ConstSelectorNameFilter {
|
||||
mut:
|
||||
const_count int
|
||||
suffix_count int
|
||||
bits [64]u64
|
||||
}
|
||||
|
||||
fn (mut t Transformer) prepare_const_selector_filter() {
|
||||
if isnil(t.tc) { return }
|
||||
mut filter := &ConstSelectorNameFilter{
|
||||
const_count: t.tc.const_types.len
|
||||
suffix_count: t.const_suffixes.len
|
||||
}
|
||||
for name, _ in t.tc.const_types { filter.add(name) }
|
||||
for name, _ in t.const_suffixes { filter.add(name) }
|
||||
t.const_selector_filter = filter
|
||||
}
|
||||
|
||||
@[direct_array_access; inline]
|
||||
fn (mut filter ConstSelectorNameFilter) add(name string) {
|
||||
bit := file_import_alias_bit(short_name_view(name))
|
||||
filter.bits[bit >> 6] |= u64(1) << (bit & 63)
|
||||
}
|
||||
|
||||
@[direct_array_access; inline]
|
||||
fn (t &Transformer) const_selector_may_match(name string) bool {
|
||||
if isnil(t.tc) || isnil(t.const_selector_filter)
|
||||
|| t.const_selector_filter.const_count != t.tc.const_types.len
|
||||
|| t.const_selector_filter.suffix_count != t.const_suffixes.len {
|
||||
return true
|
||||
}
|
||||
bit := file_import_alias_bit(short_name_view(name))
|
||||
return t.const_selector_filter.bits[bit >> 6] & (u64(1) << (bit & 63)) != 0
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_const_selector_filter_preserves_qualified_suffix_import_and_builtin_visibility() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
for name in ['main_only', 'shared', 'builtin.shared', 'alpha.value', 'nested.mod.limit',
|
||||
'beta.value', 'module', 'éclair', '@escaped', '_private', 'odd..ending', 'trailing.'] {
|
||||
tc.const_types[name] = types.Type(types.int_)
|
||||
}
|
||||
tc.const_modules['main_only'] = 'main'
|
||||
tc.const_modules['shared'] = 'main'
|
||||
tc.file_imports[file_import_key('one.v', 'dep')] = 'nested.mod'
|
||||
tc.file_imports[file_import_key('two.v', 'dep')] = 'alpha'
|
||||
mut slow := new_transformer(mut a, &tc, map[string]bool{})
|
||||
mut fast := new_transformer(mut a, &tc, map[string]bool{})
|
||||
slow.collect_const_suffixes()
|
||||
fast.collect_const_suffixes()
|
||||
// Suffix keys are a separate authority; custom spellings need not have the
|
||||
// same final segment as the constant key they resolve to.
|
||||
slow.const_suffixes['alternate'] = 'nested.mod.limit'
|
||||
fast.const_suffixes['alternate'] = 'nested.mod.limit'
|
||||
fast.prepare_const_selector_filter()
|
||||
names := ['main_only', 'shared', 'builtin.shared', 'alpha.value', 'nested.mod.limit', 'beta.value',
|
||||
'value', 'mod.limit', 'limit', 'dep.limit', 'dep.value', 'alternate', 'module', 'mXdule',
|
||||
'éclair', '@escaped', '_private', 'odd..ending', 'trailing.', 'missing', 'missing.field',
|
||||
'']
|
||||
for name in names {
|
||||
assert fast.const_type_key(name) == slow.const_type_key(name)
|
||||
for module_name in ['', 'main', 'builtin', 'alpha', 'other'] {
|
||||
for file in ['one.v', 'two.v', 'missing.v'] {
|
||||
assert fast.const_type_key_in_context(name, module_name, file) == slow.const_type_key_in_context(name, module_name, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert fast.const_type_key_in_context('dep.limit', 'main', 'one.v') or { '' } == 'nested.mod.limit'
|
||||
assert fast.const_type_key_in_context('shared', 'alpha', 'one.v') or { '' } == 'builtin.shared'
|
||||
assert fast.const_type_key_in_context('main_only', 'alpha', 'one.v') == none
|
||||
assert fast.const_type_key('alternate') or { '' } == 'nested.mod.limit'
|
||||
assert fast.const_selector_may_match('mXdule')
|
||||
assert !fast.const_selector_may_match('absent_selector_field')
|
||||
}
|
||||
|
||||
fn test_const_selector_filter_preserves_raw_expression_and_local_shadowing() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.const_types['answer'] = types.Type(types.int_)
|
||||
tc.const_types['config.value'] = types.Type(types.string_)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.collect_const_suffixes()
|
||||
answer := a.add_val(.ident, 'answer')
|
||||
base := a.add_val(.ident, 'config')
|
||||
start := a.children.len
|
||||
a.children << base
|
||||
selector := a.add_node(flat.Node{ kind: .selector, value: 'value', children_start: start, children_count: 1 })
|
||||
assert t.raw_const_type_name_for_expr(answer) or { u32(0) } == flat.source_type_int
|
||||
assert t.raw_const_type_name_for_expr(selector) or { u32(0) } == flat.source_type_string
|
||||
t.prepare_const_selector_filter()
|
||||
assert t.raw_const_type_name_for_expr(answer) or { u32(0) } == flat.source_type_int
|
||||
assert t.raw_const_type_name_for_expr(selector) or { u32(0) } == flat.source_type_string
|
||||
t.set_var_type('answer', flat.source_type_bool)
|
||||
assert t.raw_const_type_name_for_expr(answer) == none
|
||||
t.set_var_type('config', flat.source_type_id('Config'))
|
||||
assert t.raw_const_type_name_for_expr(selector) == none
|
||||
missing := a.add_val(.ident, 'absent_selector_field')
|
||||
assert t.raw_const_type_name_for_expr(missing) == none
|
||||
}
|
||||
|
||||
fn test_const_selector_filter_falls_back_when_either_authority_grows_and_rebuilds() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
assert t.const_selector_may_match('later')
|
||||
t.prepare_const_selector_filter()
|
||||
assert !t.const_selector_may_match('later')
|
||||
tc.const_types['later'] = types.Type(types.int_)
|
||||
assert t.const_type_key('later') or { '' } == 'later'
|
||||
t.prepare_const_selector_filter()
|
||||
assert !t.const_selector_may_match('alternate')
|
||||
t.const_suffixes['alternate'] = 'later'
|
||||
assert t.const_type_key('alternate') or { '' } == 'later'
|
||||
t.prepare_const_selector_filter()
|
||||
assert t.const_selector_may_match('alternate')
|
||||
tc.const_types.delete('later')
|
||||
t.const_suffixes = map[string]string{}
|
||||
t.prepare_const_selector_filter()
|
||||
assert !t.const_selector_may_match('later')
|
||||
assert !t.const_selector_may_match('alternate')
|
||||
}
|
||||
|
||||
fn test_const_selector_filter_forks_share_bits_and_mutable_query_buffers_are_read_again() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.const_types['open'] = types.Type(types.int_)
|
||||
tc.const_types['shut'] = types.Type(types.int_)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.prepare_const_selector_filter()
|
||||
worker := t.fork_program_view(&a, &tc, map[string]bool{}, false)
|
||||
scan := t.fork_scan_worker(&tc)
|
||||
assert voidptr(worker.const_selector_filter) == voidptr(t.const_selector_filter)
|
||||
assert voidptr(scan.const_selector_filter) == voidptr(t.const_selector_filter)
|
||||
mut bytes := 'open'.bytes()
|
||||
name := unsafe { tos(bytes.data, bytes.len) }
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert instance.const_type_key(name) or { '' } == 'open'
|
||||
}
|
||||
for i, ch in 'shut'.bytes() { bytes[i] = ch }
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert instance.const_type_key(name) or { '' } == 'shut'
|
||||
}
|
||||
for i, ch in 'zzzz'.bytes() { bytes[i] = ch }
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert instance.const_type_key(name) == none
|
||||
}
|
||||
}
|
||||
|
||||
fn test_const_selector_filter_reads_same_key_value_updates_without_rebuilding() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.const_types['answer'] = types.Type(types.int_)
|
||||
tc.const_types['label'] = types.Type(types.string_)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.collect_const_suffixes()
|
||||
t.const_suffixes['alternate'] = 'answer'
|
||||
t.prepare_const_selector_filter()
|
||||
worker := t.fork_program_view(&a, &tc, map[string]bool{}, false)
|
||||
scan := t.fork_scan_worker(&tc)
|
||||
const_count := tc.const_types.len
|
||||
suffix_count := t.const_suffixes.len
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert instance.const_type_name('answer') or { u32(0) } == flat.source_type_int
|
||||
assert instance.const_type_name('alternate') or { u32(0) } == flat.source_type_int
|
||||
}
|
||||
// Membership is frozen; both authorities still read the current values.
|
||||
tc.const_types['answer'] = types.Type(types.bool_)
|
||||
t.const_suffixes['alternate'] = 'label'
|
||||
t.const_suffixes['answer'] = 'label'
|
||||
assert tc.const_types.len == const_count
|
||||
assert t.const_suffixes.len == suffix_count
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert instance.const_type_name('answer') or { u32(0) } == flat.source_type_bool
|
||||
assert instance.const_type_name('alternate') or { u32(0) } == flat.source_type_string
|
||||
assert instance.const_type_key('answer') or { '' } == 'answer'
|
||||
assert instance.const_type_key_in_context('answer', 'main', '') or { '' } == 'answer'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
module transform
|
||||
|
||||
// The enum table is frozen after preparation. Record only bits, so workers do
|
||||
// not retain source strings or strings owned by a disposable worker arena.
|
||||
struct EnumSelectorNameFilter {
|
||||
mut:
|
||||
count int
|
||||
bits [64]u64
|
||||
}
|
||||
|
||||
fn (mut t Transformer) prepare_enum_selector_filter() {
|
||||
mut filter := &EnumSelectorNameFilter{ count: t.enum_types.len }
|
||||
for name, fields in t.enum_types {
|
||||
// Qualified type selectors may retain several dotted segments in value.
|
||||
// Include every suffix accepted by base + '.' + field_name resolution.
|
||||
filter.add(name)
|
||||
for i, ch in name {
|
||||
if ch == `.` {
|
||||
filter.add(unsafe { name.substr_unsafe(i + 1, name.len) })
|
||||
}
|
||||
}
|
||||
for field in fields { filter.add(field) }
|
||||
}
|
||||
t.enum_selector_filter = filter
|
||||
}
|
||||
|
||||
@[direct_array_access; inline]
|
||||
fn (mut filter EnumSelectorNameFilter) add(name string) {
|
||||
bit := file_import_alias_bit(name)
|
||||
filter.bits[bit >> 6] |= u64(1) << (bit & 63)
|
||||
}
|
||||
|
||||
@[direct_array_access; inline]
|
||||
fn (t &Transformer) enum_selector_may_match(name string) bool {
|
||||
if isnil(t.enum_selector_filter) || t.enum_selector_filter.count != t.enum_types.len {
|
||||
return true
|
||||
}
|
||||
bit := file_import_alias_bit(name)
|
||||
return t.enum_selector_filter.bits[bit >> 6] & (u64(1) << (bit & 63)) != 0
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn enum_filter_selector(mut a flat.FlatAst, base flat.NodeId, name string) flat.Node {
|
||||
start := a.children.len
|
||||
a.children << base
|
||||
return flat.Node{ kind: .selector, value: name, children_start: start, children_count: 1 }
|
||||
}
|
||||
|
||||
fn test_enum_selector_filter_keeps_alias_module_generated_and_regular_value_resolution() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.type_aliases.set_id('Alias', flat.source_type_id('model.Kind'))
|
||||
tc.type_aliases.set_id('NextAlias', flat.source_type_id('Alias'))
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.cur_module = 'model'
|
||||
t.enum_types['model.Kind'] = ['ready', 'waiting']
|
||||
t.enum_types['Kind'] = ['ready', 'waiting']
|
||||
t.enum_types['model.generated'] = ['module', '@escaped', 'éclair']
|
||||
t.enum_types['generated'] = ['module', '@escaped', 'éclair']
|
||||
mut nodes := []flat.Node{}
|
||||
for base in ['Kind', 'Alias', 'NextAlias', 'model.Kind', 'generated'] {
|
||||
base_id := a.add_val(.ident, base)
|
||||
for field in ['ready', 'waiting', 'module', '@escaped', 'éclair', 'mXdule', 'missing'] {
|
||||
nodes << enum_filter_selector(mut a, base_id, field)
|
||||
}
|
||||
}
|
||||
module_id := a.add_val(.ident, 'model')
|
||||
nodes << enum_filter_selector(mut a, module_id, 'Kind')
|
||||
nodes << enum_filter_selector(mut a, module_id, 'generated')
|
||||
mut before := []u32{}
|
||||
for node in nodes { before << t.resolve_selector_type_uncached(node) }
|
||||
assert before[0] == flat.source_type_id('Kind')
|
||||
assert before[7] == flat.source_type_id('model.Kind')
|
||||
assert before[before.len - 2] == flat.source_type_id('model.Kind')
|
||||
assert before.last() == flat.source_type_id('model.generated')
|
||||
t.prepare_enum_selector_filter()
|
||||
for i, node in nodes {
|
||||
assert t.resolve_selector_type_uncached(node) == before[i]
|
||||
}
|
||||
assert file_import_alias_bit('module') == file_import_alias_bit('mXdule')
|
||||
assert t.enum_selector_may_match('mXdule')
|
||||
assert !t.enum_selector_may_match('absent_selector_field')
|
||||
}
|
||||
|
||||
fn test_enum_selector_filter_retains_non_enum_selector_results() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
tc.const_types['Config.limit'] = types.Type(types.int_)
|
||||
t.structs['Record'] = StructInfo{ fields: [FieldInfo{ name: 'amount', typ: flat.source_type_string }] }
|
||||
t.set_var_type('record', flat.source_type_id('Record'))
|
||||
t.set_var_type('items', flat.source_type_id('[]int'))
|
||||
base := a.add_val(.ident, 'items')
|
||||
len_node := enum_filter_selector(mut a, base, 'len')
|
||||
assert t.resolve_selector_type_uncached(len_node) == flat.source_type_int
|
||||
record_id := a.add_val(.ident, 'record')
|
||||
field_node := enum_filter_selector(mut a, record_id, 'amount')
|
||||
config_id := a.add_val(.ident, 'Config')
|
||||
const_node := enum_filter_selector(mut a, config_id, 'limit')
|
||||
assert t.resolve_selector_type_uncached(field_node) == flat.source_type_string
|
||||
assert t.resolve_selector_type_uncached(const_node) == flat.source_type_int
|
||||
t.prepare_enum_selector_filter()
|
||||
assert !t.enum_selector_may_match('len')
|
||||
assert t.resolve_selector_type_uncached(len_node) == flat.source_type_int
|
||||
assert t.resolve_selector_type_uncached(field_node) == flat.source_type_string
|
||||
assert t.resolve_selector_type_uncached(const_node) == flat.source_type_int
|
||||
}
|
||||
|
||||
fn test_enum_selector_filter_rechecks_mutable_query_bytes_and_shares_only_frozen_bits() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.enum_types['Kind'] = ['open', 'shut']
|
||||
t.prepare_enum_selector_filter()
|
||||
worker := t.fork_program_view(&a, &tc, map[string]bool{}, false)
|
||||
scan := t.fork_scan_worker(&tc)
|
||||
assert voidptr(worker.enum_selector_filter) == voidptr(t.enum_selector_filter)
|
||||
assert voidptr(scan.enum_selector_filter) == voidptr(t.enum_selector_filter)
|
||||
mut bytes := 'open'.bytes()
|
||||
// Queries may refer to a reused scratch buffer. The filter records only bits.
|
||||
name := unsafe { tos(bytes.data, bytes.len) }
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert instance.enum_selector_may_match(name)
|
||||
}
|
||||
for i, ch in 'shut'.bytes() { bytes[i] = ch }
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert instance.enum_selector_may_match(name)
|
||||
}
|
||||
for i, ch in 'zzzz'.bytes() { bytes[i] = ch }
|
||||
for instance in [&t, &worker, scan] {
|
||||
assert !instance.enum_selector_may_match(name)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_enum_selector_filter_falls_back_for_new_enums_and_rebuilds_without_stale_bits() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
assert t.enum_selector_may_match('later')
|
||||
t.prepare_enum_selector_filter()
|
||||
assert !t.enum_selector_may_match('later')
|
||||
t.enum_types['Added'] = ['later']
|
||||
assert t.enum_selector_may_match('later')
|
||||
base := a.add_val(.ident, 'Added')
|
||||
node := enum_filter_selector(mut a, base, 'later')
|
||||
assert t.resolve_selector_type_uncached(node) == flat.source_type_id('Added')
|
||||
t.prepare_enum_selector_filter()
|
||||
assert t.enum_selector_may_match('later')
|
||||
t.enum_types = map[string][]string{}
|
||||
t.prepare_enum_selector_filter()
|
||||
assert !t.enum_selector_may_match('later')
|
||||
assert !t.enum_selector_may_match('Added')
|
||||
}
|
||||
|
||||
fn test_enum_selector_filter_preserves_dotted_selector_values() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.enum_types['model.nested.Kind'] = ['ready', 'state.value']
|
||||
module_id := a.add_val(.ident, 'model')
|
||||
enum_id := a.add_val(.ident, 'model.nested.Kind')
|
||||
nodes := [enum_filter_selector(mut a, module_id, 'nested.Kind'),
|
||||
enum_filter_selector(mut a, enum_id, 'state.value')]
|
||||
for node in nodes {
|
||||
assert t.resolve_selector_type_uncached(node) == flat.source_type_id('model.nested.Kind')
|
||||
}
|
||||
t.prepare_enum_selector_filter()
|
||||
for node in nodes {
|
||||
assert t.resolve_selector_type_uncached(node) == flat.source_type_id('model.nested.Kind')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
struct TransparentEscapeState {
|
||||
mut:
|
||||
amp_ptrs map[string]bool
|
||||
amp_sources map[string][]string
|
||||
ptr_aliases map[string]string
|
||||
method_receivers map[string]string
|
||||
capture_aliases map[string][]string
|
||||
interface_boxes map[string]bool
|
||||
returned map[string]bool
|
||||
local_names map[string]bool
|
||||
local_added []string
|
||||
backing_sources map[string][]string
|
||||
}
|
||||
|
||||
fn transparent_escape_node(mut a flat.FlatAst, node flat.Node, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
...node
|
||||
children_start: start
|
||||
children_count: children.len
|
||||
})
|
||||
}
|
||||
|
||||
fn (mut state TransparentEscapeState) scan(mut t Transformer, id flat.NodeId, clear bool) {
|
||||
t.scan_escape_pass(id, mut state.amp_ptrs, mut state.amp_sources, mut state.ptr_aliases,
|
||||
mut state.method_receivers, mut state.capture_aliases, mut state.interface_boxes,
|
||||
mut state.returned, mut state.local_names, mut state.local_added,
|
||||
mut state.backing_sources, clear)
|
||||
}
|
||||
|
||||
fn test_transparent_escape_nodes_preserve_child_order_and_state() {
|
||||
for clear in [false, true] {
|
||||
for kind in [flat.NodeKind.expr_stmt, .paren, .cast_expr, .as_expr, .field_init, .struct_init,
|
||||
.array_literal, .array_init, .map_init, .selector, .index, .prefix, .postfix, .range,
|
||||
.assoc, .assert_stmt, .defer_stmt, .lock_expr, .string_interp, .infix, .or_expr] {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
decl := t.make_decl_assign('alias', t.make_ident('first'))
|
||||
assign := t.make_assign(t.make_ident('alias'), t.make_ident('second'))
|
||||
ret := transparent_escape_node(mut a, flat.Node{ kind: .return_stmt }, [t.make_ident('alias')])
|
||||
nested_assign := t.make_assign(t.make_ident('alias'), t.make_ident('nested'))
|
||||
nested_fn := transparent_escape_node(mut a, flat.Node{ kind: .fn_literal }, [nested_assign])
|
||||
// Stateful descendants expose traversal order independently of wrapper syntax.
|
||||
sequence := transparent_escape_node(mut a, flat.Node{ kind: .infix, op: .plus }, [
|
||||
decl,
|
||||
assign,
|
||||
ret,
|
||||
nested_fn,
|
||||
])
|
||||
wrapper := transparent_escape_node(mut a, flat.Node{ kind: kind, op: .plus }, [sequence])
|
||||
mut state := TransparentEscapeState{}
|
||||
state.method_receivers['alias'] = 'receiver'
|
||||
state.capture_aliases['alias'] = ['captured']
|
||||
state.interface_boxes['alias'] = true
|
||||
state.scan(mut t, wrapper, clear)
|
||||
assert state.ptr_aliases['alias'] == 'second', '${kind}'
|
||||
assert state.local_names['alias'], '${kind}'
|
||||
assert state.local_added == ['alias'], '${kind}'
|
||||
assert state.returned['alias'], '${kind}'
|
||||
assert ('alias' in state.method_receivers) == !clear, '${kind}'
|
||||
assert ('alias' in state.capture_aliases) == !clear, '${kind}'
|
||||
assert ('alias' in state.interface_boxes) == !clear, '${kind}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_transparent_escape_scan_reloads_children_after_reflected_ast_growth() {
|
||||
mut a := flat.FlatAst.new()
|
||||
a.add_val(.module_decl, 'main')
|
||||
receiver := a.add_node(flat.Node{ kind: .param, op: .dot, value: 'self', typ: flat.source_type_id('App') })
|
||||
transparent_escape_node(mut a, flat.Node{ kind: .fn_decl, value: 'App.first', typ: flat.source_type_int }, [receiver])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['App'] = []types.StructField{}
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.cur_module = 'main'
|
||||
body_assign := t.make_assign(t.make_ident('alias'), t.make_ident('reflected'))
|
||||
body := t.make_block([body_assign])
|
||||
loop := transparent_escape_node(mut a, flat.Node{
|
||||
kind: .comptime_for
|
||||
value: 'method|methods'
|
||||
typ: flat.source_type_id('App')
|
||||
}, [body])
|
||||
later := t.make_decl_assign('after', t.make_ident('alias'))
|
||||
wrapper := transparent_escape_node(mut a, flat.Node{ kind: .array_literal }, [
|
||||
loop,
|
||||
later,
|
||||
])
|
||||
// Tight buffers force the reflected analysis clones to grow both arrays.
|
||||
a.nodes = []flat.Node{len: a.nodes.len, cap: a.nodes.len, init: a.nodes[index]}
|
||||
a.children = []flat.NodeId{len: a.children.len, cap: a.children.len, init: a.children[index]}
|
||||
nodes_before := a.nodes.clone()
|
||||
children_before := a.children.clone()
|
||||
nodes_cap := a.nodes.cap
|
||||
children_cap := a.children.cap
|
||||
mut state := TransparentEscapeState{}
|
||||
state.scan(mut t, wrapper, true)
|
||||
assert state.ptr_aliases['alias'] == 'reflected'
|
||||
assert state.ptr_aliases['after'] == 'alias'
|
||||
assert state.local_names['after']
|
||||
assert a.nodes.cap > nodes_cap
|
||||
assert a.children.cap > children_cap
|
||||
assert a.nodes == nodes_before
|
||||
assert a.children == children_before
|
||||
}
|
||||
+103
-85
@@ -1081,6 +1081,9 @@ fn (t &Transformer) raw_const_type_name_for_expr(id flat.NodeId) ?u32 {
|
||||
return none
|
||||
}
|
||||
node := t.a.nodes[int(id)]
|
||||
if node.kind in [.ident, .selector] && !t.const_selector_may_match(node.value) {
|
||||
return none
|
||||
}
|
||||
if t.selector_const_base_is_value(node) {
|
||||
return none
|
||||
}
|
||||
@@ -1221,13 +1224,10 @@ fn (t &Transformer) generic_call_base_is_fn_value(base_id flat.NodeId, base flat
|
||||
|| t.is_known_type_name(base.source_value_id()) {
|
||||
return false
|
||||
}
|
||||
for candidate in [t.raw_var_type(base.value), t.var_type(base.value), base.typ,
|
||||
t.node_type(base_id)] {
|
||||
if t.is_fn_pointer_type_name(candidate) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return t.is_fn_pointer_type_name(t.raw_var_type(base.value))
|
||||
|| t.is_fn_pointer_type_name(t.var_type(base.value))
|
||||
|| t.is_fn_pointer_type_name(base.typ)
|
||||
|| t.is_fn_pointer_type_name(t.node_type(base_id))
|
||||
}
|
||||
|
||||
// generic_call_type_arg_name supports generic call type arg name handling for Transformer.
|
||||
@@ -3473,7 +3473,7 @@ fn (mut t Transformer) call_param_types_for_node(call_name string, node flat.Nod
|
||||
}
|
||||
if node.children_count > 0 {
|
||||
fn_id := t.a.child(&node, 0)
|
||||
fn_node := t.a.child_node(&node, 0)
|
||||
fn_node := t.a.node(t.a.child(&node, 0))
|
||||
if fn_node.kind == .ident && (t.var_type(fn_node.value)) != 0 {
|
||||
if fn_type := t.call_callee_fn_type(fn_id) {
|
||||
return fn_type.params.map(*it.typ)
|
||||
@@ -3535,12 +3535,16 @@ fn (t &Transformer) call_callee_fn_type(fn_id flat.NodeId) ?types.FnType {
|
||||
}
|
||||
node := t.a.nodes[int(fn_id)]
|
||||
if node.kind == .ident {
|
||||
for local_type in [flat.source_type_name(t.raw_var_type(node.value)),
|
||||
flat.source_type_name(t.var_type(node.value))] {
|
||||
if local_type.len == 0 {
|
||||
for candidate in 0 .. 2 {
|
||||
local_type := if candidate == 0 {
|
||||
t.raw_var_type(node.value)
|
||||
} else {
|
||||
t.var_type(node.value)
|
||||
}
|
||||
if local_type == 0 {
|
||||
continue
|
||||
}
|
||||
if fn_type := transform_fn_type(t.tc.parse_type(flat.source_type_id(local_type))) {
|
||||
if fn_type := transform_fn_type(t.tc.parse_type(local_type)) {
|
||||
return fn_type
|
||||
}
|
||||
}
|
||||
@@ -5544,10 +5548,10 @@ fn (t &Transformer) transform_param_type_is_void_pointer(type_name u32) bool {
|
||||
}
|
||||
|
||||
fn (t &Transformer) call_arg_is_fn_pointer_value(arg_id flat.NodeId, arg_node flat.Node) bool {
|
||||
for type_name in [arg_node.typ, t.node_type(arg_id), t.resolve_expr_type(arg_id)] {
|
||||
if t.is_fn_pointer_type_name(type_name) {
|
||||
return true
|
||||
}
|
||||
if t.is_fn_pointer_type_name(arg_node.typ)
|
||||
|| t.is_fn_pointer_type_name(t.node_type(arg_id))
|
||||
|| t.is_fn_pointer_type_name(t.resolve_expr_type(arg_id)) {
|
||||
return true
|
||||
}
|
||||
if arg_node.kind == .ident {
|
||||
type_name_source_id := t.var_type(arg_node.value)
|
||||
@@ -14433,78 +14437,87 @@ fn (mut t Transformer) try_lower_builtin_call(_id flat.NodeId, node flat.Node) ?
|
||||
if named_cast_call := t.try_lower_generic_named_type_cast_call(node) {
|
||||
return named_cast_call
|
||||
}
|
||||
if flag_call := t.try_lower_flag_enum_call(_id, node) {
|
||||
return flag_call
|
||||
selector_callee := t.a.child_node(&node, 0)
|
||||
has_selector_callee := selector_callee.kind == .selector && selector_callee.children_count > 0
|
||||
if has_selector_callee {
|
||||
if flag_call := t.try_lower_flag_enum_call(_id, node) {
|
||||
return flag_call
|
||||
}
|
||||
}
|
||||
if flag_default := t.try_lower_flag_default_value_call(node) {
|
||||
return flag_default
|
||||
}
|
||||
if pool_call := t.try_lower_pool_generic_method_call(node) {
|
||||
return pool_call
|
||||
if has_selector_callee {
|
||||
if pool_call := t.try_lower_pool_generic_method_call(node) {
|
||||
return pool_call
|
||||
}
|
||||
}
|
||||
if static_call := t.try_lower_static_assoc_call(_id, node) {
|
||||
return static_call
|
||||
}
|
||||
if move_call := t.try_lower_move_method_call(_id, node) {
|
||||
return move_call
|
||||
}
|
||||
if channel_call := t.try_lower_channel_method_call(_id, node) {
|
||||
return channel_call
|
||||
}
|
||||
if map_call := t.try_lower_map_method_call(_id, node) {
|
||||
return map_call
|
||||
}
|
||||
if pointer_str_call := t.try_lower_pointer_str_method_call(_id, node) {
|
||||
return pointer_str_call
|
||||
}
|
||||
if array_call := t.try_lower_array_method_call(_id, node) {
|
||||
return array_call
|
||||
}
|
||||
if smartcast_receiver_call := t.try_lower_smartcast_target_receiver_method_call(_id, node) {
|
||||
return smartcast_receiver_call
|
||||
}
|
||||
if type_name_call := t.try_lower_sum_type_name_method_call(node) {
|
||||
return type_name_call
|
||||
}
|
||||
if iface_runtime_call := t.try_lower_interface_runtime_method_call(node) {
|
||||
return iface_runtime_call
|
||||
}
|
||||
if !t.validate_specialized_enum_from_call(_id, node) {
|
||||
return t.make_empty()
|
||||
}
|
||||
specialized_enum_type := (if t.validating_generic_spec {
|
||||
(t.builtin_enum_from_type(_id, node) or { u32(0) })
|
||||
} else {
|
||||
u32(0)
|
||||
})
|
||||
if enum_call := t.try_lower_enum_from_string_call(_id, node) {
|
||||
return enum_call
|
||||
}
|
||||
if specialized_enum_type != 0 {
|
||||
return t.lower_specialized_enum_from_call(node, specialized_enum_type)
|
||||
}
|
||||
if node.children_count > 0 {
|
||||
string_callee := t.a.child_node(&node, 0)
|
||||
if string_callee.kind == .selector && string_callee.children_count > 0 {
|
||||
if folded := t.try_fold_comptime_string_method(node, string_callee) {
|
||||
return folded
|
||||
}
|
||||
if folded := t.try_fold_literal_string_call(node, string_callee) {
|
||||
return folded
|
||||
method_callee := t.a.child_node(&node, 0)
|
||||
if method_callee.kind == .selector && method_callee.children_count > 0 {
|
||||
if move_call := t.try_lower_move_method_call(_id, node) {
|
||||
return move_call
|
||||
}
|
||||
if channel_call := t.try_lower_channel_method_call(_id, node) {
|
||||
return channel_call
|
||||
}
|
||||
if map_call := t.try_lower_map_method_call(_id, node) {
|
||||
return map_call
|
||||
}
|
||||
if pointer_str_call := t.try_lower_pointer_str_method_call(_id, node) {
|
||||
return pointer_str_call
|
||||
}
|
||||
if array_call := t.try_lower_array_method_call(_id, node) {
|
||||
return array_call
|
||||
}
|
||||
if smartcast_receiver_call := t.try_lower_smartcast_target_receiver_method_call(_id, node) {
|
||||
return smartcast_receiver_call
|
||||
}
|
||||
if type_name_call := t.try_lower_sum_type_name_method_call(node) {
|
||||
return type_name_call
|
||||
}
|
||||
if iface_runtime_call := t.try_lower_interface_runtime_method_call(node) {
|
||||
return iface_runtime_call
|
||||
}
|
||||
if !t.validate_specialized_enum_from_call(_id, node) {
|
||||
return t.make_empty()
|
||||
}
|
||||
specialized_enum_type := (if t.validating_generic_spec {
|
||||
(t.builtin_enum_from_type(_id, node) or { u32(0) })
|
||||
} else {
|
||||
u32(0)
|
||||
})
|
||||
if enum_call := t.try_lower_enum_from_string_call(_id, node) {
|
||||
return enum_call
|
||||
}
|
||||
if specialized_enum_type != 0 {
|
||||
return t.lower_specialized_enum_from_call(node, specialized_enum_type)
|
||||
}
|
||||
if node.children_count > 0 {
|
||||
string_callee := t.a.child_node(&node, 0)
|
||||
if string_callee.kind == .selector && string_callee.children_count > 0 {
|
||||
if folded := t.try_fold_comptime_string_method(node, string_callee) {
|
||||
return folded
|
||||
}
|
||||
if folded := t.try_fold_literal_string_call(node, string_callee) {
|
||||
return folded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if free_call := t.try_lower_owned_string_free_call(_id, node) {
|
||||
return free_call
|
||||
}
|
||||
if receiver_call := t.try_lower_receiver_method_call(_id, node) {
|
||||
return receiver_call
|
||||
}
|
||||
if clone_call := t.try_lower_struct_clone_method_call(_id, node) {
|
||||
return clone_call
|
||||
}
|
||||
if string_call := t.try_lower_string_method_call(node) {
|
||||
return string_call
|
||||
if free_call := t.try_lower_owned_string_free_call(_id, node) {
|
||||
return free_call
|
||||
}
|
||||
if receiver_call := t.try_lower_receiver_method_call(_id, node) {
|
||||
return receiver_call
|
||||
}
|
||||
if clone_call := t.try_lower_struct_clone_method_call(_id, node) {
|
||||
return clone_call
|
||||
}
|
||||
if string_call := t.try_lower_string_method_call(node) {
|
||||
return string_call
|
||||
}
|
||||
}
|
||||
fn_id := t.a.children[node.children_start]
|
||||
if int(fn_id) < 0 {
|
||||
@@ -17924,8 +17937,13 @@ fn (mut t Transformer) receiver_base_for_resolved_method(base_id flat.NodeId, me
|
||||
method_iface_comparison_type_id := method_iface_source_id
|
||||
|
||||
key := t.expr_key(base_id)
|
||||
for source_type in [t.raw_var_type_for_expr(base_id) or { u32(0) }, t.original_expr_type(base_id),
|
||||
t.node_type(base_id)] {
|
||||
// Resolve later views only when the preceding receiver type did not match.
|
||||
for candidate in 0 .. 3 {
|
||||
source_type := match candidate {
|
||||
0 { t.raw_var_type_for_expr(base_id) or { u32(0) } }
|
||||
1 { t.original_expr_type(base_id) }
|
||||
else { t.node_type(base_id) }
|
||||
}
|
||||
clean_source_source_id := t.trim_pointer_type(source_type)
|
||||
|
||||
if clean_source_source_id != 0 && method_receiver_source_id != 0
|
||||
@@ -18335,7 +18353,7 @@ fn (t &Transformer) explicit_generic_call_arg_ids(node flat.Node) []u32 {
|
||||
if node.children_count == 0 {
|
||||
return []u32{}
|
||||
}
|
||||
fn_node := t.a.child_node(&node, 0)
|
||||
fn_node := t.a.node(t.a.child(&node, 0))
|
||||
if fn_node.kind != .index || fn_node.children_count < 2 || fn_node.value == 'range' {
|
||||
return []u32{}
|
||||
}
|
||||
@@ -19675,7 +19693,7 @@ fn (t &Transformer) get_call_return_type(id flat.NodeId, node flat.Node) u32 {
|
||||
if !isnil(t.tc) && t.a.has_vsh_source && t.cur_module == 'main'
|
||||
&& t.cur_file.ends_with('.vsh')
|
||||
&& node.children_count > 0 {
|
||||
callee := t.a.child_node(&node, 0)
|
||||
callee := t.a.node(t.a.child(&node, 0))
|
||||
if callee.kind == .ident {
|
||||
selected := t.resolve_call_name(node)
|
||||
if selected != callee.value {
|
||||
@@ -19689,7 +19707,7 @@ fn (t &Transformer) get_call_return_type(id flat.NodeId, node flat.Node) u32 {
|
||||
return ret_source_id
|
||||
}
|
||||
if node.children_count > 0 {
|
||||
fn_node := t.a.child_node(&node, 0)
|
||||
fn_node := t.a.node(t.a.child(&node, 0))
|
||||
if fn_node.kind == .selector && fn_node.children_count > 0 {
|
||||
receiver_type_id := t.trim_pointer_type(t.node_type(t.a.child(fn_node, 0)))
|
||||
if args_source_ids := t.current_specialized_receiver_args(receiver_type_id) {
|
||||
@@ -19731,7 +19749,7 @@ fn (t &Transformer) get_call_return_type(id flat.NodeId, node flat.Node) u32 {
|
||||
}
|
||||
}
|
||||
if node.children_count > 0 {
|
||||
fn_node := t.a.child_node(&node, 0)
|
||||
fn_node := t.a.node(t.a.child(&node, 0))
|
||||
if fn_node.kind == .ident && (t.var_type(fn_node.value)) == 0 {
|
||||
if ret_source_id := t.local_fn_decl_return_type(fn_node.value) {
|
||||
return t.call_return_type_name(ret_source_id, node)
|
||||
@@ -19742,7 +19760,7 @@ fn (t &Transformer) get_call_return_type(id flat.NodeId, node flat.Node) u32 {
|
||||
return ret_source_id
|
||||
}
|
||||
if node.children_count > 0 {
|
||||
fn_node := t.a.child_node(&node, 0)
|
||||
fn_node := t.a.node(t.a.child(&node, 0))
|
||||
if fn_node.kind == .ident {
|
||||
local_type_source_id := t.var_type(fn_node.value)
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_fresh_ident_keeps_existing_body_memo_and_binding_type() {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'items')
|
||||
index := a.add_val(.int_literal, '0')
|
||||
start := a.begin_children()
|
||||
a.add_child(base)
|
||||
a.add_child(index)
|
||||
lookup := a.add_node(flat.Node{
|
||||
kind: .index
|
||||
children_start: i32(start)
|
||||
children_count: 2
|
||||
})
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.set_var_type('items', flat.source_type_id('[]string'))
|
||||
t.begin_node_type_memo(0, a.nodes.len - 1)
|
||||
assert t.node_type(lookup) == flat.source_type_string
|
||||
generation := t.node_type_memo.generation
|
||||
filled := t.node_type_memo.filled[int(lookup)]
|
||||
for name in ['items', 'unbound'] {
|
||||
prior_len := a.nodes.len
|
||||
if name == 'items' {
|
||||
tc.expr_type_set = []bool{len: prior_len + 1}
|
||||
tc.expr_type_set[prior_len] = true
|
||||
}
|
||||
fresh := t.make_ident(name)
|
||||
assert int(fresh) == prior_len
|
||||
node := a.node(fresh)
|
||||
if name == 'items' {
|
||||
assert !tc.expr_type_set[int(fresh)]
|
||||
}
|
||||
assert node.kind == .ident
|
||||
assert node.value == name
|
||||
assert node.typ == if name == 'items' { flat.source_type_id('[]string') } else { u32(0) }
|
||||
assert t.node_type_memo.generation == generation
|
||||
assert t.node_type_memo.filled[int(lookup)] == filled
|
||||
assert t.node_type(lookup) == flat.source_type_string
|
||||
}
|
||||
// Real writes to an attached descendant still invalidate its cached ancestor.
|
||||
t.set_var_type('other', flat.source_type_id('[]bool'))
|
||||
t.set_node_value(int(base), 'other')
|
||||
assert t.node_type(lookup) == flat.source_type_bool
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn generic_snapshot_node(mut a flat.FlatAst, template flat.Node, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
...template
|
||||
children_start: start
|
||||
children_count: flat.child_count(children.len)
|
||||
})
|
||||
}
|
||||
|
||||
fn test_generic_call_arguments_read_snapshot_after_ast_growth() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'convert')
|
||||
int_arg := a.add_val(.ident, 'int')
|
||||
string_arg := a.add_val(.ident, 'string')
|
||||
index := generic_snapshot_node(mut a, flat.Node{ kind: .index }, [callee, int_arg, string_arg])
|
||||
call := generic_snapshot_node(mut a, flat.Node{ kind: .call }, [index])
|
||||
snapshot := a.nodes[int(call)]
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
for _ in 0 .. a.nodes.cap + 100 {
|
||||
a.add_val(.ident, 'growth')
|
||||
a.children << flat.empty_node
|
||||
}
|
||||
assert t.generic_call_type_args(a.nodes[int(a.child(&snapshot, 0))]) == [
|
||||
flat.source_type_int,
|
||||
flat.source_type_string,
|
||||
]
|
||||
assert t.explicit_generic_call_args(snapshot, 'main') or { panic('missing type arguments') } == [
|
||||
flat.source_type_int,
|
||||
flat.source_type_string,
|
||||
]
|
||||
mut stored := flat.Node{ kind: .call }
|
||||
stored.set_call_type_args([flat.source_type_u64])
|
||||
assert stored.value_text() == 'u64'
|
||||
assert t.explicit_generic_call_args(stored, 'main') or { panic('missing stored type argument') } == [flat.source_type_u64]
|
||||
assert (flat.Node{ kind: .call }).value_text() == ''
|
||||
assert t.explicit_generic_call_args(flat.Node{ kind: .call }, 'main') == none
|
||||
a.nodes[int(index)].value = 'range'
|
||||
assert t.generic_call_type_args(a.nodes[int(a.child(&snapshot, 0))]).len == 0
|
||||
assert t.explicit_generic_call_args(snapshot, 'main') == none
|
||||
}
|
||||
|
||||
fn test_orm_fields_keep_order_duplicates_and_reassignment_behavior() {
|
||||
mut a := flat.FlatAst.new()
|
||||
first := a.add_val(.field_init, 'first')
|
||||
second := a.add_val(.field_init, 'second')
|
||||
ignored := a.add_val(.ident, 'ignored')
|
||||
init := generic_snapshot_node(mut a, flat.Node{ kind: .struct_init, value: 'Record' }, [
|
||||
first,
|
||||
ignored,
|
||||
second,
|
||||
first,
|
||||
])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.record_orm_initialized_fields('record', init)
|
||||
assert t.orm_initialized_fields['record'] == ['first', 'second']
|
||||
t.record_orm_initialized_fields('', init)
|
||||
assert t.orm_initialized_fields.len == 1
|
||||
t.record_orm_initialized_fields('record', flat.empty_node)
|
||||
assert t.orm_initialized_fields['record'] == ['first', 'second']
|
||||
t.record_orm_initialized_fields('record', ignored)
|
||||
assert 'record' !in t.orm_initialized_fields
|
||||
}
|
||||
|
||||
fn test_collected_declarations_preserve_field_children_and_enum_values() {
|
||||
mut a := flat.FlatAst.new()
|
||||
a.add_val(.module_decl, 'model')
|
||||
default_value := a.add_val(.int_literal, '42')
|
||||
field := generic_snapshot_node(mut a, flat.Node{ kind: .field_decl, value: 'value', typ: flat.source_type_int }, [default_value])
|
||||
generic_snapshot_node(mut a, flat.Node{ kind: .struct_decl, value: 'Record' }, [field])
|
||||
variant := a.add_val(.ident, 'int')
|
||||
generic_snapshot_node(mut a, flat.Node{ kind: .type_decl, value: 'Choice' }, [variant])
|
||||
first := generic_snapshot_node(mut a, flat.Node{ kind: .enum_field, value: 'first' }, [default_value])
|
||||
second := a.add_val(.enum_field, 'second')
|
||||
generic_snapshot_node(mut a, flat.Node{ kind: .enum_decl, value: 'Kind' }, [first, second])
|
||||
global := a.add_node(flat.Node{ kind: .ident, value: 'counter', typ: flat.source_type_i64 })
|
||||
generic_snapshot_node(mut a, flat.Node{ kind: .global_decl }, [global])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.collect_types()
|
||||
info := t.structs['model.Record'] or { panic('missing record') }
|
||||
assert info.fields.len == 1
|
||||
assert info.fields[0].name == 'value'
|
||||
assert info.fields[0].typ == flat.source_type_int
|
||||
assert info.fields[0].default_expr == default_value
|
||||
assert t.sum_types.type_ids('model.Choice') or { panic('missing choice') } == [flat.source_type_int]
|
||||
assert t.enum_types['model.Kind'] == ['first', 'second']
|
||||
assert t.enum_explicit_first_values['model.Kind']
|
||||
assert t.globals.type_id('model.counter') or { panic('missing global') } == flat.source_type_i64
|
||||
}
|
||||
|
||||
fn test_collected_generic_sum_preserves_payload_parameter_spelling() {
|
||||
mut a := flat.FlatAst.new()
|
||||
a.add_val(.module_decl, 'model')
|
||||
generic := a.add_val(.ident, 'T')
|
||||
concrete := a.add_val(.ident, 'Item')
|
||||
pointer := a.add_val(.ident, '&T')
|
||||
generic_snapshot_node(mut a, flat.Node{
|
||||
kind: .type_decl
|
||||
value: 'GenericChoice'
|
||||
payload: flat.node_payload(['T'])
|
||||
}, [generic, concrete, pointer])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.collect_types()
|
||||
variants := t.sum_types.type_ids('model.GenericChoice') or { panic('missing generic choice') }
|
||||
assert variants.map(flat.source_type_name(it)) == ['T', 'model.Item', '&T']
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_callee_signature_keeps_raw_binding_precedence_and_checked_fallback() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callback := a.add_val(.ident, 'callback')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.type_aliases.set_id('RawCallback', flat.source_type_id('fn () string'))
|
||||
tc.register_synth_type(callback, tc.parse_type(flat.source_type_id('fn () bool')))
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.set_var_type_with_raw('callback', flat.source_type_id('fn () int'), flat.source_type_id('RawCallback'))
|
||||
raw_signature := t.call_callee_fn_type(callback) or { panic('missing raw callback') }
|
||||
assert raw_signature.return_type is types.String
|
||||
// An unusable raw spelling falls through to the live binding, then the checker.
|
||||
t.set_var_type_with_raw('callback', flat.source_type_id('fn () int'), flat.source_type_int)
|
||||
live_signature := t.call_callee_fn_type(callback) or { panic('missing live callback') }
|
||||
assert types.semantic_types_equal(*live_signature.return_type, types.Type(types.int_))
|
||||
t.unset_var_type('callback')
|
||||
checked_signature := t.call_callee_fn_type(callback) or { panic('missing checked callback') }
|
||||
assert types.semantic_types_equal(*checked_signature.return_type, types.Type(types.bool_))
|
||||
}
|
||||
|
||||
fn test_generic_callback_detection_keeps_each_type_fallback() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callback := a.add_val(.ident, 'callback')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
callback_type := flat.source_type_id('fn (int) int')
|
||||
t.set_var_type_with_raw('callback', flat.source_type_int, callback_type)
|
||||
assert t.generic_call_base_is_fn_value(callback, a.node(callback))
|
||||
t.set_var_type_with_raw('callback', callback_type, flat.source_type_int)
|
||||
assert t.generic_call_base_is_fn_value(callback, a.node(callback))
|
||||
t.unset_var_type('callback')
|
||||
t.set_node_typ(int(callback), callback_type)
|
||||
assert t.generic_call_base_is_fn_value(callback, a.node(callback))
|
||||
t.set_node_typ(int(callback), 0)
|
||||
tc.register_synth_type(callback, tc.parse_type(callback_type))
|
||||
assert t.generic_call_base_is_fn_value(callback, a.node(callback))
|
||||
other := a.add_val(.ident, 'ordinary')
|
||||
t.set_var_type('ordinary', flat.source_type_int)
|
||||
assert !t.generic_call_base_is_fn_value(other, a.node(other))
|
||||
}
|
||||
|
||||
fn test_known_callback_views_do_not_resolve_later_type_candidates() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callback := a.add_node(flat.Node{
|
||||
kind: .ident
|
||||
value: 'callback'
|
||||
typ: flat.source_type_id('fn () int')
|
||||
})
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.begin_node_type_memo(0, int(callback))
|
||||
assert t.call_arg_is_fn_pointer_value(callback, a.node(callback))
|
||||
assert t.node_type_memo.filled[int(callback)] == 0
|
||||
t.set_var_type('callback', flat.source_type_id('fn () string'))
|
||||
assert t.generic_call_base_is_fn_value(callback, a.node(callback))
|
||||
assert t.node_type_memo.filled[int(callback)] == 0
|
||||
}
|
||||
|
||||
fn test_resolved_receiver_keeps_raw_alias_and_pointer_views() {
|
||||
for spelling in ['Thing', '&Thing', 'AliasThing'] {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_node(flat.Node{ kind: .ident, value: 'object', typ: flat.source_type_id(spelling) })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.type_aliases.set_id('AliasThing', flat.source_type_id('Thing'))
|
||||
tc.structs['Thing'] = []types.StructField{}
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.structs['Thing'] = StructInfo{ name: flat.source_type_id('Thing') }
|
||||
t.set_var_type_with_raw('object', flat.source_type_id(spelling), flat.source_type_id(spelling))
|
||||
result := t.receiver_base_for_resolved_method(base, 'Thing.run', [], flat.source_type_id(spelling))
|
||||
assert a.node(result).kind == .ident
|
||||
assert a.node(result).value == 'object'
|
||||
assert t.expected_expr_type == 0
|
||||
assert t.pending_stmts.len == 0
|
||||
}
|
||||
}
|
||||
@@ -6540,7 +6540,7 @@ fn (mut t Transformer) concrete_generic_call_param_types(id flat.NodeId, node fl
|
||||
}
|
||||
decl_key := t.generic_call_decl_key(id, node, t.cur_module, decls) or { return none }
|
||||
decl := decls[decl_key] or { return none }
|
||||
callee := t.a.child_node(&node, 0)
|
||||
callee := t.a.node(t.a.child(&node, 0))
|
||||
mut args := []u32{}
|
||||
if callee.kind == .ident {
|
||||
if exact_source_ids := t.recorded_generic_specialization_args(callee.value_text()) {
|
||||
@@ -6571,7 +6571,7 @@ fn (mut t Transformer) concrete_generic_call_param_types(id flat.NodeId, node fl
|
||||
params := t.generic_fn_param_names(decl.node, decl.module)
|
||||
mut result := []types.Type{}
|
||||
for i in 0 .. decl.node.children_count {
|
||||
child := t.a.child_node(&decl.node, i)
|
||||
child := t.a.node(t.a.child(&decl.node, i))
|
||||
if child.kind != .param {
|
||||
if t.prefix_param_scan {
|
||||
break
|
||||
@@ -9357,7 +9357,7 @@ fn (t &Transformer) explicit_generic_call_args(node flat.Node, module_name strin
|
||||
if node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
fn_node := t.a.child_node(&node, 0)
|
||||
fn_node := t.a.node(t.a.child(&node, 0))
|
||||
if fn_node.kind != .index || fn_node.children_count < 2 || fn_node.value == 'range' {
|
||||
return none
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_terminal_node_types_ignore_annotations_and_checker_guesses() {
|
||||
mut a := flat.FlatAst.new()
|
||||
kinds := [flat.NodeKind.bool_literal, .float_literal, .int_literal, .string_literal,
|
||||
.string_interp, .nil_literal, .char_literal, .sizeof_expr, .offsetof_expr]
|
||||
expected := [flat.source_type_bool, flat.source_type_f64, flat.source_type_int,
|
||||
flat.source_type_string, flat.source_type_string, flat.source_type_voidptr,
|
||||
flat.source_type_rune, flat.source_type_u32, flat.source_type_u32]
|
||||
for kind in kinds {
|
||||
a.add_node(flat.Node{ kind: kind, value: 'value', typ: flat.source_type_id('Guess') })
|
||||
}
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.structs['Guess'] = []types.StructField{}
|
||||
for i in 0 .. kinds.len {
|
||||
tc.register_synth_type(flat.NodeId(i), types.Type(types.Struct{ name_id: flat.source_type_id('Guess') }))
|
||||
}
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.begin_node_type_memo(0, a.nodes.len - 1)
|
||||
for building_v in [false, true] {
|
||||
t.building_v = building_v
|
||||
for skip_generics in [false, true] {
|
||||
t.skip_generics = skip_generics
|
||||
for i, result in expected {
|
||||
id := flat.NodeId(i)
|
||||
assert t.node_type_uncached(id) == result, '${kinds[i]}'
|
||||
assert t.node_type(id) == result, '${kinds[i]}'
|
||||
}
|
||||
}
|
||||
}
|
||||
assert t.node_type(flat.NodeId(-1)) == 0
|
||||
}
|
||||
|
||||
fn test_terminal_queries_preserve_contextual_resolution_and_rewrites() {
|
||||
mut a := flat.FlatAst.new()
|
||||
value := a.add_val(.ident, 'value')
|
||||
character := a.add_val(.char_literal, 'c:hello')
|
||||
optional := a.add_node(flat.Node{ kind: .none_expr, typ: flat.source_type_id('?string') })
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.type_aliases.set_id('one.Item', flat.source_type_int)
|
||||
tc.type_aliases.set_id('two.Item', flat.source_type_bool)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.cur_module = 'one'
|
||||
t.set_var_type('value', flat.source_type_id('Item'))
|
||||
t.begin_node_type_memo(0, a.nodes.len - 1)
|
||||
assert t.node_type(value) == flat.source_type_int
|
||||
assert t.node_type(character) == flat.source_type_id('&u8')
|
||||
t.cur_module = 'two'
|
||||
t.expected_expr_type = flat.source_type_string
|
||||
t.in_const_init = true
|
||||
assert t.node_type(character) == flat.source_type_id('&u8')
|
||||
// A terminal query can defer the memo context check; the next contextual
|
||||
// query must still observe the new module and resolve its alias again.
|
||||
assert t.node_type(value) == flat.source_type_bool
|
||||
t.set_node_value(int(character), 'hello')
|
||||
assert t.node_type(character) == flat.source_type_rune
|
||||
// `none` retains the annotation fallback for its unresolved `?void` type.
|
||||
assert t.node_type(optional) == flat.source_type_id('?string')
|
||||
a.nodes[int(character)].kind = .ident
|
||||
t.set_node_value(int(character), 'value')
|
||||
assert t.node_type(character) == flat.source_type_bool
|
||||
}
|
||||
|
||||
fn test_boolean_operator_node_types_preserve_operand_count_guards() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.ident, 'left')
|
||||
right := a.add_val(.ident, 'right')
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
for op in [flat.Op.eq, .ne, .lt, .gt, .le, .ge, .logical_and, .logical_or] {
|
||||
start := a.begin_children()
|
||||
a.add_child(left)
|
||||
a.add_child(right)
|
||||
id := a.add_node(flat.Node{
|
||||
kind: .infix
|
||||
op: op
|
||||
typ: flat.source_type_f64
|
||||
children_start: i32(start)
|
||||
children_count: 2
|
||||
})
|
||||
assert t.node_type_uncached(id) == flat.source_type_bool
|
||||
assert t.node_type(id) == flat.source_type_bool
|
||||
// Incomplete expressions retain the annotation fallback.
|
||||
a.nodes[int(id)].children_count = 1
|
||||
assert t.node_type(id) == flat.source_type_f64
|
||||
}
|
||||
start := a.begin_children()
|
||||
a.add_child(left)
|
||||
negated := a.add_node(flat.Node{
|
||||
kind: .prefix
|
||||
op: .not
|
||||
typ: flat.source_type_f64
|
||||
children_start: i32(start)
|
||||
children_count: 1
|
||||
})
|
||||
assert t.node_type_uncached(negated) == flat.source_type_bool
|
||||
assert t.node_type(negated) == flat.source_type_bool
|
||||
a.nodes[int(negated)].children_count = 0
|
||||
assert t.node_type(negated) == flat.source_type_f64
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
// EscapeCallStorageBorrow holds a call's already-proven storage-only summary.
|
||||
// The borrowed slice belongs to the transformer's immutable summary table.
|
||||
struct EscapeCallStorageBorrow {
|
||||
summary []bool
|
||||
offset int
|
||||
}
|
||||
|
||||
fn (b EscapeCallStorageBorrow) borrows(child_index int) bool {
|
||||
if child_index == 0 && b.offset != 1 {
|
||||
return false
|
||||
}
|
||||
index := if child_index == 0 { 0 } else { child_index - 1 + b.offset }
|
||||
return index >= 0 && index < b.summary.len && b.summary[index]
|
||||
}
|
||||
|
||||
// escape_call_storage_borrow uses the existing storage-only proof for an
|
||||
// exactly aligned direct call. It does not prove that the value's payload is
|
||||
// unrelated to the result: aggregate copies can carry pointers elsewhere.
|
||||
fn (t &Transformer) escape_call_storage_borrow(id flat.NodeId, call flat.Node) EscapeCallStorageBorrow {
|
||||
if isnil(t.tc) || call.children_count == 0 {
|
||||
return EscapeCallStorageBorrow{}
|
||||
}
|
||||
callee := t.a.child_node(&call, 0)
|
||||
// Wrapped generic/function-value callees need an independent identity proof.
|
||||
if callee.kind !in [.ident, .selector] {
|
||||
return EscapeCallStorageBorrow{}
|
||||
}
|
||||
name := t.call_name_for_node(id, call)
|
||||
decl := t.fixed_array_call_param_ref(name, call, 0) or { return EscapeCallStorageBorrow{} }
|
||||
summary := t.fixed_array_borrow_params[decl.decl] or { return EscapeCallStorageBorrow{} }
|
||||
params := t.call_param_types_decl_cache[decl.decl] or { return EscapeCallStorageBorrow{} }
|
||||
offset := t.call_param_offset(name, call, params)
|
||||
// Optional/context arguments and named or spread forms need their own ABI
|
||||
// alignment proof. Leave those calls conservative.
|
||||
if call.children_count - 1 != params.len - offset {
|
||||
return EscapeCallStorageBorrow{}
|
||||
}
|
||||
for i in 1 .. call.children_count {
|
||||
arg := t.a.node(t.a.child(&call, i))
|
||||
if arg.kind == .field_init || t.call_arg_is_spread(t.a.child(&call, i)) {
|
||||
return EscapeCallStorageBorrow{}
|
||||
}
|
||||
}
|
||||
if callee.kind == .selector && callee.children_count > 0 {
|
||||
base_id := t.a.child(callee, 0)
|
||||
base := types.unalias_type(types.unwrap_all_pointers(t.tc.resolve_type(base_id)))
|
||||
if base is types.Interface {
|
||||
return EscapeCallStorageBorrow{}
|
||||
}
|
||||
base_type := t.normalize_type_alias(t.trim_pointer_type(t.lvalue_type(base_id)))
|
||||
if t.receiver_selector_is_fn_field(base_type, callee.value) {
|
||||
return EscapeCallStorageBorrow{}
|
||||
}
|
||||
}
|
||||
return EscapeCallStorageBorrow{
|
||||
summary: summary
|
||||
offset: offset
|
||||
}
|
||||
}
|
||||
|
||||
// escape_storage_borrow_ident restricts the new storage projection to locals
|
||||
// whose complete recorded payload provenance is available by name. Dereferences
|
||||
// and scalar fields/indexes need their original conservative address provenance.
|
||||
fn (t &Transformer) escape_storage_borrow_ident(id flat.NodeId) bool {
|
||||
return t.a.node(t.fixed_array_unwrap_arg(id)).kind == .ident
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
module transform
|
||||
|
||||
import os
|
||||
import v.flat
|
||||
import v.gen.c as cgen
|
||||
import v.parser
|
||||
import v.pref
|
||||
import v.types
|
||||
|
||||
fn storage_borrow_fixture(mut t Transformer, name string, params []types.Type, summary []bool) {
|
||||
decl := t.a.add_node(flat.Node{ kind: .fn_decl, value: name })
|
||||
t.call_param_types_index_ready = true
|
||||
t.call_param_types_decl_index[name] = FnParamDeclRef{ idx: int(decl) }
|
||||
t.call_param_types_decl_cache[int(decl)] = params
|
||||
t.fixed_array_borrow_params[int(decl)] = summary
|
||||
t.tc.fn_param_types[name] = params
|
||||
t.tc.fn_ret_types[name] = types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
}
|
||||
|
||||
fn test_borrowed_storage_address_keeps_payload_aliases_and_self_references() {
|
||||
for proven in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
ptr := types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
storage_borrow_fixture(mut t, 'borrow', [ptr], [proven])
|
||||
local := t.make_ident('local')
|
||||
tc.register_synth_type(local, types.Type(types.int_))
|
||||
address := t.make_prefix(.amp, local)
|
||||
tc.register_synth_type(address, ptr)
|
||||
call := t.make_call_typed('borrow', [address], flat.source_type_id('&int'))
|
||||
tc.register_synth_type(call, ptr)
|
||||
assert t.escape_aggregate_address_sources(call, map[string][]string{}, map[string]string{}) == if proven {
|
||||
[]string{}
|
||||
} else {
|
||||
['local']
|
||||
}
|
||||
// A copied aggregate can forward an embedded pointer despite borrowing its header.
|
||||
assert t.escape_aggregate_address_sources(call, {
|
||||
'local': ['pointee']
|
||||
}, map[string]string{}) == if proven {
|
||||
['pointee']
|
||||
} else {
|
||||
['pointee', 'local']
|
||||
}
|
||||
assert t.escape_aggregate_address_sources(call, {
|
||||
'local': ['local']
|
||||
}, map[string]string{}) == ['local']
|
||||
}
|
||||
}
|
||||
|
||||
fn test_borrowed_storage_only_strips_the_explicit_address_layer() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
ptr := types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
storage_borrow_fixture(mut t, 'borrow', [ptr], [true])
|
||||
owner := t.make_ident('owner')
|
||||
tc.register_synth_type(owner, types.Type(types.Struct{ name_id: flat.source_type_id('Owner') }))
|
||||
field := t.make_selector(owner, 'pointer', flat.source_type_id('&int'))
|
||||
tc.register_synth_type(field, ptr)
|
||||
address := t.make_prefix(.amp, field)
|
||||
tc.register_synth_type(address, types.Type(types.Pointer{ base_type: ptr }))
|
||||
call := t.make_call_typed('borrow', [address], flat.source_type_id('&int'))
|
||||
tc.register_synth_type(call, ptr)
|
||||
assert t.escape_aggregate_address_sources(call, {
|
||||
'owner': ['pointee']
|
||||
}, map[string]string{}) == ['pointee', 'owner']
|
||||
// A pointer alias is not an explicit address. Keep its existing conservative provenance.
|
||||
pointer := t.make_ident('p')
|
||||
tc.register_synth_type(pointer, ptr)
|
||||
alias_call := t.make_call_typed('borrow', [pointer], flat.source_type_id('&int'))
|
||||
tc.register_synth_type(alias_call, ptr)
|
||||
assert t.escape_aggregate_address_sources(alias_call, {
|
||||
'p': ['local']
|
||||
}, map[string]string{}) == ['local']
|
||||
}
|
||||
|
||||
fn test_borrowed_storage_dereference_and_scalar_fields_keep_original_provenance() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
ptr := types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
storage_borrow_fixture(mut t, 'borrow', [ptr], [true])
|
||||
pointer := t.make_ident('pointer')
|
||||
tc.register_synth_type(pointer, ptr)
|
||||
deref := t.make_prefix(.mul, pointer)
|
||||
tc.register_synth_type(deref, types.Type(types.int_))
|
||||
owner := t.make_ident('owner')
|
||||
tc.register_synth_type(owner, types.Type(types.Struct{ name_id: flat.source_type_id('Owner') }))
|
||||
field := t.make_selector(owner, 'encoded', flat.source_type_id('int'))
|
||||
tc.register_synth_type(field, types.Type(types.int_))
|
||||
for operand in [deref, field] {
|
||||
address := t.make_prefix(.amp, operand)
|
||||
tc.register_synth_type(address, ptr)
|
||||
call := t.make_call_typed('borrow', [address], flat.source_type_id('&int'))
|
||||
tc.register_synth_type(call, ptr)
|
||||
assert t.escape_aggregate_address_sources(call, {
|
||||
'pointer': ['stack']
|
||||
'owner': ['stack']
|
||||
}, map[string]string{}) == if operand == deref {
|
||||
['stack']
|
||||
} else {
|
||||
['stack', 'owner']
|
||||
}
|
||||
}
|
||||
// An identifier's recorded provenance also covers pointer-encoded scalars.
|
||||
encoded := t.make_ident('encoded')
|
||||
tc.register_synth_type(encoded, types.Type(types.int_))
|
||||
address := t.make_prefix(.amp, encoded)
|
||||
tc.register_synth_type(address, ptr)
|
||||
call := t.make_call_typed('borrow', [address], flat.source_type_id('&int'))
|
||||
tc.register_synth_type(call, ptr)
|
||||
assert t.escape_aggregate_address_sources(call, {
|
||||
'encoded': ['stack']
|
||||
}, map[string]string{}) == ['stack']
|
||||
}
|
||||
|
||||
fn test_unknown_function_values_arity_and_nested_calls_stay_conservative() {
|
||||
for mode in ['known', 'unknown', 'fn_value', 'lexical_shadow', 'arity', 'nested'] {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
ptr := types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
if mode != 'unknown' {
|
||||
storage_borrow_fixture(mut t, 'borrow', [ptr], [true])
|
||||
}
|
||||
if mode == 'fn_value' {
|
||||
t.set_var_type('borrow', flat.source_type_id('fn (&int) &int'))
|
||||
}
|
||||
local := t.make_ident('local')
|
||||
tc.register_synth_type(local, types.Type(types.int_))
|
||||
address := t.make_prefix(.amp, local)
|
||||
tc.register_synth_type(address, ptr)
|
||||
mut args := [address]
|
||||
if mode == 'arity' { args << address }
|
||||
mut call := t.make_call_typed('borrow', args, flat.source_type_id('&int'))
|
||||
tc.register_synth_type(call, ptr)
|
||||
if mode == 'lexical_shadow' {
|
||||
shadow := t.make_decl_assign('borrow', t.make_ident('callback'))
|
||||
body := t.make_block([shadow, call])
|
||||
start := a.children.len
|
||||
a.children << body
|
||||
a.add_node(flat.Node{ kind: .fn_decl, value: 'caller', children_start: start, children_count: 1 })
|
||||
}
|
||||
if mode == 'nested' {
|
||||
call = t.make_call_typed('unknown_outer', [call], flat.source_type_id('&int'))
|
||||
tc.register_synth_type(call, ptr)
|
||||
}
|
||||
assert t.escape_aggregate_address_sources(call, map[string][]string{}, map[string]string{}) == if mode in [
|
||||
'known',
|
||||
'nested',
|
||||
] {
|
||||
[]string{}
|
||||
} else {
|
||||
['local']
|
||||
}, mode
|
||||
}
|
||||
}
|
||||
|
||||
fn test_reference_receiver_borrows_only_its_storage_and_dynamic_interfaces_are_conservative() {
|
||||
for is_interface in [false, true] {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
receiver_type := if is_interface {
|
||||
types.Type(types.Interface{ name_id: flat.source_type_id('Owner') })
|
||||
} else {
|
||||
types.Type(types.Struct{ name_id: flat.source_type_id('Owner') })
|
||||
}
|
||||
ptr := types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
storage_borrow_fixture(mut t, 'Owner.read', [
|
||||
types.Type(types.Pointer{ base_type: receiver_type }),
|
||||
ptr,
|
||||
], [true, true])
|
||||
receiver := t.make_ident('owner')
|
||||
t.set_var_type('owner', flat.source_type_id('Owner'))
|
||||
tc.register_synth_type(receiver, receiver_type)
|
||||
callee := t.make_selector(receiver, 'read', 0)
|
||||
local := t.make_ident('local')
|
||||
tc.register_synth_type(local, types.Type(types.int_))
|
||||
address := t.make_prefix(.amp, local)
|
||||
tc.register_synth_type(address, ptr)
|
||||
start := a.children.len
|
||||
a.children << [callee, address]
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: start, children_count: 2 })
|
||||
tc.register_synth_type(call, ptr)
|
||||
t.set_resolved_call_entry(int(call), 'Owner.read')
|
||||
assert t.escape_aggregate_address_sources(call, map[string][]string{}, map[string]string{}) == if is_interface {
|
||||
['local']
|
||||
} else {
|
||||
[]string{}
|
||||
}
|
||||
assert t.escape_aggregate_address_sources(call, {
|
||||
'owner': ['payload']
|
||||
}, map[string]string{}) == if is_interface {
|
||||
['payload', 'local']
|
||||
} else {
|
||||
['payload']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const storage_borrow_source = 'module main
|
||||
struct Entry { value int }
|
||||
struct Tree { entries []Entry }
|
||||
struct Payload { pointer &int }
|
||||
struct Holder { inner Payload }
|
||||
fn extract(copy Payload) &int { return copy.pointer }
|
||||
fn (holder &Holder) copy_payload() &int { return extract(holder.inner) }
|
||||
fn payload_snapshot(value int) &int {
|
||||
local := value
|
||||
holder := Holder{inner: Payload{pointer: &local}}
|
||||
return holder.copy_payload()
|
||||
}
|
||||
fn (tree &Tree) index(node &Entry) int { return node.value }
|
||||
fn (tree &Tree) child_node(node &Entry) &Entry {
|
||||
id := tree.index(node)
|
||||
return unsafe { &tree.entries[id] }
|
||||
}
|
||||
fn (tree &Tree) type_reference(node &Entry) &Entry {
|
||||
if node.value == 0 { return unsafe { &tree.entries[0] } }
|
||||
return unsafe { &tree.entries[1] }
|
||||
}
|
||||
fn (tree &Tree) copied_payload(node &Entry) &Entry {
|
||||
_ = node.value
|
||||
return tree.child_node(node)
|
||||
}
|
||||
fn identity(node &Entry) &Entry { return node }
|
||||
fn inline_address(node &Entry) &int { return &node.value }
|
||||
fn snapshot_lookup(tree &Tree, node Entry) &Entry { return tree.child_node(&node) }
|
||||
fn snapshot_type(tree &Tree, node Entry) &Entry { return tree.type_reference(&node) }
|
||||
fn snapshot_identity(node Entry) &Entry { return identity(&node) }
|
||||
fn main() {}
|
||||
'
|
||||
|
||||
fn test_borrowed_snapshot_pointer_results_remove_boxes_but_identity_remains_boxed() {
|
||||
path := os.join_path(os.vtmp_dir(), 'return_storage_borrow_${os.getpid()}.v')
|
||||
os.write_file(path, storage_borrow_source)!
|
||||
defer { os.rm(path) or {} }
|
||||
mut p := parser.Parser.new(pref.new_preferences())
|
||||
mut a := p.parse_file(path)
|
||||
assert p.diagnostics.len == 0, p.diagnostics.str()
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.check_semantics()
|
||||
assert tc.errors.len == 0, tc.errors.str()
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.skip_generics = true
|
||||
t.cur_module = 'main'
|
||||
t.cur_file = path
|
||||
t.transformed_fns = []bool{len: a.nodes.len}
|
||||
t.prepare()
|
||||
mut used := map[string]bool{}
|
||||
for i, node in a.nodes {
|
||||
if node.kind == .fn_decl {
|
||||
used[node.value] = true
|
||||
if node.value in ['Tree.child_node', 'Tree.type_reference', 'Tree.copied_payload'] {
|
||||
assert t.fixed_array_borrow_params[i] == [false, true], '${node.value}: ${t.fixed_array_borrow_params[i]}'
|
||||
}
|
||||
if node.value == 'Holder.copy_payload' {
|
||||
assert t.fixed_array_borrow_params[i] == [true]
|
||||
}
|
||||
if node.value in ['identity', 'inline_address'] {
|
||||
assert t.fixed_array_borrow_params[i].len == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
t.transform_all()
|
||||
assert t.monomorph_errors.len == 0, t.monomorph_errors.str()
|
||||
mut gen := cgen.FlatGen.new()
|
||||
generated := gen.gen_with_used(a, used, &tc)
|
||||
for name in ['snapshot_lookup', 'snapshot_type'] {
|
||||
assert generated.contains('${name}(')
|
||||
body := generated.all_after_last('${name}(').all_after('{').all_before('\n}')
|
||||
assert !body.contains('memdup('), '${name}: ${body}'
|
||||
assert !body.contains('__heap_value_param'), '${name}: ${body}'
|
||||
}
|
||||
identity_body := generated.all_after_last('snapshot_identity(').all_after('{').all_before('\n}')
|
||||
assert identity_body.contains('memdup('), identity_body
|
||||
payload_body := generated.all_after_last('payload_snapshot(').all_after('{').all_before('\n}')
|
||||
assert payload_body.contains('memdup('), payload_body
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_scalar_child_lookup_preserves_empty_sentinels_and_snapshot_after_growth() {
|
||||
mut a := flat.FlatAst.new()
|
||||
child := a.add_node(flat.Node{ kind: .ident, value: 'saved' })
|
||||
a.children << child
|
||||
snapshot := flat.Node{ kind: .call, children_start: 0, children_count: 1 }
|
||||
for i in [-1, 0, 1] {
|
||||
assert a.node(a.child(&snapshot, i)).kind == a.child_node(&snapshot, i).kind
|
||||
assert a.node(a.child(&snapshot, i)).value == a.child_node(&snapshot, i).value
|
||||
}
|
||||
for start in [-1, a.children.len, a.children.len + 100] {
|
||||
invalid := flat.Node{ kind: .call, children_start: start, children_count: 1 }
|
||||
assert a.node(a.child(&invalid, 0)).kind == a.child_node(&invalid, 0).kind
|
||||
}
|
||||
for id in [flat.NodeId(-1), flat.NodeId(a.nodes.len + 100)] {
|
||||
start := a.children.len
|
||||
a.children << id
|
||||
invalid := flat.Node{ kind: .call, children_start: start, children_count: 1 }
|
||||
assert a.node(a.child(&invalid, 0)).kind == a.child_node(&invalid, 0).kind
|
||||
}
|
||||
count := a.nodes.cap + 100
|
||||
for _ in 0 .. count {
|
||||
a.add_node(flat.Node{ kind: .ident, value: 'growth' })
|
||||
a.children << flat.NodeId(-1)
|
||||
}
|
||||
assert a.node(a.child(&snapshot, 0)).value == 'saved'
|
||||
assert snapshot.children_count == 1
|
||||
}
|
||||
|
||||
fn test_call_type_queries_use_stable_snapshot_after_ast_growth() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_node(flat.Node{ kind: .ident, value: 'known' })
|
||||
start := a.children.len
|
||||
a.children << callee
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: start, children_count: 1 })
|
||||
snapshot := a.nodes[int(call)]
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
tc.fn_ret_types['known'] = types.Type(types.bool_)
|
||||
tc.fn_param_types['known'] = [types.Type(types.String{})]
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
count := a.nodes.cap + 100
|
||||
for _ in 0 .. count {
|
||||
a.add_node(flat.Node{ kind: .ident, value: 'growth' })
|
||||
a.children << flat.NodeId(-1)
|
||||
}
|
||||
assert t.get_call_return_type(call, snapshot) == flat.source_type_bool
|
||||
params := t.call_param_types_for_node('known', snapshot)
|
||||
assert params.len == 1
|
||||
assert params[0] is types.String
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn test_scoped_text_promotion_reuses_ast_text_and_deduplicates_unknown_text() {
|
||||
mut a := flat.FlatAst.new()
|
||||
_, canonical := a.intern_text('source identifier'.clone())
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.promote_text_cache = &PromoteTextCache{}
|
||||
t.begin_promote_text_window()
|
||||
source_copy := 'source identifier'.clone()
|
||||
assert t.promote_scoped_result_text(source_copy) == canonical
|
||||
assert t.scoped_promoted_texts.len == 0
|
||||
first := t.promote_scoped_result_text('new identifier'.clone())
|
||||
second := t.promote_scoped_result_text('new identifier'.clone())
|
||||
assert first == second
|
||||
assert first.str == second.str
|
||||
assert t.scoped_promoted_texts.len == 1
|
||||
t.end_promote_text_window()
|
||||
assert !t.promote_text_cache.active
|
||||
assert t.promote_scoped_result_text(source_copy) == canonical
|
||||
}
|
||||
|
||||
fn test_scoped_text_local_clone_can_become_durable_ast_canonical_text() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
local := t.promote_scoped_result_text('later canonical identifier'.clone())
|
||||
assert t.scoped_promoted_texts.len == 1
|
||||
text_start := a.text_values.len
|
||||
scratch := transform_worker_scope_begin(true)
|
||||
a.intern_text('later canonical identifier'.clone())
|
||||
transform_worker_scope_leave(scratch)
|
||||
a.promote_transform_texts_from(text_start, scratch)
|
||||
id := a.text_ids['later canonical identifier'] or { panic('missing canonical text') }
|
||||
canonical := a.text(id)
|
||||
t.promote_text_cache = &PromoteTextCache{}
|
||||
t.begin_promote_text_window()
|
||||
published := t.promote_scoped_result_text('later canonical identifier'.clone())
|
||||
assert published == local
|
||||
assert published.str == canonical.str
|
||||
$if prealloc {
|
||||
assert !transform_scope_owns(scratch, published.str)
|
||||
}
|
||||
t.end_promote_text_window()
|
||||
transform_worker_scope_free(scratch)
|
||||
assert published == 'later canonical identifier'
|
||||
assert local == published
|
||||
assert t.promote_scoped_result_text('later canonical identifier'.clone()) == published
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn sql_guard_node(mut a flat.FlatAst, template flat.Node, children []flat.NodeId) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << children
|
||||
return a.add_node(flat.Node{
|
||||
...template
|
||||
children_start: start
|
||||
children_count: flat.child_count(children.len)
|
||||
})
|
||||
}
|
||||
|
||||
fn test_sql_bookkeeping_is_conservative_before_program_scan() {
|
||||
mut a := flat.FlatAst.new()
|
||||
field := a.add_val(.field_init, 'name')
|
||||
init := sql_guard_node(mut a, flat.Node{ kind: .struct_init }, [field])
|
||||
data := a.add_val(.sql_expr, 'querydata select from records')
|
||||
name := a.add_val(.ident, 'record')
|
||||
other := a.add_val(.int_literal, '1')
|
||||
assignment := sql_guard_node(mut a, flat.Node{ kind: .assign, op: .assign }, [
|
||||
name,
|
||||
other,
|
||||
])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
assert !t.sql_tracking_disabled
|
||||
t.record_orm_initialized_fields('record', init)
|
||||
assert t.sql_initialized_fields('record') == ['name']
|
||||
t.record_sql_query_data_alias('record', data)
|
||||
assert t.sql_query_data_aliases['record'] == ['select', 'from', 'records']
|
||||
t.update_orm_initialized_fields_for_assignment(a.nodes[int(assignment)])
|
||||
t.update_sql_query_data_aliases_for_assignment(a.nodes[int(assignment)])
|
||||
assert 'record' !in t.orm_initialized_fields
|
||||
assert 'record' !in t.sql_query_data_aliases
|
||||
assert t.sql_initialized_fields('record') == ['*']
|
||||
}
|
||||
|
||||
fn test_sql_free_program_disables_bookkeeping_and_workers_keep_the_proof() {
|
||||
mut a := flat.FlatAst.new()
|
||||
field := a.add_val(.field_init, 'name')
|
||||
init := sql_guard_node(mut a, flat.Node{ kind: .struct_init }, [field])
|
||||
name := a.add_val(.ident, 'record')
|
||||
assignment := sql_guard_node(mut a, flat.Node{ kind: .assign, op: .assign }, [
|
||||
name,
|
||||
init,
|
||||
])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.collect_exclusive_closure_return_fns()
|
||||
assert t.sql_tracking_disabled
|
||||
t.record_orm_initialized_fields('record', init)
|
||||
t.update_orm_initialized_fields_for_assignment(a.nodes[int(assignment)])
|
||||
t.record_sql_query_data_alias('record', init)
|
||||
t.update_sql_query_data_aliases_for_assignment(a.nodes[int(assignment)])
|
||||
assert t.orm_initialized_fields.len == 0
|
||||
assert t.sql_query_data_aliases.len == 0
|
||||
mut worker := t.fork_program_view(&a, &tc, map[string]bool{}, false)
|
||||
assert worker.sql_tracking_disabled
|
||||
worker.record_orm_initialized_fields('record', init)
|
||||
assert worker.orm_initialized_fields.len == 0
|
||||
t.reset_var_types()
|
||||
assert t.sql_tracking_disabled
|
||||
}
|
||||
|
||||
fn test_sql_in_generic_or_unreachable_body_preserves_program_bookkeeping() {
|
||||
mut a := flat.FlatAst.new()
|
||||
field := a.add_val(.field_init, 'name')
|
||||
init := sql_guard_node(mut a, flat.Node{ kind: .struct_init }, [field])
|
||||
sql := a.add_val(.sql_expr, 'querydata select from records')
|
||||
body := sql_guard_node(mut a, flat.Node{ kind: .comptime_if }, [sql])
|
||||
generic_fn := sql_guard_node(mut a, flat.Node{ kind: .fn_decl, value: 'unreachable' }, [body])
|
||||
a.nodes[int(generic_fn)].set_generic_params(['T'])
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.collect_exclusive_closure_return_fns()
|
||||
assert !t.sql_tracking_disabled
|
||||
t.record_orm_initialized_fields('record', init)
|
||||
assert t.sql_initialized_fields('record') == ['name']
|
||||
mut worker := t.fork_program_view(&a, &tc, map[string]bool{}, false)
|
||||
assert !worker.sql_tracking_disabled
|
||||
worker.record_orm_initialized_fields('record', init)
|
||||
worker.record_sql_query_data_alias('query', sql)
|
||||
assert worker.sql_initialized_fields('record') == ['name']
|
||||
assert worker.sql_query_data_aliases['query'] == ['select', 'from', 'records']
|
||||
// A second use of the completed prescan retains its immutable result.
|
||||
t.collect_exclusive_closure_return_fns()
|
||||
assert !t.sql_tracking_disabled
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
module transform
|
||||
|
||||
import os
|
||||
import v.flat
|
||||
import v.gen.c as cgen
|
||||
import v.parser
|
||||
import v.pref
|
||||
import v.types
|
||||
|
||||
const storage_borrow_audit_source = 'module main
|
||||
struct AuditPayload { pointer &int }
|
||||
struct AuditEnvelope { inner AuditPayload }
|
||||
struct AuditSink { mut: pointer &int = unsafe { nil } }
|
||||
fn audit_extract(copy AuditPayload) &int { return copy.pointer }
|
||||
fn audit_extract_optional(copy AuditPayload) ?&int { return copy.pointer }
|
||||
fn audit_extract_result(copy AuditPayload) !&int { return copy.pointer }
|
||||
fn audit_storage(input &AuditEnvelope) &int { return audit_extract(input.inner) }
|
||||
fn audit_storage_optional(input &AuditEnvelope) ?&int { return audit_extract_optional(input.inner) }
|
||||
fn audit_storage_result(input &AuditEnvelope) !&int { return audit_extract_result(input.inner) }
|
||||
fn audit_storage_field(input &AuditEnvelope) &&int { return &input.inner.pointer }
|
||||
fn audit_store_payload(input &AuditEnvelope, mut sink AuditSink) &int {
|
||||
sink.pointer = input.inner.pointer
|
||||
return sink.pointer
|
||||
}
|
||||
fn audit_direct(value int) &int {
|
||||
local := value
|
||||
payload := AuditEnvelope{inner: AuditPayload{pointer: &local}}
|
||||
return audit_storage(&payload)
|
||||
}
|
||||
fn audit_optional(value int) ?&int {
|
||||
local := value
|
||||
payload := AuditEnvelope{inner: AuditPayload{pointer: &local}}
|
||||
return audit_storage_optional(&payload)
|
||||
}
|
||||
fn audit_result(value int) !&int {
|
||||
local := value
|
||||
payload := AuditEnvelope{inner: AuditPayload{pointer: &local}}
|
||||
return audit_storage_result(&payload)
|
||||
}
|
||||
fn audit_field(value int) &&int {
|
||||
local := value
|
||||
payload := AuditEnvelope{inner: AuditPayload{pointer: &local}}
|
||||
return audit_storage_field(&payload)
|
||||
}
|
||||
fn audit_mutable_store(value int, mut sink AuditSink) &int {
|
||||
local := value
|
||||
payload := AuditEnvelope{inner: AuditPayload{pointer: &local}}
|
||||
return audit_store_payload(&payload, mut sink)
|
||||
}
|
||||
fn main() {}
|
||||
'
|
||||
|
||||
fn test_storage_only_summary_keeps_real_embedded_aliases_through_return_wrappers() {
|
||||
path := os.join_path(os.vtmp_dir(), 'storage_borrow_audit_${os.getpid()}.v')
|
||||
os.write_file(path, storage_borrow_audit_source)!
|
||||
defer { os.rm(path) or {} }
|
||||
mut p := parser.Parser.new(pref.new_preferences())
|
||||
mut a := p.parse_file(path)
|
||||
assert p.diagnostics.len == 0, p.diagnostics.str()
|
||||
mut tc := types.TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.check_semantics()
|
||||
assert tc.errors.len == 0, tc.errors.str()
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
t.skip_generics = true
|
||||
t.cur_module = 'main'
|
||||
t.cur_file = path
|
||||
t.transformed_fns = []bool{len: a.nodes.len}
|
||||
t.prepare()
|
||||
mut used := map[string]bool{}
|
||||
for i, node in a.nodes {
|
||||
if node.kind != .fn_decl {
|
||||
continue
|
||||
}
|
||||
used[node.value] = true
|
||||
if node.value in ['audit_storage', 'audit_storage_optional', 'audit_storage_result'] {
|
||||
assert t.fixed_array_borrow_params[i] == [true], '${node.value}: ${t.fixed_array_borrow_params[i]}'
|
||||
}
|
||||
if node.value in ['audit_storage_field', 'audit_store_payload'] {
|
||||
summary := t.fixed_array_borrow_params[i] or { []bool{} }
|
||||
assert summary.len == 0 || !summary[0], '${node.value}: ${summary}'
|
||||
}
|
||||
}
|
||||
t.transform_all()
|
||||
assert t.monomorph_errors.len == 0, t.monomorph_errors.str()
|
||||
mut gen := cgen.FlatGen.new()
|
||||
generated := gen.gen_with_used(a, used, &tc)
|
||||
for name in ['audit_direct', 'audit_optional', 'audit_result', 'audit_field', 'audit_mutable_store'] {
|
||||
assert generated.contains('${name}('), name
|
||||
body := generated.all_after_last('${name}(').all_after('{').all_before('\n}')
|
||||
mut local_is_boxed := false
|
||||
for line in body.split_into_lines() {
|
||||
if line.contains('local =') && line.contains('memdup(') {
|
||||
local_is_boxed = true
|
||||
}
|
||||
}
|
||||
assert local_is_boxed, '${name}: ${body}'
|
||||
}
|
||||
proof := os.getenv('STORAGE_BORROW_AUDIT_C')
|
||||
if proof.len > 0 {
|
||||
os.write_file(proof, generated)!
|
||||
}
|
||||
}
|
||||
|
||||
fn test_storage_projection_rejects_nonidentifier_address_operands() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
value := t.make_ident('value')
|
||||
assert t.escape_storage_borrow_ident(value)
|
||||
paren := t.make_paren(value)
|
||||
assert t.escape_storage_borrow_ident(paren)
|
||||
field := t.make_selector(value, 'field', 0)
|
||||
assert !t.escape_storage_borrow_ident(field)
|
||||
deref := t.make_prefix(.mul, value)
|
||||
assert !t.escape_storage_borrow_ident(deref)
|
||||
}
|
||||
|
||||
fn test_storage_summary_rejects_wrapped_indirect_callees() {
|
||||
for kind in [flat.NodeKind.index, .paren] {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut t := new_transformer(mut a, &tc, map[string]bool{})
|
||||
ptr := types.Type(types.Pointer{ base_type: types.Type(types.int_) })
|
||||
decl := a.add_node(flat.Node{ kind: .fn_decl, value: 'audit_borrow' })
|
||||
t.call_param_types_index_ready = true
|
||||
t.call_param_types_decl_index['audit_borrow'] = FnParamDeclRef{ idx: int(decl) }
|
||||
t.call_param_types_decl_cache[int(decl)] = [ptr]
|
||||
t.fixed_array_borrow_params[int(decl)] = [true]
|
||||
tc.fn_param_types['audit_borrow'] = [ptr]
|
||||
tc.fn_ret_types['audit_borrow'] = ptr
|
||||
callback := t.make_ident('audit_borrow')
|
||||
t.set_var_type('audit_borrow', flat.source_type_id('fn (&int) &int'))
|
||||
wrapped_start := a.children.len
|
||||
a.children << callback
|
||||
wrapped := a.add_node(flat.Node{
|
||||
kind: kind
|
||||
children_start: wrapped_start
|
||||
children_count: 1
|
||||
})
|
||||
local := t.make_ident('local')
|
||||
tc.register_synth_type(local, types.Type(types.int_))
|
||||
address := t.make_prefix(.amp, local)
|
||||
tc.register_synth_type(address, ptr)
|
||||
call_start := a.children.len
|
||||
a.children << [wrapped, address]
|
||||
call := a.add_node(flat.Node{
|
||||
kind: .call
|
||||
children_start: call_start
|
||||
children_count: 2
|
||||
})
|
||||
tc.register_synth_type(call, ptr)
|
||||
t.set_resolved_call_entry(int(call), 'audit_borrow')
|
||||
assert !t.escape_call_storage_borrow(call, *a.node(call)).borrows(1), kind.str()
|
||||
assert t.escape_aggregate_address_sources(call, map[string][]string{}, map[string]string{}) == ['local'], kind.str()
|
||||
}
|
||||
}
|
||||
+112
-27
@@ -210,6 +210,7 @@ mut:
|
||||
receiver_method_suffix_index map[string]string
|
||||
declared_fn_name_counts map[string]u8
|
||||
variadic_suffix_index map[string]i8
|
||||
const_selector_filter &ConstSelectorNameFilter = unsafe { nil }
|
||||
const_suffixes map[string]string
|
||||
source_parent_ids []i32
|
||||
array_append_tree_factories map[string]bool // immutable freshness proof, shared by workers
|
||||
@@ -245,6 +246,7 @@ mut:
|
||||
// const_array_fixed_storage_cache avoids rescanning the complete AST for
|
||||
// repeated uses of the same array constant in one transform worker.
|
||||
const_array_fixed_storage_cache map[string]i8
|
||||
enum_selector_filter &EnumSelectorNameFilter = unsafe { nil }
|
||||
enum_types map[string][]string
|
||||
enum_explicit_first_values map[string]bool // the first member has a value, maybe not 0
|
||||
enum_backing_types flat.TypeTextBindings
|
||||
@@ -283,6 +285,7 @@ mut:
|
||||
fixed_array_value_rvalues map[string]bool
|
||||
addr_lvalue_pointer_locals map[string]bool
|
||||
orm_initialized_fields map[string][]string
|
||||
sql_tracking_disabled bool // complete program scan found no SQL expressions
|
||||
sql_query_data_aliases map[string][]string
|
||||
sql_array_it_name string
|
||||
bound_method_arrays map[string]BoundMethodArrayInfo
|
||||
@@ -2532,12 +2535,18 @@ fn (mut t Transformer) mark_fn_used_name(name string) {
|
||||
return
|
||||
}
|
||||
lowered := if isnil(t.tc) { c_name(name) } else { t.tc.cached_c_name(name) }
|
||||
if !t.used_fn_contains_name(name) && !t.used_fn_contains_name(lowered)
|
||||
name_used := t.used_fn_contains_name(name)
|
||||
lowered_used := if lowered == name { name_used } else { t.used_fn_contains_name(lowered) }
|
||||
if !name_used && !lowered_used
|
||||
&& t.enum_method_name_shadows_field(name) {
|
||||
return
|
||||
}
|
||||
t.mark_used_fn_key(name)
|
||||
t.mark_used_fn_key(lowered)
|
||||
if !name_used {
|
||||
t.record_used_fn_key(name)
|
||||
}
|
||||
if lowered != name && !lowered_used {
|
||||
t.record_used_fn_key(lowered)
|
||||
}
|
||||
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
|
||||
needs_module_prefix := !name.contains('.') || local_method_fn_name_needs_module_prefix(name)
|
||||
if needs_module_prefix && !name.starts_with('${t.cur_module}.') {
|
||||
@@ -2584,6 +2593,11 @@ fn (mut t Transformer) mark_used_fn_key(key string) {
|
||||
if t.used_fn_contains_name(key) {
|
||||
return
|
||||
}
|
||||
t.record_used_fn_key(key)
|
||||
}
|
||||
|
||||
// record_used_fn_key records a spelling after the caller has checked all used sets.
|
||||
fn (mut t Transformer) record_used_fn_key(key string) {
|
||||
if t.used_fns_log_active {
|
||||
t.used_fns_log << key
|
||||
}
|
||||
@@ -2646,6 +2660,7 @@ fn (mut t Transformer) prepare() {
|
||||
}
|
||||
mut psw := time.new_stopwatch()
|
||||
t.collect_types()
|
||||
t.prepare_enum_selector_filter()
|
||||
t.rebuild_struct_autostr_recurse_index()
|
||||
if !t.defer_pre_scan_indexes {
|
||||
t.build_source_parent_index()
|
||||
@@ -2660,6 +2675,7 @@ fn (mut t Transformer) prepare() {
|
||||
t.collect_multi_return_fn_ret_types()
|
||||
}
|
||||
t.collect_const_suffixes()
|
||||
t.prepare_const_selector_filter()
|
||||
t.timing_profile(' [ttime] prep small idx ${f64(psw.elapsed().microseconds()) / 1000.0:7.2f} ms')
|
||||
psw.restart()
|
||||
// Alias normalization consults the type maps populated by collect_types.
|
||||
@@ -3708,6 +3724,9 @@ fn (t &Transformer) declared_var_spelling(name string) ?string {
|
||||
}
|
||||
|
||||
fn (mut t Transformer) record_orm_initialized_fields(name string, rhs_id flat.NodeId) {
|
||||
if t.sql_tracking_disabled {
|
||||
return
|
||||
}
|
||||
if name == '' || int(rhs_id) < 0 || int(rhs_id) >= t.a.nodes.len {
|
||||
return
|
||||
}
|
||||
@@ -3727,6 +3746,9 @@ fn (mut t Transformer) record_orm_initialized_fields(name string, rhs_id flat.No
|
||||
}
|
||||
|
||||
fn (mut t Transformer) record_sql_query_data_alias(name string, rhs_id flat.NodeId) {
|
||||
if t.sql_tracking_disabled {
|
||||
return
|
||||
}
|
||||
if name == '' || int(rhs_id) < 0 || int(rhs_id) >= t.a.nodes.len {
|
||||
return
|
||||
}
|
||||
@@ -3744,6 +3766,9 @@ fn (mut t Transformer) record_sql_query_data_alias(name string, rhs_id flat.Node
|
||||
}
|
||||
|
||||
fn (mut t Transformer) update_sql_query_data_aliases_for_assignment(node flat.Node) {
|
||||
if t.sql_tracking_disabled {
|
||||
return
|
||||
}
|
||||
if node.children_count < 2 {
|
||||
return
|
||||
}
|
||||
@@ -3764,6 +3789,9 @@ fn (mut t Transformer) update_sql_query_data_aliases_for_assignment(node flat.No
|
||||
}
|
||||
|
||||
fn (mut t Transformer) update_orm_initialized_fields_for_assignment(node flat.Node) {
|
||||
if t.sql_tracking_disabled {
|
||||
return
|
||||
}
|
||||
if node.children_count < 2 {
|
||||
return
|
||||
}
|
||||
@@ -4038,7 +4066,7 @@ fn (mut t Transformer) collect_types() {
|
||||
})
|
||||
mut fields := []FieldInfo{}
|
||||
for i in 0 .. node.children_count {
|
||||
f := t.a.child_node(&node, i)
|
||||
f := t.a.node(t.a.child(&node, i))
|
||||
if f.kind != .field_decl {
|
||||
continue
|
||||
}
|
||||
@@ -4065,10 +4093,10 @@ fn (mut t Transformer) collect_types() {
|
||||
info := StructInfo{
|
||||
name: node.source_value_id()
|
||||
module: cur_mod
|
||||
is_params: comma_list_has_item(node.typ_text(), 'params')
|
||||
is_aligned: transform_struct_decl_alignment_is_set(node.typ_text())
|
||||
is_c_anon: comma_list_has_item(node.typ_text(), 'c_anon')
|
||||
alignment: transform_struct_decl_alignment_value(node.typ_text())
|
||||
is_params: comma_list_has_item(flat.source_type_name(node.typ), 'params')
|
||||
is_aligned: transform_struct_decl_alignment_is_set(flat.source_type_name(node.typ))
|
||||
is_c_anon: comma_list_has_item(flat.source_type_name(node.typ), 'c_anon')
|
||||
alignment: transform_struct_decl_alignment_value(flat.source_type_name(node.typ))
|
||||
fields: fields
|
||||
field_indices: struct_field_indices(fields)
|
||||
}
|
||||
@@ -4090,10 +4118,11 @@ fn (mut t Transformer) collect_types() {
|
||||
}
|
||||
.type_decl {
|
||||
if node.children_count > 0 {
|
||||
generic_params := node.generic_params()
|
||||
mut variants := []u32{}
|
||||
for i in 0 .. node.children_count {
|
||||
v := t.a.child_node(&node, i)
|
||||
variants << t.normalize_sum_variant_type(v.source_value_id(), cur_mod, node.generic_params())
|
||||
variants << t.normalize_sum_variant_type(v.source_value_id(), cur_mod, generic_params)
|
||||
}
|
||||
decl_name := if cur_mod !in ['', 'main', 'builtin'] {
|
||||
'${cur_mod}.${node.value_text()}'
|
||||
@@ -4136,7 +4165,7 @@ fn (mut t Transformer) collect_types() {
|
||||
mut field_names := []string{}
|
||||
mut explicit_first_value := false
|
||||
for i in 0 .. node.children_count {
|
||||
f := t.a.child_node(&node, i)
|
||||
f := t.a.node(t.a.child(&node, i))
|
||||
if f.kind == .enum_field {
|
||||
if field_names.len == 0 {
|
||||
explicit_first_value = f.children_count > 0
|
||||
@@ -4186,7 +4215,7 @@ fn (mut t Transformer) collect_types() {
|
||||
t.cur_file = cur_file
|
||||
t.cur_module = cur_mod
|
||||
for i in 0 .. node.children_count {
|
||||
f := t.a.child_node(&node, i)
|
||||
f := t.a.node(t.a.child(&node, i))
|
||||
mut typ := t.normalize_type_in_module(f.typ, cur_mod)
|
||||
if typ == 0 && f.children_count > 0 {
|
||||
typ = t.normalize_type_in_module(t.node_type(t.a.child(f, 0)), cur_mod)
|
||||
@@ -5482,6 +5511,7 @@ fn (t &Transformer) fork_program_view(ast &flat.FlatAst, wtc &types.TypeChecker,
|
||||
receiver_method_suffix_index: t.receiver_method_suffix_index
|
||||
declared_fn_name_counts: t.declared_fn_name_counts
|
||||
variadic_suffix_index: t.variadic_suffix_index
|
||||
const_selector_filter: t.const_selector_filter
|
||||
const_suffixes: t.const_suffixes
|
||||
source_parent_ids: t.source_parent_ids
|
||||
array_append_tree_factories: t.array_append_tree_factories
|
||||
@@ -5497,6 +5527,7 @@ fn (t &Transformer) fork_program_view(ast &flat.FlatAst, wtc &types.TypeChecker,
|
||||
} else {
|
||||
map[string]i8{}
|
||||
}
|
||||
enum_selector_filter: t.enum_selector_filter
|
||||
enum_types: t.enum_types
|
||||
enum_explicit_first_values: t.enum_explicit_first_values
|
||||
enum_backing_types: fork_prepared_type_bindings(t.enum_backing_types)
|
||||
@@ -5572,6 +5603,7 @@ fn (t &Transformer) fork_program_view(ast &flat.FlatAst, wtc &types.TypeChecker,
|
||||
pointer_value_rvalues: map[string]bool{}
|
||||
fixed_array_value_rvalues: map[string]bool{}
|
||||
orm_initialized_fields: map[string][]string{}
|
||||
sql_tracking_disabled: t.sql_tracking_disabled
|
||||
sql_query_data_aliases: map[string][]string{}
|
||||
invalidated_smartcasts: map[string]bool{}
|
||||
smartcast_invalidation_event_ids: map[string]int{}
|
||||
@@ -7974,6 +8006,9 @@ fn (mut t Transformer) collect_exclusive_closure_return_fns() {
|
||||
return
|
||||
}
|
||||
t.exclusive_closure_returns_done = true
|
||||
// This existing whole-program scan also proves whether SQL bookkeeping can
|
||||
// be skipped. Standalone transformers retain the conservative default.
|
||||
t.sql_tracking_disabled = true
|
||||
t.exclusive_closure_return_fns.clear()
|
||||
old_module := t.cur_module
|
||||
defer {
|
||||
@@ -7987,6 +8022,9 @@ fn (mut t Transformer) collect_exclusive_closure_return_fns() {
|
||||
for idx in 0 .. t.a.nodes.len {
|
||||
// The AST arena is unchanged during this scan.
|
||||
node := unsafe { &t.a.nodes[idx] }
|
||||
if node.kind == .sql_expr {
|
||||
t.sql_tracking_disabled = false
|
||||
}
|
||||
span_cost += match node.kind {
|
||||
.call, .struct_init { 8 }
|
||||
.selector { 6 }
|
||||
@@ -10735,11 +10773,33 @@ fn (mut t Transformer) scan_escape_pass(id flat.NodeId, mut amp_ptrs map[string]
|
||||
if int(id) < 0 || int(id) >= t.a.nodes.len {
|
||||
return
|
||||
}
|
||||
kind := t.a.nodes[int(id)].kind
|
||||
children_count := t.a.nodes[int(id)].children_count
|
||||
// A childless node cannot introduce an alias or retain a local address.
|
||||
if t.a.nodes[int(id)].children_count == 0 && t.a.nodes[int(id)].kind != .comptime_for {
|
||||
if children_count == 0 && kind != .comptime_for {
|
||||
return
|
||||
}
|
||||
t.scan_escape_pass_nonempty(id, mut amp_ptrs, mut amp_sources, mut ptr_aliases, mut method_value_receivers, mut closure_capture_aliases, mut interface_boxes, mut returned, mut local_stack_names, mut local_stack_added, mut reference_backing_sources, can_clear_interface_boxes)
|
||||
needs_scan := match kind {
|
||||
.comptime_for, .if_expr, .comptime_if, .match_stmt, .match_branch, .for_stmt,
|
||||
.select_stmt, .select_branch, .block, .for_in_stmt, .fn_literal, .lambda_expr,
|
||||
.fn_decl, .decl_assign, .assign, .return_stmt, .spawn_expr, .call,
|
||||
.selector_assign, .index_assign {
|
||||
true
|
||||
}
|
||||
.infix { t.a.nodes[int(id)].op == .left_shift && children_count == 2 }
|
||||
.or_expr { children_count == 2 }
|
||||
else { false }
|
||||
}
|
||||
if needs_scan {
|
||||
t.scan_escape_pass_nonempty(id, mut amp_ptrs, mut amp_sources, mut ptr_aliases, mut method_value_receivers, mut closure_capture_aliases, mut interface_boxes, mut returned, mut local_stack_names, mut local_stack_added, mut reference_backing_sources, can_clear_interface_boxes)
|
||||
return
|
||||
}
|
||||
// Value wrappers only forward their children. Keep scalar offsets, not a node
|
||||
// header or child slice: reflected loops below them can grow both AST arrays.
|
||||
children_start := t.a.nodes[int(id)].children_start
|
||||
for i in 0 .. children_count {
|
||||
t.scan_escape_pass(t.a.children[children_start + i], mut amp_ptrs, mut amp_sources, mut ptr_aliases, mut method_value_receivers, mut closure_capture_aliases, mut interface_boxes, mut returned, mut local_stack_names, mut local_stack_added, mut reference_backing_sources, can_clear_interface_boxes)
|
||||
}
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
@@ -10804,7 +10864,7 @@ fn (mut t Transformer) scan_escape_pass_nonempty(id flat.NodeId, mut amp_ptrs ma
|
||||
// The `else` of an `if x := res()` guard binds the error as `err`, which is only
|
||||
// declared when the branch is lowered.
|
||||
is_guard := node.kind == .if_expr && node.children_count > 0
|
||||
&& t.a.child_node(node, 0).kind == .decl_assign
|
||||
&& t.a.node(t.a.child(node, 0)).kind == .decl_assign
|
||||
mut previous_err_depth := -1
|
||||
if is_guard {
|
||||
previous_err_depth = t.escape_decl_depths['err'] or { -1 }
|
||||
@@ -10847,7 +10907,7 @@ fn (mut t Transformer) scan_escape_pass_nonempty(id flat.NodeId, mut amp_ptrs ma
|
||||
}
|
||||
mut body_start := if node.value == 'else' { 0 } else { 1 }
|
||||
if node.children_count >= 2 {
|
||||
second := t.a.child_node(node, 1)
|
||||
second := t.a.node(t.a.child(node, 1))
|
||||
if second.kind == .prefix && second.op == .arrow {
|
||||
body_start = 2
|
||||
}
|
||||
@@ -11770,22 +11830,28 @@ fn (t &Transformer) escape_aggregate_address_sources(id flat.NodeId, amp_sources
|
||||
return []string{}
|
||||
}
|
||||
mut sources := []string{}
|
||||
mut storage_borrow := EscapeCallStorageBorrow{}
|
||||
mut storage_borrow_ready := false
|
||||
if node.children_count > 0 {
|
||||
callee := t.a.child_node(node, 0)
|
||||
if callee.kind == .selector && callee.children_count > 0 {
|
||||
receiver_id := t.a.child(callee, 0)
|
||||
if !t.callee_base_is_not_a_runtime_value(receiver_id)
|
||||
&& t.method_receiver_is_reference(id, receiver_id, callee.value_text()) {
|
||||
if t.escape_storage_borrow_ident(receiver_id) {
|
||||
storage_borrow = t.escape_call_storage_borrow(id, *node)
|
||||
storage_borrow_ready = true
|
||||
}
|
||||
// Interface dispatch borrows the header to read its object pointer;
|
||||
// the concrete method receives the object, never the header's address.
|
||||
// A receiver that already is a pointer (`node.parent.method()` with
|
||||
// `parent &T`) is passed by value: only what it points to can flow
|
||||
// through the call, never the address of the expression holding it.
|
||||
receiver_sources := if !isnil(t.tc)
|
||||
receiver_sources := if (!isnil(t.tc)
|
||||
&& types.unalias_type(t.tc.resolve_type(receiver_id)) in [
|
||||
types.Interface,
|
||||
types.Pointer,
|
||||
] {
|
||||
]) || (t.escape_storage_borrow_ident(receiver_id) && storage_borrow.borrows(0)) {
|
||||
t.escape_aggregate_address_sources(receiver_id, amp_sources, ptr_aliases)
|
||||
} else {
|
||||
t.escape_address_sources(receiver_id, amp_sources, ptr_aliases)
|
||||
@@ -11800,7 +11866,22 @@ fn (t &Transformer) escape_aggregate_address_sources(id flat.NodeId, amp_sources
|
||||
}
|
||||
// Reference receivers and argument addresses can flow through the call result.
|
||||
for i in 1 .. node.children_count {
|
||||
for source_name in t.escape_aggregate_address_sources(t.a.child(node, i), amp_sources, ptr_aliases) {
|
||||
arg_id := t.a.child(node, i)
|
||||
arg := t.a.node(t.fixed_array_unwrap_arg(arg_id))
|
||||
// A borrowed address cannot be returned, but pointers held inside the
|
||||
// addressed value may still be copied into the result.
|
||||
mut payload_id := arg_id
|
||||
if arg.kind == .prefix && arg.op == .amp && arg.children_count == 1
|
||||
&& t.escape_storage_borrow_ident(t.a.child(arg, 0)) {
|
||||
if !storage_borrow_ready {
|
||||
storage_borrow = t.escape_call_storage_borrow(id, *node)
|
||||
storage_borrow_ready = true
|
||||
}
|
||||
if storage_borrow.borrows(i) {
|
||||
payload_id = t.a.child(arg, 0)
|
||||
}
|
||||
}
|
||||
for source_name in t.escape_aggregate_address_sources(payload_id, amp_sources, ptr_aliases) {
|
||||
if source_name !in sources {
|
||||
sources << source_name
|
||||
}
|
||||
@@ -19274,7 +19355,7 @@ fn (mut t Transformer) transform_decl_assign_stmt(id flat.NodeId, node flat.Node
|
||||
}
|
||||
panic('internal error: empty decl_assign child in ${t.cur_fn_name}: count=${node.children_count} typ=${node.typ_text()} value=${node.value_text()} children=${parts.join('|')}')
|
||||
}
|
||||
source_lhs := t.a.child_node(&node, 0)
|
||||
source_lhs := t.a.node(t.a.child(&node, 0))
|
||||
shadows_pointer_storage := source_single_decl && source_lhs.kind == .ident
|
||||
&& (t.heaped_amp_locals[source_lhs.value_text()] || t.pointer_value_rvalues[source_lhs.value_text()]
|
||||
|| t.pointer_value_lvalues[source_lhs.value_text()])
|
||||
@@ -19512,7 +19593,7 @@ fn (mut t Transformer) transform_decl_assign_stmt(id flat.NodeId, node flat.Node
|
||||
}
|
||||
if typ != 0 {
|
||||
mut raw_typ := u32(0)
|
||||
for candidate in [t.a.child_node(&node, 0).typ, node.typ] {
|
||||
for candidate in [t.a.node(t.a.child(&node, 0)).typ, node.typ] {
|
||||
if candidate != 0
|
||||
&& (t.generic_type_text_contains_alias(candidate, t.cur_module)
|
||||
|| (t.enum_type_name_for_expected(candidate, t.cur_module)) != 0) {
|
||||
@@ -19626,7 +19707,7 @@ fn (mut t Transformer) transform_decl_assign_stmt(id flat.NodeId, node flat.Node
|
||||
for i in 0 .. node.children_count {
|
||||
child_id := t.a.child(&node, i)
|
||||
if i == 0 || (node.children_count > 2 && i > 1) {
|
||||
child := t.a.child_node(&node, i)
|
||||
child := t.a.node(t.a.child(&node, i))
|
||||
if i == 0 && multi_match_decl_type_source_id != 0 && child.kind == .ident {
|
||||
lhs := t.make_ident(child.value_text())
|
||||
t.set_node_typ(int(lhs), multi_match_decl_type_source_id)
|
||||
@@ -29490,10 +29571,14 @@ fn (mut t Transformer) new_global_temp(prefix string) string {
|
||||
// make_ident builds make ident data for transform.
|
||||
pub fn (mut t Transformer) make_ident(name string) flat.NodeId {
|
||||
id := t.a.add_val(.ident, name)
|
||||
typ_source_id_6 := t.var_type(name)
|
||||
|
||||
if typ_source_id_6 != 0 {
|
||||
t.set_node_typ(int(id), typ_source_id_6)
|
||||
typ := t.var_type(name)
|
||||
if typ != 0 {
|
||||
// This fresh leaf has no cached ancestors. Keep the body memo while
|
||||
// preserving checker invalidation for recycled speculative node ids.
|
||||
t.a.nodes[int(id)].typ = typ
|
||||
if !isnil(t.tc) {
|
||||
t.tc.invalidate_checked_expr_type(int(id))
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
@@ -31147,7 +31232,7 @@ fn (t &Transformer) const_owned_expr_type(name string, node_typ_id u32) u32 {
|
||||
|
||||
// const_type_key supports const type key handling for Transformer.
|
||||
fn (t &Transformer) const_type_key(name string) ?string {
|
||||
if name == '' || isnil(t.tc) {
|
||||
if name == '' || isnil(t.tc) || !t.const_selector_may_match(name) {
|
||||
return none
|
||||
}
|
||||
if name in t.tc.const_types {
|
||||
@@ -31162,7 +31247,7 @@ fn (t &Transformer) const_type_key(name string) ?string {
|
||||
}
|
||||
|
||||
fn (t &Transformer) const_type_key_in_context(name string, module_name string, file string) ?string {
|
||||
if name == '' || isnil(t.tc) {
|
||||
if name == '' || isnil(t.tc) || !t.const_selector_may_match(name) {
|
||||
return none
|
||||
}
|
||||
if !name.contains('.') {
|
||||
|
||||
@@ -2360,10 +2360,10 @@ fn (mut t Transformer) promote_scoped_result_text(value string) string {
|
||||
// strings before adding a worker-local entry; generic specialization clones
|
||||
// otherwise retain another copy of almost every source identifier and type.
|
||||
mut canonical := ''
|
||||
if promoted := t.scoped_promoted_texts[value] {
|
||||
canonical = promoted
|
||||
} else if id := t.a.text_ids[value] {
|
||||
if id := t.a.text_ids[value] {
|
||||
canonical = t.a.text_values[int(id) - 1]
|
||||
} else if promoted := t.scoped_promoted_texts[value] {
|
||||
canonical = promoted
|
||||
} else {
|
||||
publication_state := transform_stage_scope_suspend(t.merge_scratch_scope)
|
||||
canonical = value.clone()
|
||||
|
||||
@@ -756,38 +756,43 @@ fn (t &Transformer) resolve_selector_type_uncached(node flat.Node) u32 {
|
||||
if field_name.len == 0 {
|
||||
return u32(0)
|
||||
}
|
||||
base_selector_name := t.selector_expr_name(base_id)
|
||||
if base_selector_name.len > 0 {
|
||||
if enum_type_source_id := t.enum_type_name_from_selector_name(base_selector_name) {
|
||||
enum_type_source_id_2 := enum_type_source_id
|
||||
enum_type := flat.source_type_name(enum_type_source_id_2)
|
||||
enum_may_match := t.enum_selector_may_match(field_name)
|
||||
if enum_may_match {
|
||||
base_selector_name := t.selector_expr_name(base_id)
|
||||
if base_selector_name.len > 0 {
|
||||
if enum_type_source_id := t.enum_type_name_from_selector_name(base_selector_name) {
|
||||
enum_type_source_id_2 := enum_type_source_id
|
||||
enum_type := flat.source_type_name(enum_type_source_id_2)
|
||||
|
||||
if fields := t.enum_types[enum_type] {
|
||||
if field_name in fields {
|
||||
return enum_type_source_id_2
|
||||
if fields := t.enum_types[enum_type] {
|
||||
if field_name in fields {
|
||||
return enum_type_source_id_2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if type_name_source_id := t.qualified_enum_type_selector_name_from_selector_name(base_selector_name, field_name) {
|
||||
type_name_source_id_2 := type_name_source_id
|
||||
if type_name_source_id := t.qualified_enum_type_selector_name_from_selector_name(base_selector_name, field_name) {
|
||||
type_name_source_id_2 := type_name_source_id
|
||||
|
||||
return type_name_source_id_2
|
||||
return type_name_source_id_2
|
||||
}
|
||||
}
|
||||
}
|
||||
if base_node.kind == .ident {
|
||||
if typ_source_id := t.const_type_name('${base_node.value}.${field_name}') {
|
||||
return typ_source_id
|
||||
}
|
||||
if fields := t.enum_types[base_node.value] {
|
||||
if field_name in fields {
|
||||
return base_node.source_value_id()
|
||||
}
|
||||
}
|
||||
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
|
||||
qenum := '${t.cur_module}.${base_node.value}'
|
||||
if fields := t.enum_types[qenum] {
|
||||
if enum_may_match {
|
||||
if fields := t.enum_types[base_node.value] {
|
||||
if field_name in fields {
|
||||
return flat.source_type_id(qenum)
|
||||
return base_node.source_value_id()
|
||||
}
|
||||
}
|
||||
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
|
||||
qenum := '${t.cur_module}.${base_node.value}'
|
||||
if fields := t.enum_types[qenum] {
|
||||
if field_name in fields {
|
||||
return flat.source_type_id(qenum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2078,6 +2083,38 @@ fn (t &Transformer) normalized_node_type(id flat.NodeId) u32 {
|
||||
@[direct_array_access; inline]
|
||||
fn (t &Transformer) node_type(id flat.NodeId) u32 {
|
||||
idx := int(id)
|
||||
if idx < 0 {
|
||||
return u32(0)
|
||||
}
|
||||
// These expression types cannot depend on annotations, bindings or the
|
||||
// expected type. Resolve them before maintaining the contextual body memo.
|
||||
match t.a.nodes[idx].kind {
|
||||
.bool_literal { return flat.source_type_bool }
|
||||
.float_literal { return flat.source_type_f64 }
|
||||
.int_literal { return flat.source_type_int }
|
||||
.string_literal, .string_interp { return flat.source_type_string }
|
||||
.nil_literal { return flat.source_type_voidptr }
|
||||
.char_literal {
|
||||
return if t.a.nodes[idx].value.starts_with('c:') {
|
||||
flat.source_type_id('&u8')
|
||||
} else {
|
||||
flat.source_type_rune
|
||||
}
|
||||
}
|
||||
.sizeof_expr, .offsetof_expr { return flat.source_type_u32 }
|
||||
.prefix {
|
||||
if t.a.nodes[idx].children_count > 0 && t.a.nodes[idx].op == .not {
|
||||
return flat.source_type_bool
|
||||
}
|
||||
}
|
||||
.infix {
|
||||
if t.a.nodes[idx].children_count >= 2
|
||||
&& t.a.nodes[idx].op in [.eq, .ne, .lt, .gt, .le, .ge, .logical_and, .logical_or] {
|
||||
return flat.source_type_bool
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
if !isnil(t.node_type_memo) {
|
||||
mut memo := t.node_type_memo
|
||||
if memo.active && idx >= memo.lo && idx <= memo.hi {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
module transform
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
fn used_name_reference(mut t Transformer, name string) {
|
||||
if name == '' {
|
||||
return
|
||||
}
|
||||
lowered := if isnil(t.tc) { c_name(name) } else { t.tc.cached_c_name(name) }
|
||||
if !t.used_fn_contains_name(name) && !t.used_fn_contains_name(lowered)
|
||||
&& t.enum_method_name_shadows_field(name) {
|
||||
return
|
||||
}
|
||||
t.mark_used_fn_key(name)
|
||||
t.mark_used_fn_key(lowered)
|
||||
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
|
||||
needs_module_prefix := !name.contains('.') || local_method_fn_name_needs_module_prefix(name)
|
||||
if needs_module_prefix && !name.starts_with('${t.cur_module}.') {
|
||||
qname := '${t.cur_module}.${name}'
|
||||
t.mark_used_fn_key(qname)
|
||||
t.mark_used_fn_key(if isnil(t.tc) { c_name(qname) } else { t.tc.cached_c_name(qname) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_used_name_probes_preserve_spellings_overlays_and_log_order() {
|
||||
for module_name in ['', 'main', 'builtin', 'demo'] {
|
||||
for logging in [false, true] {
|
||||
for state in 0 .. 8 {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := types.TypeChecker.new(&a)
|
||||
mut slow := new_transformer(mut a, &tc, map[string]bool{})
|
||||
mut fast := new_transformer(mut a, &tc, map[string]bool{})
|
||||
mut parent := map[string]bool{}
|
||||
mut root := map[string]bool{}
|
||||
if state & 1 != 0 {
|
||||
parent['demo.call'] = true
|
||||
parent['helper'] = true
|
||||
}
|
||||
if state & 2 != 0 {
|
||||
root['demo__call'] = true
|
||||
root['Color.red'] = true
|
||||
}
|
||||
if state & 4 != 0 {
|
||||
slow.used_fns['malloc'] = true
|
||||
fast.used_fns['malloc'] = true
|
||||
}
|
||||
slow.used_fns_parent = unsafe { &parent }
|
||||
fast.used_fns_parent = unsafe { &parent }
|
||||
slow.used_fns_root = unsafe { &root }
|
||||
fast.used_fns_root = unsafe { &root }
|
||||
slow.cur_module = module_name
|
||||
fast.cur_module = module_name
|
||||
slow.used_fns_log_active = logging
|
||||
fast.used_fns_log_active = logging
|
||||
slow.enum_types['Color'] = ['red', 'blue']
|
||||
fast.enum_types['Color'] = ['red', 'blue']
|
||||
for name in ['', 'helper', 'demo.call', 'malloc', 'int_str', 'exit', 'Color.red',
|
||||
'Color.method', 'word__value', 'Box.+', 'helper'] {
|
||||
used_name_reference(mut slow, name)
|
||||
fast.mark_fn_used_name(name)
|
||||
assert fast.used_fns == slow.used_fns, '${module_name}:${state}:${name}'
|
||||
assert fast.used_fns_log == slow.used_fns_log, '${module_name}:${state}:${name}'
|
||||
}
|
||||
// A parent's additions remain visible on later invocations.
|
||||
parent['later'] = true
|
||||
used_name_reference(mut slow, 'later')
|
||||
fast.mark_fn_used_name('later')
|
||||
assert fast.used_fns == slow.used_fns
|
||||
assert fast.used_fns_log == slow.used_fns_log
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,11 @@ Dense cache entries retain their canonical references. Sparse cache and function
|
||||
receive stable descriptor copies whose nested metadata stays shared. Keep either reference only
|
||||
while its owning checker arena remains live; use `clone_owned_type` across arena boundaries.
|
||||
|
||||
`TypeChecker.resolve_type_reference(node)` returns `resolve_type`'s result as a canonical
|
||||
reference. It observes the same checked-cache visibility and mixed-integer widening rules.
|
||||
The reference remains valid while its owning checker arena is live; use `clone_owned_type`
|
||||
when retaining the result beyond that arena.
|
||||
|
||||
Transform stages register generated return signatures with
|
||||
`TypeChecker.register_generated_fn_return_type(name, ret)` and remove them with
|
||||
`TypeChecker.unregister_generated_fn_return_type(name)`. Both increment the return-signature
|
||||
|
||||
+191
-41
@@ -474,6 +474,7 @@ mut:
|
||||
// SumVariantPatternFileEntries owns the module tables for one source file.
|
||||
@[heap]
|
||||
struct SumVariantPatternFileEntries {
|
||||
file string
|
||||
mut:
|
||||
modules map[string]&SumVariantPatternModuleEntries
|
||||
}
|
||||
@@ -481,6 +482,8 @@ mut:
|
||||
// SumVariantPatternModuleEntries owns the numeric source-type pairs in one context.
|
||||
@[heap]
|
||||
struct SumVariantPatternModuleEntries {
|
||||
file string
|
||||
module string
|
||||
mut:
|
||||
values map[u64]u32
|
||||
}
|
||||
@@ -584,6 +587,9 @@ mut:
|
||||
source_error_embed_shared bool
|
||||
ierror_impl_names flat.TypeTextList
|
||||
ierror_impl_names_set bool
|
||||
// Directory entries own these strings; a clear invalidates the recorded count.
|
||||
sum_variant_pattern_recent &SumVariantPatternModuleEntries = unsafe { nil }
|
||||
sum_variant_pattern_recent_count int
|
||||
// short type name -> unique qualified name ('' = ambiguous); built lazily
|
||||
// by unique_qualified_type_name from the five type-name maps.
|
||||
short_type_name_index flat.TypeTextBindings
|
||||
@@ -707,6 +713,12 @@ mut:
|
||||
// Import-aware views keyed by source file, reused across every declaration
|
||||
// type resolved in that file instead of forking a full checker per call.
|
||||
scoped_by_file map[string]&TypeChecker
|
||||
// Synchronous calls usually remain in one source file. The pointers belong to
|
||||
// the same cache as the maps and are discarded with it at every phase reset.
|
||||
recent_unscoped_file string
|
||||
recent_unscoped &TypeChecker = unsafe { nil }
|
||||
recent_scoped_file string
|
||||
recent_scoped &TypeChecker = unsafe { nil }
|
||||
}
|
||||
|
||||
// TypeCacheStats reports semantic cache effectiveness for compiler telemetry.
|
||||
@@ -1232,6 +1244,7 @@ pub mut:
|
||||
// Read-only post-check call graph for transform forks (see
|
||||
// fork_for_parallel_transform); nil in the checker that owns the graph.
|
||||
post_check_dependencies &DependencyGraphView = unsafe { nil }
|
||||
global_store_cache &FnGlobalStoreCache = unsafe { nil }
|
||||
// call_param_flows_to_callback results computed before transform lowers the
|
||||
// bodies they read (see prepare_callback_param_flows); shared with forks.
|
||||
callback_param_flows &CallbackParamFlows = unsafe { nil }
|
||||
@@ -1289,10 +1302,14 @@ pub mut:
|
||||
// node-indexed arrays (this checker is the range's only writer) and only
|
||||
// out-of-range ids (consts, other decls' nodes) go through the private
|
||||
// sparse maps that are merged after join.
|
||||
check_range_lo int = -1
|
||||
check_range_hi int = -1
|
||||
sparse_resolved_call_names map[int]string
|
||||
sparse_resolved_fn_values map[int]string // the only fn-value name store: a few dozen entries, a node-indexed array cost ~12 MB
|
||||
check_range_lo int = -1
|
||||
check_range_hi int = -1
|
||||
sparse_resolved_call_names map[int]string
|
||||
sparse_resolved_fn_values map[int]string // the only fn-value name store: a few dozen entries, a node-indexed array cost ~12 MB
|
||||
// Only private transform/codegen snapshots enable this conservative filter.
|
||||
// Mutable masters and storage probes keep supporting direct map writes.
|
||||
sparse_fn_value_filter [4]u64
|
||||
sparse_fn_value_filter_ready bool
|
||||
fork_fn_value_writes map[int]string // a transform fork's own fn-value writes ('' = cleared); its sparse map stays a read-only snapshot and only these are merged
|
||||
sparse_statement_nodes map[int]bool
|
||||
sparse_expr_type_values map[int]Type
|
||||
@@ -1340,6 +1357,8 @@ pub mut:
|
||||
alias_short_names map[string]bool
|
||||
alias_short_names_count int = -1
|
||||
const_suffix_key_count int = -1
|
||||
const_selector_bloom NameBloom // of const_types and const_suffixes
|
||||
const_selector_suffix_count int = -1
|
||||
const_expr_short_names map[string]bool
|
||||
const_expr_short_names_count int = -1
|
||||
global_name_bloom NameBloom // of global_names
|
||||
@@ -1368,12 +1387,14 @@ pub mut:
|
||||
import_line_indexed_files map[int]bool
|
||||
source_texts_by_file map[string]string
|
||||
source_texts_by_id map[int]string
|
||||
ct_update_pos map[int]token.Pos
|
||||
ct_update_indexed bool
|
||||
insert_include_dirs_by_file map[string][]string
|
||||
has_spawn_expr int = -1
|
||||
inactive_top_level_node_ids []i32
|
||||
selected_file_called_fns map[string]bool
|
||||
// Zero means unindexed; one rules out the exceptional `cosnt` diagnostic.
|
||||
malformed_const_source_states []u8
|
||||
ct_update_pos map[int]token.Pos
|
||||
ct_update_indexed bool
|
||||
insert_include_dirs_by_file map[string][]string
|
||||
has_spawn_expr int = -1
|
||||
inactive_top_level_node_ids []i32
|
||||
selected_file_called_fns map[string]bool
|
||||
// Names newly inserted into selected_file_called_fns and not yet chased by
|
||||
// the transitive closure in collect_selected_file_called_fns_transitively.
|
||||
selected_file_worklist []string
|
||||
@@ -1802,6 +1823,8 @@ fn (tc &TypeChecker) fork_program_view(ast &flat.FlatAst, direct_dependencies_by
|
||||
const_files: tc.const_files
|
||||
const_suffixes: tc.const_suffixes
|
||||
const_suffix_key_count: tc.const_suffix_key_count
|
||||
const_selector_bloom: tc.const_selector_bloom
|
||||
const_selector_suffix_count: tc.const_selector_suffix_count
|
||||
const_expr_short_names: tc.const_expr_short_names
|
||||
const_expr_short_names_count: tc.const_expr_short_names_count
|
||||
global_short_names: tc.global_short_names
|
||||
@@ -1881,6 +1904,7 @@ fn (tc &TypeChecker) fork_program_view(ast &flat.FlatAst, direct_dependencies_by
|
||||
multiple_module_import_lines: tc.multiple_module_import_lines
|
||||
source_texts_by_file: tc.source_texts_by_file
|
||||
source_texts_by_id: tc.source_texts_by_id
|
||||
malformed_const_source_states: tc.malformed_const_source_states
|
||||
ct_update_pos: tc.ct_update_pos
|
||||
ct_update_indexed: tc.ct_update_indexed
|
||||
has_spawn_expr: tc.has_spawn_expr
|
||||
@@ -1973,6 +1997,7 @@ pub fn (tc &TypeChecker) fork_for_parallel_codegen() &TypeChecker {
|
||||
// while cgen workers read (a type query on the declaration task can record
|
||||
// a resolution), so each worker reads a private snapshot of its few entries.
|
||||
forked.sparse_resolved_fn_values = tc.fn_value_snapshot()
|
||||
forked.rebuild_sparse_fn_value_filter()
|
||||
forked.visible_mutation_cache = unsafe { nil }
|
||||
forked.inherit_ownership_codegen_metadata_from(tc)
|
||||
forked.set_fresh_type_cache_based_on(tc, tc.type_cache_parse_enabled())
|
||||
@@ -2046,6 +2071,8 @@ pub fn (tc &TypeChecker) fork_for_parallel_transform(ast &flat.FlatAst) &TypeChe
|
||||
// Visible-mutation analysis only runs during checking. Do not allocate its
|
||||
// three private maps for the many short-lived transform forks.
|
||||
forked.visible_mutation_cache = unsafe { nil }
|
||||
// Completed root queries are private to this immutable graph view.
|
||||
forked.global_store_cache = &FnGlobalStoreCache{}
|
||||
if check_memos_enabled() {
|
||||
// Per-fork memos; the fork (and these caches) live inside one batch
|
||||
// scope, so cached scratch-arena strings can never outlive their arena.
|
||||
@@ -2068,6 +2095,7 @@ pub fn (tc &TypeChecker) fork_for_parallel_transform(ast &flat.FlatAst) &TypeChe
|
||||
// Transformer.merge_worker / absorb_scoped_batch replay into the parent
|
||||
// through apply_forked_fn_value.
|
||||
forked.sparse_resolved_fn_values = tc.fn_value_snapshot()
|
||||
forked.rebuild_sparse_fn_value_filter()
|
||||
forked.fork_fn_value_writes = map[int]string{}
|
||||
// Transform helpers allocate inside disposable arenas. A shared interner
|
||||
// would let one helper publish map/array storage owned by its arena and leave
|
||||
@@ -2083,8 +2111,9 @@ pub fn (tc &TypeChecker) fork_for_parallel_transform(ast &flat.FlatAst) &TypeChe
|
||||
tc.post_check_dependencies
|
||||
} else {
|
||||
&DependencyGraphView{
|
||||
by_fn: tc.direct_dependencies_by_fn
|
||||
names: if isnil(tc.symbols) { []string{} } else { tc.symbols.names }
|
||||
by_fn: tc.direct_dependencies_by_fn
|
||||
names: if isnil(tc.symbols) { []string{} } else { tc.symbols.names }
|
||||
declarations: tc.visible_mutation_cache
|
||||
}
|
||||
}
|
||||
forked.type_cache = new_type_cache_with_base(if tc.type_cache != unsafe { nil } {
|
||||
@@ -2106,6 +2135,10 @@ pub fn (tc &TypeChecker) fork_for_parallel_transform(ast &flat.FlatAst) &TypeChe
|
||||
// transform worker writes them. The worker's private maps are merged while its
|
||||
// disposable arena is still alive.
|
||||
pub fn (mut tc TypeChecker) ensure_private_transform_signatures() {
|
||||
if !isnil(tc.global_store_cache) {
|
||||
mut cache := tc.global_store_cache
|
||||
cache.values.clear()
|
||||
}
|
||||
if tc.transform_signature_maps_shared {
|
||||
tc.fn_ret_types = tc.fn_ret_types.clone()
|
||||
tc.fn_param_types = tc.fn_param_types.clone()
|
||||
@@ -2391,6 +2424,7 @@ fn (mut tc TypeChecker) reset_node_caches(n int) {
|
||||
// both the serial and the parallel cache initialization.
|
||||
fn (mut tc TypeChecker) reset_sparse_fn_values() {
|
||||
tc.sparse_resolved_fn_values.clear()
|
||||
tc.sparse_fn_value_filter_ready = false
|
||||
tc.fork_fn_value_writes.clear()
|
||||
}
|
||||
|
||||
@@ -5773,6 +5807,16 @@ fn (mut tc TypeChecker) build_const_suffixes() {
|
||||
}
|
||||
}
|
||||
tc.const_suffix_key_count = tc.const_types.len
|
||||
// Include the complete existing suffix table, including ambiguous suffixes.
|
||||
tc.const_selector_bloom = NameBloom{}
|
||||
for name, _ in tc.const_types {
|
||||
tc.const_selector_bloom.add(name)
|
||||
}
|
||||
for name, _ in tc.const_suffixes {
|
||||
tc.const_selector_bloom.add(name)
|
||||
}
|
||||
tc.const_selector_bloom.count = u64(tc.const_types.len)
|
||||
tc.const_selector_suffix_count = tc.const_suffixes.len
|
||||
}
|
||||
|
||||
// prepare_check_name_filters takes short names of globals, constants, sum types and aliases
|
||||
@@ -5916,8 +5960,14 @@ fn generic_base_view(name string) string {
|
||||
// segment and no bare const key, so no selector with this field names a const.
|
||||
@[inline]
|
||||
fn (tc &TypeChecker) selector_names_no_const(field string) bool {
|
||||
return tc.const_suffix_key_count == tc.const_types.len && field !in tc.const_suffixes
|
||||
&& field !in tc.const_types
|
||||
if tc.const_suffix_key_count != tc.const_types.len {
|
||||
return false
|
||||
}
|
||||
if tc.const_selector_suffix_count == tc.const_suffixes.len
|
||||
&& tc.const_selector_bloom.excludes(field, u64(tc.const_types.len)) {
|
||||
return true
|
||||
}
|
||||
return field !in tc.const_suffixes && field !in tc.const_types
|
||||
}
|
||||
|
||||
// selector_names_no_global reports that no global has the last name segment of
|
||||
@@ -6884,12 +6934,19 @@ pub fn (tc &TypeChecker) parse_resolution_type(source_id u32) Type {
|
||||
return *direct_view.parse_type(qualified_source_id)
|
||||
}
|
||||
mut views := unsafe { tc.resolution_type_views }
|
||||
if !isnil(views.recent_unscoped) && views.recent_unscoped_file == tc.cur_file {
|
||||
return *views.recent_unscoped.parse_type(qualified_source_id)
|
||||
}
|
||||
if cached := views.by_file[tc.cur_file] {
|
||||
views.recent_unscoped_file = tc.cur_file
|
||||
views.recent_unscoped = cached
|
||||
return *cached.parse_type(qualified_source_id)
|
||||
}
|
||||
mut unscoped := tc.fork_type_parse_view(tc.cur_file, '')
|
||||
unscoped.resolution_type_mode = false
|
||||
views.by_file[tc.cur_file] = unscoped
|
||||
views.recent_unscoped_file = tc.cur_file
|
||||
views.recent_unscoped = unscoped
|
||||
return *unscoped.parse_type(qualified_source_id)
|
||||
}
|
||||
|
||||
@@ -6899,16 +6956,24 @@ pub fn (tc &TypeChecker) parse_resolution_type_in_file(source_id u32, file strin
|
||||
if context_independent_type(source_id) {
|
||||
return *tc.parse_type(source_id)
|
||||
}
|
||||
module_name := tc.file_modules[file] or { '' }
|
||||
if !isnil(tc.resolution_type_views) {
|
||||
mut views := unsafe { tc.resolution_type_views }
|
||||
if !isnil(views.recent_scoped) && views.recent_scoped_file == file {
|
||||
return views.recent_scoped.parse_resolution_type(source_id)
|
||||
}
|
||||
if cached := views.scoped_by_file[file] {
|
||||
views.recent_scoped_file = file
|
||||
views.recent_scoped = cached
|
||||
return cached.parse_resolution_type(source_id)
|
||||
}
|
||||
module_name := tc.file_modules[file] or { '' }
|
||||
mut scoped := tc.fork_type_parse_view(file, module_name)
|
||||
views.scoped_by_file[file] = scoped
|
||||
views.recent_scoped_file = file
|
||||
views.recent_scoped = scoped
|
||||
return scoped.parse_resolution_type(source_id)
|
||||
}
|
||||
module_name := tc.file_modules[file] or { '' }
|
||||
mut scoped := tc.fork_type_parse_view(file, module_name)
|
||||
return scoped.parse_resolution_type(source_id)
|
||||
}
|
||||
@@ -8272,16 +8337,17 @@ fn (tc &TypeChecker) type_symbol_known(name string) bool {
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) unqualified_type_symbol_is_builtin(name string) bool {
|
||||
if tc.unqualified_type_symbol_has_scoped_shadow(name) {
|
||||
return false
|
||||
}
|
||||
if mod_name := tc.struct_modules[name] {
|
||||
return mod_name == 'builtin'
|
||||
if mod_name != 'builtin' {
|
||||
return false
|
||||
}
|
||||
} else if name != 'IError' && name !in tc.enum_names && name !in tc.flag_enums {
|
||||
return false
|
||||
}
|
||||
// Builtin enum declarations use their unqualified source name, just like
|
||||
// builtin structs. Keep that name visible from imported modules unless a
|
||||
// declaration in the active scope shadows it.
|
||||
return name == 'IError' || name in tc.enum_names || name in tc.flag_enums
|
||||
return !tc.unqualified_type_symbol_has_scoped_shadow(name)
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) unqualified_type_symbol_has_scoped_shadow(name string) bool {
|
||||
@@ -8315,8 +8381,8 @@ fn (tc &TypeChecker) unqualified_resolved_type_symbol_is_builtin(name string) bo
|
||||
|
||||
fn (tc &TypeChecker) type_from_known_symbol(name_id u32) ?Type {
|
||||
name := flat.source_type_name(name_id)
|
||||
if tc.type_aliases.contains(name) {
|
||||
return tc.parse_alias_type(name_id, tc.type_aliases.value_id(name))
|
||||
if target_id := tc.type_aliases.type_id(name) {
|
||||
return tc.parse_alias_type(name_id, target_id)
|
||||
}
|
||||
if name in tc.structs {
|
||||
return Type(Struct{
|
||||
@@ -10204,7 +10270,7 @@ fn (tc &TypeChecker) builtin_array_annotation_expected_type(node flat.Node, arg_
|
||||
if node.children_count == 0 {
|
||||
return builtin_void_type
|
||||
}
|
||||
mut callee := tc.a.child_node(&node, 0)
|
||||
mut callee := tc.a.node(tc.a.child(&node, 0))
|
||||
if callee.kind == .index && callee.children_count > 0 {
|
||||
callee = tc.a.child_node(callee, 0)
|
||||
}
|
||||
@@ -11097,8 +11163,7 @@ pub fn (tc &TypeChecker) resolved_call_name(id flat.NodeId) ?string {
|
||||
// transitive callees belongs to a source file annotated with `@[has_globals]` or `@[translated]`.
|
||||
pub fn (tc &TypeChecker) resolved_call_may_store_globally(id flat.NodeId) bool {
|
||||
name := tc.cached_resolved_call(id) or { return false }
|
||||
mut visiting := map[string]bool{}
|
||||
return tc.fn_may_store_globally(name, mut visiting)
|
||||
return tc.cached_fn_may_store_globally(name)
|
||||
}
|
||||
|
||||
// fn_value_may_store_globally reports whether invoking a function value can reach a
|
||||
@@ -11106,6 +11171,22 @@ pub fn (tc &TypeChecker) resolved_call_may_store_globally(id flat.NodeId) bool {
|
||||
// value is conservative because its body is opaque at this call site.
|
||||
pub fn (tc &TypeChecker) fn_value_may_store_globally(id flat.NodeId) bool {
|
||||
name := tc.resolved_fn_value_name(id) or { return true }
|
||||
return tc.cached_fn_may_store_globally(name)
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) cached_fn_may_store_globally(name string) bool {
|
||||
if !isnil(tc.post_check_dependencies) && !isnil(tc.global_store_cache) {
|
||||
mut cache := tc.global_store_cache
|
||||
if result := cache.values[name] {
|
||||
return result
|
||||
}
|
||||
mut visiting := map[string]bool{}
|
||||
result := tc.fn_may_store_globally(name, mut visiting)
|
||||
// A recursive branch can be false only because an ancestor is
|
||||
// already visiting. Cache completed root answers, never DFS branches.
|
||||
cache.values[name] = result
|
||||
return result
|
||||
}
|
||||
mut visiting := map[string]bool{}
|
||||
return tc.fn_may_store_globally(name, mut visiting)
|
||||
}
|
||||
@@ -11120,7 +11201,7 @@ fn (tc &TypeChecker) fn_may_store_globally(name string, mut visiting map[string]
|
||||
return true
|
||||
}
|
||||
decl_module := tc.fn_type_modules[name] or { '' }
|
||||
decl := tc.visible_mutation_fn_decl(name, decl_module) or { return false }
|
||||
decl := tc.global_store_fn_decl(name, decl_module) or { return false }
|
||||
if !isnil(tc.post_check_dependencies) {
|
||||
// A transform fork: other workers may be rewriting callee bodies in place,
|
||||
// so follow only the call graph recorded while checking.
|
||||
@@ -11149,6 +11230,25 @@ fn (tc &TypeChecker) fn_may_store_globally(name string, mut visiting map[string]
|
||||
return false
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) global_store_fn_decl(name string, fallback_mod string) ?VisibleMutationFnDecl {
|
||||
if !isnil(tc.post_check_dependencies) {
|
||||
index := tc.post_check_dependencies.declarations
|
||||
if !isnil(index) && index.decl_index_ready {
|
||||
lookup_name := visible_mutation_fn_lookup_name(name)
|
||||
if decl := index.global_decls[lookup_name] {
|
||||
if fallback_mod == '' || fallback_mod == decl.mod {
|
||||
return decl
|
||||
}
|
||||
}
|
||||
if decl := index.decls['${fallback_mod}\x01${lookup_name}'] {
|
||||
return decl
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generated declarations may not exist in the frozen source index.
|
||||
return tc.visible_mutation_fn_decl(name, fallback_mod)
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) node_calls_fn_that_may_store_globally(id flat.NodeId, caller_mod string, mut visiting map[string]bool) bool {
|
||||
if int(id) < 0 || int(id) >= tc.a.nodes.len {
|
||||
return false
|
||||
@@ -11258,21 +11358,21 @@ fn (tc &TypeChecker) name_never_returns(name string) bool {
|
||||
if name in tc.a.noreturn_fns {
|
||||
return true
|
||||
}
|
||||
// Only dotted suffixes can match a qualified declaration. Probe those few
|
||||
// names instead of scanning every noreturn declaration for every call.
|
||||
mut has_dot := false
|
||||
for i := name.len - 1; i >= 0; i-- {
|
||||
if name[i] != `.` {
|
||||
continue
|
||||
// Only dotted suffixes can match a qualified declaration. Search complete
|
||||
// words for each preceding dot instead of examining every name byte here.
|
||||
mut end := name.last_index_u8(`.`)
|
||||
for end > 0 {
|
||||
// Both searches and the membership query only borrow these spans.
|
||||
prefix := unsafe { name.substr_unsafe(0, end) }
|
||||
dot := prefix.last_index_u8(`.`)
|
||||
if dot < 0 {
|
||||
break
|
||||
}
|
||||
if has_dot {
|
||||
// The membership query does not retain the suffix.
|
||||
suffix := unsafe { name.substr_unsafe(i + 1, name.len) }
|
||||
if tc.a.noreturn_fns[suffix] {
|
||||
return true
|
||||
}
|
||||
suffix := unsafe { name.substr_unsafe(dot + 1, name.len) }
|
||||
if tc.a.noreturn_fns[suffix] {
|
||||
return true
|
||||
}
|
||||
has_dot = true
|
||||
end = dot
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -11312,9 +11412,40 @@ pub fn (tc &TypeChecker) resolved_fn_value_name(id flat.NodeId) ?string {
|
||||
return name
|
||||
}
|
||||
}
|
||||
if tc.sparse_fn_value_filter_ready {
|
||||
bit := sparse_fn_value_filter_bit(idx)
|
||||
if tc.sparse_fn_value_filter[bit >> 6] & (u64(1) << (bit & 63)) == 0 {
|
||||
return none
|
||||
}
|
||||
}
|
||||
return tc.sparse_resolved_fn_values[idx] or { none }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn sparse_fn_value_filter_bit(idx int) u32 {
|
||||
key := u32(idx)
|
||||
return (key ^ (key >> 8)) & 255
|
||||
}
|
||||
|
||||
@[direct_array_access; inline]
|
||||
fn (mut tc TypeChecker) record_sparse_fn_value_filter_key(idx int) {
|
||||
if tc.sparse_fn_value_filter_ready {
|
||||
bit := sparse_fn_value_filter_bit(idx)
|
||||
tc.sparse_fn_value_filter[bit >> 6] |= u64(1) << (bit & 63)
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild_sparse_fn_value_filter runs only after taking an owned snapshot.
|
||||
// Fork source maps are otherwise read-only; later setters maintain the filter,
|
||||
// and deletions may retain bits because a false positive still checks the map.
|
||||
fn (mut tc TypeChecker) rebuild_sparse_fn_value_filter() {
|
||||
tc.sparse_fn_value_filter = [4]u64{}
|
||||
tc.sparse_fn_value_filter_ready = true
|
||||
for idx, _ in tc.sparse_resolved_fn_values {
|
||||
tc.record_sparse_fn_value_filter_key(idx)
|
||||
}
|
||||
}
|
||||
|
||||
// set_resolved_fn_value records the resolved function-value target of a node.
|
||||
// A transform fork keeps the write private (its snapshot of the parent stays
|
||||
// read-only) until the merge replays it.
|
||||
@@ -11327,6 +11458,7 @@ pub fn (mut tc TypeChecker) set_resolved_fn_value(idx int, name string) {
|
||||
return
|
||||
}
|
||||
tc.sparse_resolved_fn_values[idx] = name
|
||||
tc.record_sparse_fn_value_filter_key(idx)
|
||||
}
|
||||
|
||||
// clear_resolved_fn_value removes stale function-value metadata after a later
|
||||
@@ -11403,8 +11535,15 @@ pub fn (mut tc TypeChecker) apply_forked_fn_value(idx int, name string) {
|
||||
// DependencyGraphView is the checker's post-check call graph with the symbol
|
||||
// names its ids refer to. Transform forks share it read-only.
|
||||
struct DependencyGraphView {
|
||||
by_fn map[int][]SymbolId
|
||||
names []string
|
||||
by_fn map[int][]SymbolId
|
||||
names []string
|
||||
declarations &VisibleMutationCache = unsafe { nil }
|
||||
}
|
||||
|
||||
@[heap]
|
||||
struct FnGlobalStoreCache {
|
||||
mut:
|
||||
values map[string]bool
|
||||
}
|
||||
|
||||
// direct_dependency_ids returns the checker-resolved function dependency
|
||||
@@ -13358,6 +13497,11 @@ fn (tc &TypeChecker) malformed_const_keyword_pos(id flat.NodeId) ?token.Pos {
|
||||
return none
|
||||
}
|
||||
pos := tc.a.node(id).pos
|
||||
file_id := int(pos.id)
|
||||
if file_id >= 0 && file_id < tc.malformed_const_source_states.len
|
||||
&& tc.malformed_const_source_states[file_id] == 1 {
|
||||
return none
|
||||
}
|
||||
file := tc.a.source_files[pos.id] or { return none }
|
||||
source := tc.source_texts_by_file[file.name] or { return none }
|
||||
if pos.offset < 0 || pos.offset > source.len {
|
||||
@@ -20758,6 +20902,12 @@ fn (tc &TypeChecker) match_branch_definitely_returns(branch &flat.Node) bool {
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) stmt_sequence_definitely_returns(node &flat.Node, body_start int) bool {
|
||||
if body_start < node.children_count {
|
||||
tail_id := tc.a.child(node, int(node.children_count) - 1)
|
||||
if tc.valid_node_id(tail_id) && tc.a.nodes[int(tail_id)].kind == .return_stmt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Only the `smartcasts` binding needs isolation here (see
|
||||
// branch_tail_never_returns); avoid the ~11KB full struct copy.
|
||||
mut mtc := unsafe { &TypeChecker(voidptr(tc)) }
|
||||
|
||||
@@ -5714,6 +5714,7 @@ fn (mut tc TypeChecker) merge_parallel_check_worker_scoped(w &TypeChecker, scope
|
||||
for idx, name in w.sparse_resolved_fn_values {
|
||||
owned_name := if scoped { name.clone() } else { name }
|
||||
tc.sparse_resolved_fn_values[idx] = owned_name
|
||||
tc.record_sparse_fn_value_filter_key(idx)
|
||||
}
|
||||
for idx, _ in w.sparse_statement_nodes {
|
||||
if tc.parallel_check_sparse {
|
||||
|
||||
+44
-12
@@ -5610,7 +5610,7 @@ fn (tc &TypeChecker) translated_named_cast_call_name(id flat.NodeId, node flat.N
|
||||
if node.children_count != 2 || !tc.node_is_in_translated_file(id) {
|
||||
return none
|
||||
}
|
||||
callee := tc.a.child_node(&node, 0)
|
||||
callee := tc.a.node(tc.a.child(&node, 0))
|
||||
if callee.kind != .ident || tc.ident_resolves_to_value(callee.value) {
|
||||
return none
|
||||
}
|
||||
@@ -6921,7 +6921,7 @@ fn (tc &TypeChecker) unknown_import_function_call_parts(node flat.Node) ?(flat.N
|
||||
if node.kind != .call || node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
callee := tc.a.child_node(&node, 0)
|
||||
callee := tc.a.node(tc.a.child(&node, 0))
|
||||
if callee.kind != .selector || callee.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
@@ -7381,7 +7381,7 @@ fn (tc &TypeChecker) explicit_generic_local_fn_value(node flat.Node) ?FnType {
|
||||
if node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
callee := tc.a.child_node(&node, 0)
|
||||
callee := tc.a.node(tc.a.child(&node, 0))
|
||||
if callee.kind != .index || callee.children_count < 2 {
|
||||
return none
|
||||
}
|
||||
@@ -7477,7 +7477,7 @@ fn (tc &TypeChecker) deprecated_call_diagnostic_pos(node flat.Node, is_method bo
|
||||
if !is_method || node.children_count == 0 {
|
||||
return node.pos
|
||||
}
|
||||
selector := tc.a.child_node(&node, 0)
|
||||
selector := tc.a.node(tc.a.child(&node, 0))
|
||||
if selector.kind != .selector {
|
||||
return node.pos
|
||||
}
|
||||
@@ -9087,6 +9087,7 @@ fn (mut tc TypeChecker) index_multiple_module_import_lines(a &flat.FlatAst) {
|
||||
tc.multiple_module_import_lines = map[u64]bool{}
|
||||
tc.source_texts_by_file = map[string]string{}
|
||||
tc.source_texts_by_id = map[int]string{}
|
||||
tc.malformed_const_source_states = []u8{}
|
||||
tc.import_line_indexed_files = map[int]bool{}
|
||||
tc.index_module_import_lines_of_new_files(a)
|
||||
}
|
||||
@@ -9095,11 +9096,18 @@ fn (mut tc TypeChecker) index_multiple_module_import_lines(a &flat.FlatAst) {
|
||||
// not indexed yet.
|
||||
fn (mut tc TypeChecker) index_module_import_lines_of_new_files(a &flat.FlatAst) {
|
||||
mut scans := []ImportLineScan{}
|
||||
mut dense_file_id := tc.malformed_const_source_states.len - 1
|
||||
// Parser file ids are dense. Sparse ids supplied by tools retain the fallback
|
||||
// rather than making this diagnostic filter allocate an oversized table.
|
||||
dense_limit := a.source_files.len * 2 + 64
|
||||
for file_id, file in a.source_files {
|
||||
if tc.import_line_indexed_files[file_id] {
|
||||
continue
|
||||
}
|
||||
tc.import_line_indexed_files[file_id] = true
|
||||
if file_id >= 0 && file_id <= dense_limit && file_id > dense_file_id {
|
||||
dense_file_id = file_id
|
||||
}
|
||||
// The parser may have kept the text it read (flat.FlatAst.source_texts).
|
||||
has_source := file_id in a.source_texts
|
||||
scans << ImportLineScan{
|
||||
@@ -9109,6 +9117,9 @@ fn (mut tc TypeChecker) index_module_import_lines_of_new_files(a &flat.FlatAst)
|
||||
source: if has_source { a.source_texts[file_id] } else { '' }
|
||||
}
|
||||
}
|
||||
if dense_file_id >= tc.malformed_const_source_states.len {
|
||||
tc.malformed_const_source_states << []u8{len: dense_file_id + 1 - tc.malformed_const_source_states.len}
|
||||
}
|
||||
// Reading and scanning one file does not depend on the others. The pool's
|
||||
// threads keep what they allocate, so the texts outlive the batch.
|
||||
pool := checker_worker_pool(a)
|
||||
@@ -9137,16 +9148,37 @@ fn (mut tc TypeChecker) index_module_import_lines_of_new_files(a &flat.FlatAst)
|
||||
scan.run()
|
||||
}
|
||||
}
|
||||
mut duplicate_names := map[string]bool{}
|
||||
for scan in scans {
|
||||
if !scan.read {
|
||||
continue
|
||||
}
|
||||
if scan.name in tc.source_texts_by_file {
|
||||
duplicate_names[scan.name] = true
|
||||
}
|
||||
tc.source_texts_by_file[scan.name] = scan.source
|
||||
tc.source_texts_by_id[scan.file_id] = scan.source
|
||||
if scan.file_id >= 0 && scan.file_id < tc.malformed_const_source_states.len {
|
||||
tc.malformed_const_source_states[scan.file_id] = if scan.source.contains('cosnt') {
|
||||
u8(2)
|
||||
} else {
|
||||
u8(1)
|
||||
}
|
||||
}
|
||||
for line in scan.lines {
|
||||
tc.multiple_module_import_lines[multiple_module_import_line_key(scan.file_id, line)] = true
|
||||
}
|
||||
}
|
||||
if duplicate_names.len > 0 {
|
||||
// The diagnostic reads by filename. Different ids for one filename can
|
||||
// therefore see another id's text, including an earlier indexed file.
|
||||
for file_id, file in a.source_files {
|
||||
if file_id >= 0 && file_id < tc.malformed_const_source_states.len
|
||||
&& duplicate_names[file.name] {
|
||||
tc.malformed_const_source_states[file_id] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ImportLineScan is the source text of one file and the lines of it that
|
||||
@@ -9377,7 +9409,7 @@ fn (mut tc TypeChecker) resolve_call_info_uncached(id flat.NodeId, node flat.Nod
|
||||
if node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
fn_node := tc.a.child_node(&node, 0)
|
||||
fn_node := tc.a.node(tc.a.child(&node, 0))
|
||||
if fn_node.kind == .selector && '__v3_comptime_method_selector' in fn_node.generic_params()
|
||||
&& fn_node.typ != 0 {
|
||||
return CallInfo{
|
||||
@@ -14924,7 +14956,7 @@ fn (tc &TypeChecker) visible_mutation_call_name(call_id flat.NodeId, call flat.N
|
||||
if call.children_count == 0 {
|
||||
return ''
|
||||
}
|
||||
mut fn_node := tc.a.child_node(&call, 0)
|
||||
mut fn_node := tc.a.node(tc.a.child(&call, 0))
|
||||
if fn_node.kind == .index && fn_node.children_count > 0 {
|
||||
fn_node = tc.a.child_node(fn_node, 0)
|
||||
}
|
||||
@@ -17569,7 +17601,7 @@ fn (tc &TypeChecker) call_argument_target_name(node flat.Node, info CallInfo) st
|
||||
return 'Map.delete'
|
||||
}
|
||||
if node.children_count > 0 {
|
||||
mut callee := tc.a.child_node(&node, 0)
|
||||
mut callee := tc.a.node(tc.a.child(&node, 0))
|
||||
if callee.kind == .index && callee.children_count > 0 {
|
||||
callee = tc.a.child_node(callee, 0)
|
||||
}
|
||||
@@ -17600,7 +17632,7 @@ fn (tc &TypeChecker) call_argument_target_name(node flat.Node, info CallInfo) st
|
||||
if node.children_count == 0 {
|
||||
return tc.call_display_name(node)
|
||||
}
|
||||
callee := tc.a.child_node(&node, 0)
|
||||
callee := tc.a.node(tc.a.child(&node, 0))
|
||||
if callee.kind in [.fn_literal, .lambda_expr] {
|
||||
return 'anon'
|
||||
}
|
||||
@@ -21710,7 +21742,7 @@ fn (tc &TypeChecker) direct_call_return_type(node flat.Node) ?Type {
|
||||
if node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
fn_node := tc.a.child_node(&node, 0)
|
||||
fn_node := tc.a.node(tc.a.child(&node, 0))
|
||||
if fn_node.kind == .index && fn_node.children_count >= 2 && fn_node.value != 'range' {
|
||||
base_node := tc.a.child_node(fn_node, 0)
|
||||
name := tc.generic_call_base_name(base_node) or { return none }
|
||||
@@ -21800,7 +21832,7 @@ fn (tc &TypeChecker) spawn_child_call_return_type(node flat.Node) ?Type {
|
||||
if node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
fn_node := tc.a.child_node(&node, 0)
|
||||
fn_node := tc.a.node(tc.a.child(&node, 0))
|
||||
if fn_node.kind == .selector && fn_node.children_count > 0 {
|
||||
base_type := tc.resolve_type(tc.a.child(fn_node, 0))
|
||||
clean_base := unwrap_all_pointers(base_type)
|
||||
@@ -22618,7 +22650,7 @@ fn (tc &TypeChecker) c_va_macro_call_name(node flat.Node) string {
|
||||
if node.children_count == 0 {
|
||||
return ''
|
||||
}
|
||||
callee := tc.a.child_node(&node, 0)
|
||||
callee := tc.a.node(tc.a.child(&node, 0))
|
||||
if callee.kind == .selector && callee.children_count > 0 {
|
||||
if tc.a.child_node(callee, 0).value != 'C' {
|
||||
return ''
|
||||
@@ -22646,7 +22678,7 @@ fn (tc &TypeChecker) call_display_name(node flat.Node) string {
|
||||
if node.children_count == 0 {
|
||||
return '<missing>'
|
||||
}
|
||||
fn_node := tc.a.child_node(&node, 0)
|
||||
fn_node := tc.a.node(tc.a.child(&node, 0))
|
||||
if fn_node.kind == .ident {
|
||||
return fn_node.value_text()
|
||||
}
|
||||
|
||||
@@ -5656,7 +5656,7 @@ fn (tc &TypeChecker) bound_method_node_source_type_text(node flat.Node) string {
|
||||
mut params := []string{}
|
||||
mut skipped_receiver := false
|
||||
for i in 0 .. node.children_count {
|
||||
param := tc.a.child_node(&node, i)
|
||||
param := tc.a.node(tc.a.child(&node, i))
|
||||
if param.kind != .param {
|
||||
continue
|
||||
}
|
||||
@@ -5685,7 +5685,7 @@ fn (tc &TypeChecker) bound_method_node_source_type_text(node flat.Node) string {
|
||||
fn (tc &TypeChecker) fn_node_source_type_text(node flat.Node) string {
|
||||
mut params := []string{}
|
||||
for i in 0 .. node.children_count {
|
||||
param := tc.a.child_node(&node, i)
|
||||
param := tc.a.node(tc.a.child(&node, i))
|
||||
if param.kind != .param {
|
||||
continue
|
||||
}
|
||||
@@ -6258,7 +6258,7 @@ fn (tc &TypeChecker) source_struct_field_decls(struct_name_id u32) []SourceStruc
|
||||
}
|
||||
mut fields := []SourceStructFieldDecl{}
|
||||
for i in 0 .. decl.children_count {
|
||||
field := tc.a.child_node(&decl, i)
|
||||
field := tc.a.node(tc.a.child(&decl, i))
|
||||
if field.kind != .field_decl {
|
||||
continue
|
||||
}
|
||||
@@ -7783,8 +7783,10 @@ fn (tc &TypeChecker) selector_type(_id flat.NodeId, node flat.Node) ?Type {
|
||||
if base_node.kind == .ident && base_node.value == 'C' && c_int_selector_name(node.value) {
|
||||
return builtin_int_type
|
||||
}
|
||||
if typ := tc.enum_selector_type(&node) {
|
||||
return typ
|
||||
if node.kind == .selector {
|
||||
if typ := tc.enum_selector_type_from_base(base_id, node.value) {
|
||||
return typ
|
||||
}
|
||||
}
|
||||
if base_node.kind == .typeof_expr {
|
||||
if node.value == 'name' {
|
||||
@@ -9885,7 +9887,7 @@ fn (tc &TypeChecker) fn_value_key(node flat.Node) ?string {
|
||||
return tc.selector_fn_value_key(node)
|
||||
}
|
||||
if node.kind in [.cast_expr, .paren, .expr_stmt] && node.children_count > 0 {
|
||||
return tc.fn_value_key(tc.a.child_node(&node, 0))
|
||||
return tc.fn_value_key(tc.a.node(tc.a.child(&node, 0)))
|
||||
}
|
||||
return none
|
||||
}
|
||||
@@ -10563,7 +10565,11 @@ fn (tc &TypeChecker) enum_selector_type(node &flat.Node) ?Type {
|
||||
if node.kind != .selector || node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
base := tc.a.child_node(node, 0)
|
||||
return tc.enum_selector_type_from_base(tc.a.child(node, 0), node.value)
|
||||
}
|
||||
|
||||
fn (tc &TypeChecker) enum_selector_type_from_base(base_id flat.NodeId, field string) ?Type {
|
||||
base := tc.a.node(base_id)
|
||||
mut enum_name := flat.source_type_id('')
|
||||
if base.kind == .ident {
|
||||
if !tc.ident_may_be_type(base.value) {
|
||||
@@ -10577,7 +10583,7 @@ fn (tc &TypeChecker) enum_selector_type(node &flat.Node) ?Type {
|
||||
enum_name = (tc.resolve_enum_name(flat.source_type_id('${mod_name}.${base.value}')) or { flat.source_type_id('') })
|
||||
}
|
||||
}
|
||||
if enum_name == 0 || !tc.enum_has_field(enum_name, node.value) {
|
||||
if enum_name == 0 || !tc.enum_has_field(enum_name, field) {
|
||||
return none
|
||||
}
|
||||
return Type(Enum{
|
||||
@@ -16142,27 +16148,39 @@ pub fn (tc &TypeChecker) sum_variant_type_for_pattern(sum_name_id u32, variant_n
|
||||
}
|
||||
mut cache := tc.type_cache
|
||||
key := (u64(sum_name_id) << 32) | u64(variant_name_id)
|
||||
if file_entries := cache.sum_variant_pattern_entries[tc.cur_file] {
|
||||
if module_entries := file_entries.modules[tc.cur_module] {
|
||||
if result := module_entries.values[key] {
|
||||
if result == 0 { return none }
|
||||
return result
|
||||
mut context := cache.sum_variant_pattern_recent
|
||||
if isnil(context) || cache.sum_variant_pattern_recent_count != cache.sum_variant_pattern_entries.len
|
||||
|| context.file != tc.cur_file || context.module != tc.cur_module {
|
||||
context = unsafe { nil }
|
||||
if file_entries := cache.sum_variant_pattern_entries[tc.cur_file] {
|
||||
if module_entries := file_entries.modules[tc.cur_module] {
|
||||
context = module_entries
|
||||
cache.sum_variant_pattern_recent = context
|
||||
cache.sum_variant_pattern_recent_count = cache.sum_variant_pattern_entries.len
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isnil(context) {
|
||||
if result := context.values[key] {
|
||||
if result == 0 { return none }
|
||||
return result
|
||||
}
|
||||
}
|
||||
result := tc.sum_variant_type_for_pattern_depth(sum_name_id, variant_name_id, 0) or { u32(0) }
|
||||
// Resolution can populate this context recursively; acquire its current tables again.
|
||||
mut file_entries := cache.sum_variant_pattern_entries[tc.cur_file] or {
|
||||
created := &SumVariantPatternFileEntries{}
|
||||
cache.sum_variant_pattern_entries[tc.cur_file.clone()] = created
|
||||
created := &SumVariantPatternFileEntries{ file: tc.cur_file.clone() }
|
||||
cache.sum_variant_pattern_entries[created.file] = created
|
||||
created
|
||||
}
|
||||
mut module_entries := file_entries.modules[tc.cur_module] or {
|
||||
created := &SumVariantPatternModuleEntries{}
|
||||
file_entries.modules[tc.cur_module.clone()] = created
|
||||
created := &SumVariantPatternModuleEntries{ file: file_entries.file, module: tc.cur_module.clone() }
|
||||
file_entries.modules[created.module] = created
|
||||
created
|
||||
}
|
||||
module_entries.values[key] = result
|
||||
cache.sum_variant_pattern_recent = module_entries
|
||||
cache.sum_variant_pattern_recent_count = cache.sum_variant_pattern_entries.len
|
||||
if result == 0 { return none }
|
||||
return result
|
||||
}
|
||||
@@ -19455,6 +19473,29 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {
|
||||
return tc.resolve_type_slow(id)
|
||||
}
|
||||
|
||||
// resolve_type_reference returns the canonical reference for an expression's resolved type.
|
||||
// It follows resolve_type's checked-cache visibility and mixed-integer widening.
|
||||
// The reference remains valid while the owning checker arena is live.
|
||||
@[direct_array_access; inline]
|
||||
pub fn (tc &TypeChecker) resolve_type_reference(id flat.NodeId) &Type {
|
||||
if tc.trust_checked_expr_types && isnil(tc.storage_query_probe) {
|
||||
tidx := int(id)
|
||||
if tidx >= 0 && tidx < tc.expr_type_set.len && tc.expr_type_set[tidx]
|
||||
&& (!tc.parallel_check_sparse || tc.in_check_range(tidx)) {
|
||||
typ := tc.expr_type_values[tidx]
|
||||
if !type_contains_unknown(typ) {
|
||||
// Non-arithmetic nodes retain their existing canonical identity.
|
||||
if !mixed_integer_expr_type_can_widen(typ) || tidx >= tc.a.nodes.len
|
||||
|| tc.a.nodes[tidx].kind != .infix {
|
||||
return typ
|
||||
}
|
||||
return tc.intern_type_reference(tc.widen_mixed_integer_infix_type(id, typ))
|
||||
}
|
||||
}
|
||||
}
|
||||
return tc.intern_type_reference(tc.resolve_type_slow(id))
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn (tc &TypeChecker) resolve_type_slow(id flat.NodeId) Type {
|
||||
if tc.trust_checked_expr_types && !isnil(tc.storage_query_probe) {
|
||||
@@ -20904,7 +20945,7 @@ fn (tc &TypeChecker) fn_literal_type(node flat.Node) Type {
|
||||
mut reached_params := false
|
||||
mut is_variadic := false
|
||||
for i in 0 .. node.children_count {
|
||||
child := tc.a.child_node(&node, i)
|
||||
child := tc.a.node(tc.a.child(&node, i))
|
||||
if child.kind != .param {
|
||||
// Captures precede parameters in a function literal's flat children, so a
|
||||
// prefix-only scan must skip those identifiers, then stop once the body begins.
|
||||
@@ -21098,7 +21139,7 @@ fn (tc &TypeChecker) explicit_generic_fn_value_type(node flat.Node) ?Type {
|
||||
if node.kind != .index || node.children_count < 2 || node.value == 'range' {
|
||||
return none
|
||||
}
|
||||
base_node := tc.a.child_node(&node, 0)
|
||||
base_node := tc.a.node(tc.a.child(&node, 0))
|
||||
if base_node.kind == .ident && tc.ident_resolves_to_value(base_node.value) {
|
||||
return none
|
||||
}
|
||||
@@ -22911,12 +22952,15 @@ fn (tc &TypeChecker) int_literal_promoted_infix_type(lit_id flat.NodeId, other_i
|
||||
if tc.int_literal_value(other_id) != none {
|
||||
return none
|
||||
}
|
||||
value := tc.implicit_integer_constant_value(lit_id, lit_type)?
|
||||
clean_type := unalias_type(other_type)
|
||||
// An untyped integer literal adopts a float operand's type too, including an
|
||||
// alias of a float: 1 + f where is ype Fraction = f64 is Fraction,
|
||||
// not int.
|
||||
if clean_type.is_float() {
|
||||
// Reject ineligible storage before recursively evaluating the constant.
|
||||
is_float := clean_type.is_float()
|
||||
if !is_float && (!clean_type.is_integer() || tc.type_name(clean_type) == flat.source_type_int) {
|
||||
return none
|
||||
}
|
||||
value := tc.implicit_integer_constant_value(lit_id, lit_type)?
|
||||
// Untyped integer constants also adopt a float operand, including aliases.
|
||||
if is_float {
|
||||
return other_type
|
||||
}
|
||||
if clean_type is Primitive && clean_type.props.has(.integer)
|
||||
@@ -22929,10 +22973,7 @@ fn (tc &TypeChecker) int_literal_promoted_infix_type(lit_id flat.NodeId, other_i
|
||||
// An untyped integer literal adopts the other integer operand's concrete storage
|
||||
// type. In particular, `24 * time.hour` is `i64`, not `int`; map literal
|
||||
// inference relies on that distinction when it chooses its value type.
|
||||
if clean_type.is_integer() && tc.type_name(clean_type) != flat.source_type_int {
|
||||
return *clean_type
|
||||
}
|
||||
return none
|
||||
return *clean_type
|
||||
}
|
||||
|
||||
fn infix_integer_promotion_type(lhs Type, rhs Type) ?Type {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn assert_const_selector_filter_matches_tables(tc &TypeChecker, names []string) {
|
||||
for name in names {
|
||||
expected := tc.const_suffix_key_count == tc.const_types.len
|
||||
&& name !in tc.const_suffixes && name !in tc.const_types
|
||||
assert tc.selector_names_no_const(name) == expected, name
|
||||
}
|
||||
}
|
||||
|
||||
fn test_const_selector_filter_preserves_bare_qualified_and_ambiguous_names() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
for name in ['bare', 'one.limit', 'two.limit', 'deep.module.width', 'éclair', '_hidden'] {
|
||||
tc.const_types[name] = builtin_int_type
|
||||
}
|
||||
// Existing suffix entries must remain visible even when not derived from this collection.
|
||||
tc.const_suffixes['old_suffix'] = 'prior.old_suffix'
|
||||
tc.build_const_suffixes()
|
||||
assert tc.const_suffixes['limit'] == ''
|
||||
assert !tc.selector_names_no_const('old_suffix')
|
||||
for name, _ in tc.const_types {
|
||||
assert !tc.selector_names_no_const(name), name
|
||||
}
|
||||
for name, _ in tc.const_suffixes {
|
||||
assert !tc.selector_names_no_const(name), name
|
||||
}
|
||||
mut names := ['bare', 'limit', 'width', 'module.width', 'deep.module.width', 'one.limit',
|
||||
'old_suffix', '', 'éclair', '_hidden', 'runtime_local']
|
||||
for i in 0 .. 1000 {
|
||||
names << 'absent_${i}'
|
||||
}
|
||||
assert_const_selector_filter_matches_tables(tc, names)
|
||||
fork := tc.fork_for_parallel_codegen()
|
||||
assert fork.const_selector_bloom.count == tc.const_selector_bloom.count
|
||||
assert fork.const_selector_suffix_count == tc.const_suffixes.len
|
||||
assert_const_selector_filter_matches_tables(fork, names)
|
||||
}
|
||||
|
||||
fn test_const_selector_filter_falls_back_after_collection_changes() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.const_types['pkg.first'] = builtin_int_type
|
||||
tc.build_const_suffixes()
|
||||
tc.const_suffixes['added_suffix'] = 'pkg.added_suffix'
|
||||
assert !tc.selector_names_no_const('added_suffix')
|
||||
assert_const_selector_filter_matches_tables(tc, ['first', 'added_suffix', 'missing'])
|
||||
tc.const_types['pkg.second'] = builtin_int_type
|
||||
assert !tc.selector_names_no_const('second')
|
||||
assert !tc.selector_names_no_const('missing')
|
||||
tc.build_const_suffixes()
|
||||
assert_const_selector_filter_matches_tables(tc, ['first', 'second', 'added_suffix', 'missing'])
|
||||
// Value changes preserve the key set and may continue to use the snapshot.
|
||||
tc.const_types['pkg.first'] = builtin_string_type
|
||||
assert_const_selector_filter_matches_tables(tc, ['first', 'second', 'missing'])
|
||||
tc.const_types.delete('pkg.second')
|
||||
assert !tc.selector_names_no_const('missing')
|
||||
tc.build_const_suffixes()
|
||||
assert_const_selector_filter_matches_tables(tc, ['first', 'second', 'added_suffix', 'missing'])
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_source_struct_field_decls_keeps_field_names_and_source_types() {
|
||||
mut a := flat.FlatAst.new()
|
||||
file := a.add_val(.file, 'model.v')
|
||||
mod := a.add_val(.module_decl, 'model')
|
||||
field := a.add_node(flat.Node{ kind: .field_decl, value: 'value', typ: flat.source_type_int })
|
||||
non_field := a.add_val(.ident, 'ignored')
|
||||
a.children << field
|
||||
a.children << non_field
|
||||
decl := a.add_node(flat.Node{ kind: .struct_decl, value: 'Item', children_start: 0, children_count: 2 })
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_module = 'model'
|
||||
tc.top_level_idx = [int(file), int(mod), int(decl)]
|
||||
tc.struct_files['model.Item'] = 'model.v'
|
||||
fields := tc.source_struct_field_decls(flat.source_type_id('model.Item'))
|
||||
assert fields.len == 1
|
||||
assert fields[0].name == 'value'
|
||||
assert fields[0].typ == flat.source_type_int
|
||||
assert tc.source_struct_field_decls(flat.source_type_id('Missing')).len == 0
|
||||
}
|
||||
|
||||
fn test_enum_selector_keeps_saved_header_after_ast_reallocation() {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'Color')
|
||||
a.children << base
|
||||
selector := a.add_node(flat.Node{ kind: .selector, value: 'red', children_start: 0, children_count: 1 })
|
||||
snapshot := a.nodes[int(selector)]
|
||||
a.nodes[int(selector)].children_count = 0
|
||||
growth := a.nodes.cap + 100
|
||||
for _ in 0 .. growth {
|
||||
a.add_val(.ident, 'padding')
|
||||
}
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.enum_names['Color'] = true
|
||||
tc.enum_fields['Color'] = ['red']
|
||||
from_pointer := tc.enum_selector_type(&snapshot) or { panic('missing enum selector') }
|
||||
from_value := tc.selector_type(selector, snapshot) or { panic('missing saved selector') }
|
||||
assert from_pointer.source_type_id() == flat.source_type_id('Color')
|
||||
assert from_value.source_type_id() == flat.source_type_id('Color')
|
||||
assert snapshot.children_count == 1
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_fn_value_filter_snapshot_hits_misses_collisions_and_empty_names() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.set_resolved_fn_value(3, 'main.callback')
|
||||
tc.set_resolved_fn_value(5, '')
|
||||
forked := tc.fork_for_parallel_codegen()
|
||||
assert forked.sparse_fn_value_filter_ready
|
||||
assert forked.resolved_fn_value_name(3)? == 'main.callback'
|
||||
assert forked.resolved_fn_value_name(5)? == ''
|
||||
assert forked.resolved_fn_value_name(4) == none
|
||||
// 258 hashes to the same bit as 3; the final map lookup rejects it.
|
||||
assert sparse_fn_value_filter_bit(258) == sparse_fn_value_filter_bit(3)
|
||||
assert forked.resolved_fn_value_name(258) == none
|
||||
assert forked.resolved_fn_value_name(-1) == none
|
||||
}
|
||||
|
||||
fn test_fn_value_filter_stays_disabled_on_mutable_master_direct_writes() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
assert !tc.sparse_fn_value_filter_ready
|
||||
tc.sparse_resolved_fn_values[3] = 'main.first'
|
||||
assert tc.resolved_fn_value_name(3)? == 'main.first'
|
||||
tc.sparse_resolved_fn_values.delete(3)
|
||||
tc.sparse_resolved_fn_values[4] = 'main.replacement'
|
||||
assert tc.resolved_fn_value_name(3) == none
|
||||
assert tc.resolved_fn_value_name(4)? == 'main.replacement'
|
||||
}
|
||||
|
||||
fn test_fn_value_filter_codegen_set_clear_and_reset_keep_exact_membership() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.set_resolved_fn_value(3, 'main.original')
|
||||
mut forked := tc.fork_for_parallel_codegen()
|
||||
assert forked.resolved_fn_value_name(4) == none
|
||||
forked.set_resolved_fn_value(4, 'main.later')
|
||||
assert forked.resolved_fn_value_name(4)? == 'main.later'
|
||||
forked.clear_resolved_fn_value(4)
|
||||
assert forked.resolved_fn_value_name(4) == none
|
||||
forked.set_resolved_fn_value(4, 'main.again')
|
||||
assert forked.resolved_fn_value_name(4)? == 'main.again'
|
||||
forked.reset_sparse_fn_values()
|
||||
assert !forked.sparse_fn_value_filter_ready
|
||||
assert forked.resolved_fn_value_name(3) == none
|
||||
assert forked.resolved_fn_value_name(4) == none
|
||||
forked.sparse_resolved_fn_values[7] = 'main.new_ast'
|
||||
assert forked.resolved_fn_value_name(7)? == 'main.new_ast'
|
||||
assert tc.resolved_fn_value_name(3)? == 'main.original'
|
||||
assert tc.resolved_fn_value_name(4) == none
|
||||
}
|
||||
|
||||
fn test_fn_value_filter_transform_writes_tombstones_and_nested_snapshots() {
|
||||
mut a := flat.FlatAst.new()
|
||||
for _ in 0 .. 8 {
|
||||
a.add(.ident)
|
||||
}
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.set_resolved_fn_value(3, 'main.original')
|
||||
mut forked := tc.fork_for_parallel_transform(&a)
|
||||
assert forked.sparse_fn_value_filter_ready
|
||||
assert forked.resolved_fn_value_name(3)? == 'main.original'
|
||||
forked.set_resolved_fn_value(4, 'main.private')
|
||||
assert forked.resolved_fn_value_name(4)? == 'main.private'
|
||||
forked.clear_resolved_fn_value(3)
|
||||
assert forked.resolved_fn_value_name(3) == none
|
||||
// The nested snapshot must include private writes and omit tombstones,
|
||||
// even when the parent's snapshot filter did not contain the new key.
|
||||
nested := forked.fork_for_parallel_transform(&a)
|
||||
assert nested.sparse_fn_value_filter_ready
|
||||
assert nested.resolved_fn_value_name(3) == none
|
||||
assert nested.resolved_fn_value_name(4)? == 'main.private'
|
||||
forked.fork_overlay.resolved_fn_values[9] = 'main.generated'
|
||||
assert forked.resolved_fn_value_name(9)? == 'main.generated'
|
||||
assert tc.resolved_fn_value_name(3)? == 'main.original'
|
||||
assert tc.resolved_fn_value_name(4) == none
|
||||
}
|
||||
|
||||
fn test_fn_value_filter_parallel_worker_merge_records_new_snapshot_keys() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
mut forked := tc.fork_for_parallel_codegen()
|
||||
mut worker := tc.fork_for_parallel_check()
|
||||
assert !worker.sparse_fn_value_filter_ready
|
||||
worker.sparse_resolved_fn_values[7] = 'main.worker'
|
||||
forked.merge_parallel_check_worker_scoped(worker, true)
|
||||
assert forked.sparse_fn_value_filter_ready
|
||||
assert forked.resolved_fn_value_name(7)? == 'main.worker'
|
||||
}
|
||||
|
||||
fn test_fn_value_filter_storage_clones_and_nested_observers_stay_unfiltered() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.set_resolved_fn_value(3, 'main.original')
|
||||
forked := tc.fork_for_parallel_codegen()
|
||||
mut exact := forked.fork_storage_query_view()
|
||||
assert !exact.sparse_fn_value_filter_ready
|
||||
exact.sparse_resolved_fn_values.delete(3)
|
||||
exact.sparse_resolved_fn_values[4] = 'main.private_exact'
|
||||
assert exact.resolved_fn_value_name(3) == none
|
||||
assert exact.resolved_fn_value_name(4)? == 'main.private_exact'
|
||||
mut outer := forked.fork_storage_observation_view()
|
||||
assert !outer.sparse_fn_value_filter_ready
|
||||
assert outer.resolved_fn_value_name(3)? == 'main.original'
|
||||
outer.sparse_resolved_fn_values[4] = 'main.private_observer'
|
||||
assert outer.resolved_fn_value_name(4)? == 'main.private_observer'
|
||||
mut nested := outer.fork_storage_observation_view()
|
||||
assert !nested.sparse_fn_value_filter_ready
|
||||
assert nested.resolved_fn_value_name(3)? == 'main.original'
|
||||
assert nested.resolved_fn_value_name(4)? == 'main.private_observer'
|
||||
nested.clear_resolved_fn_value(3)
|
||||
assert nested.resolved_fn_value_name(3) == none
|
||||
assert outer.resolved_fn_value_name(3)? == 'main.original'
|
||||
assert forked.resolved_fn_value_name(4) == none
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn global_store_query_checker() &TypeChecker {
|
||||
mut a := flat.FlatAst.new()
|
||||
names := ['root', 'cycle', 'sink', 'clean', 'leaf', 'translated']
|
||||
for name in names {
|
||||
a.add_node(flat.Node{ kind: .fn_decl, value: name })
|
||||
}
|
||||
mut tc := TypeChecker.new(&a)
|
||||
for i, name in names {
|
||||
tc.register_visible_mutation_fn_decl(i, 'main', name, name)
|
||||
tc.fn_type_files[name] = '${name}.v'
|
||||
tc.fn_type_modules[name] = 'main'
|
||||
}
|
||||
tc.has_globals_files['sink.v'] = true
|
||||
tc.translated_files['translated.v'] = true
|
||||
root_id, _ := tc.intern_symbol('root')
|
||||
cycle_id, _ := tc.intern_symbol('cycle')
|
||||
sink_id, _ := tc.intern_symbol('sink')
|
||||
leaf_id, _ := tc.intern_symbol('leaf')
|
||||
tc.direct_dependencies_by_fn[0] = [cycle_id, sink_id]
|
||||
tc.direct_dependencies_by_fn[1] = [root_id]
|
||||
tc.direct_dependencies_by_fn[3] = [leaf_id]
|
||||
tc.visible_mutation_cache.decl_index_ready = true
|
||||
tc.remember_resolved_call(flat.NodeId(0), 'root')
|
||||
tc.set_resolved_fn_value(1, 'cycle')
|
||||
return tc
|
||||
}
|
||||
|
||||
fn test_frozen_global_store_queries_keep_recursive_roots_independent() {
|
||||
tc := global_store_query_checker()
|
||||
forked := tc.fork_for_parallel_transform(tc.a)
|
||||
// root -> cycle -> root and root -> sink. A negative answer reached while
|
||||
// root is visiting must not become the answer to a later cycle root query.
|
||||
for _ in 0 .. 3 {
|
||||
assert forked.resolved_call_may_store_globally(flat.NodeId(0))
|
||||
assert forked.fn_value_may_store_globally(flat.NodeId(1))
|
||||
assert !forked.cached_fn_may_store_globally('clean')
|
||||
assert forked.cached_fn_may_store_globally('translated')
|
||||
}
|
||||
assert forked.fn_value_may_store_globally(flat.NodeId(-1))
|
||||
assert !forked.resolved_call_may_store_globally(flat.NodeId(-1))
|
||||
}
|
||||
|
||||
fn test_frozen_global_store_cache_is_private_and_invalidated_by_signature_changes() {
|
||||
tc := global_store_query_checker()
|
||||
mut first := tc.fork_for_parallel_transform(tc.a)
|
||||
second := tc.fork_for_parallel_transform(tc.a)
|
||||
assert !first.cached_fn_may_store_globally('clean')
|
||||
assert !second.cached_fn_may_store_globally('clean')
|
||||
|
||||
first.ensure_private_transform_signatures()
|
||||
first.fn_type_files['leaf'] = 'sink.v'
|
||||
assert first.cached_fn_may_store_globally('clean')
|
||||
assert !second.cached_fn_may_store_globally('clean')
|
||||
|
||||
// Later edits must invalidate as well as the initial detachment.
|
||||
first.ensure_private_transform_signatures()
|
||||
first.fn_type_files['leaf'] = 'leaf.v'
|
||||
assert !first.cached_fn_may_store_globally('clean')
|
||||
assert !second.cached_fn_may_store_globally('clean')
|
||||
|
||||
nested := first.fork_for_parallel_transform(first.a)
|
||||
assert !nested.cached_fn_may_store_globally('clean')
|
||||
}
|
||||
|
||||
fn test_live_global_store_queries_do_not_reuse_post_check_results() {
|
||||
mut tc := global_store_query_checker()
|
||||
assert !tc.cached_fn_may_store_globally('clean')
|
||||
tc.fn_type_files['leaf'] = 'sink.v'
|
||||
assert tc.cached_fn_may_store_globally('clean')
|
||||
tc.fn_type_files['leaf'] = 'leaf.v'
|
||||
assert !tc.cached_fn_may_store_globally('clean')
|
||||
}
|
||||
|
||||
fn test_frozen_global_store_declaration_index_preserves_module_priority() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.register_visible_mutation_fn_decl_with_lowered(10, 'alpha', 'alpha.helper', 'helper',
|
||||
'alpha__helper', 'helper')
|
||||
tc.register_visible_mutation_fn_decl_with_lowered(20, 'beta', 'beta.helper', 'helper',
|
||||
'beta__helper', 'helper')
|
||||
tc.register_visible_mutation_fn_decl_with_lowered(30, 'alpha', 'alpha.Box[T].method',
|
||||
'Box[T].method', 'alpha__Box_T__method', 'Box_T__method')
|
||||
tc.visible_mutation_cache.decl_index_ready = true
|
||||
forked := tc.fork_for_parallel_transform(&a)
|
||||
for module_name, expected in {
|
||||
'alpha': 10
|
||||
'beta': 20
|
||||
'': 10
|
||||
} {
|
||||
decl := forked.global_store_fn_decl('helper', module_name) or { panic('missing helper') }
|
||||
assert decl.idx == expected
|
||||
}
|
||||
assert forked.global_store_fn_decl('helper', 'missing') == none
|
||||
for name in ['Box[int].method', 'alpha.Box[string].method', 'Box.method'] {
|
||||
decl := forked.global_store_fn_decl(name, 'alpha') or { panic('missing generic method') }
|
||||
assert decl.idx == 30
|
||||
}
|
||||
alias_decl := forked.global_store_fn_decl('beta__helper', 'beta') or { panic('missing alias') }
|
||||
assert alias_decl.idx == 20
|
||||
}
|
||||
|
||||
fn test_frozen_global_store_declaration_misses_fall_back_to_current_ast() {
|
||||
mut a := flat.FlatAst.new()
|
||||
module_id := a.add_node(flat.Node{ kind: .module_decl, value: 'main' })
|
||||
generated_id := a.add_node(flat.Node{ kind: .fn_decl, value: 'generated' })
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.top_level_idx = [i32(module_id), i32(generated_id)]
|
||||
tc.visible_mutation_cache.decl_index_ready = true
|
||||
forked := tc.fork_for_parallel_transform(&a)
|
||||
decl := forked.global_store_fn_decl('generated', 'main') or { panic('missing generated declaration') }
|
||||
assert decl.idx == int(generated_id)
|
||||
assert decl.mod == 'main'
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
struct IntegerPromotionBoundaryCase {
|
||||
value string
|
||||
other Type
|
||||
expected string
|
||||
}
|
||||
|
||||
fn test_integer_promotion_guard_preserves_storage_and_small_integer_boundaries() {
|
||||
cases := [
|
||||
IntegerPromotionBoundaryCase{'127', Primitive{ props: .integer, size: 8 }, 'i8'},
|
||||
IntegerPromotionBoundaryCase{'128', Primitive{ props: .integer, size: 8 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'-128', Primitive{ props: .integer, size: 8 }, 'i8'},
|
||||
IntegerPromotionBoundaryCase{'-129', Primitive{ props: .integer, size: 8 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'255', Primitive{ props: .integer | .unsigned, size: 8 }, 'u8'},
|
||||
IntegerPromotionBoundaryCase{'256', Primitive{ props: .integer | .unsigned, size: 8 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'-1', Primitive{ props: .integer | .unsigned, size: 8 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'32767', Primitive{ props: .integer, size: 16 }, 'i16'},
|
||||
IntegerPromotionBoundaryCase{'32768', Primitive{ props: .integer, size: 16 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'-32768', Primitive{ props: .integer, size: 16 }, 'i16'},
|
||||
IntegerPromotionBoundaryCase{'-32769', Primitive{ props: .integer, size: 16 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'65535', Primitive{ props: .integer | .unsigned, size: 16 }, 'u16'},
|
||||
IntegerPromotionBoundaryCase{'65536', Primitive{ props: .integer | .unsigned, size: 16 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'-1', Primitive{ props: .integer | .unsigned, size: 16 }, 'int'},
|
||||
IntegerPromotionBoundaryCase{'42', Primitive{ props: .integer, size: 32 }, 'i32'},
|
||||
IntegerPromotionBoundaryCase{'42', Primitive{ props: .integer, size: 64 }, 'i64'},
|
||||
IntegerPromotionBoundaryCase{'42', Primitive{ props: .integer | .unsigned, size: 32 }, 'u32'},
|
||||
IntegerPromotionBoundaryCase{'42', Primitive{ props: .integer | .unsigned, size: 64 }, 'u64'},
|
||||
IntegerPromotionBoundaryCase{'42', Rune{}, 'rune'},
|
||||
IntegerPromotionBoundaryCase{'42', ISize{}, 'isize'},
|
||||
IntegerPromotionBoundaryCase{'42', USize{}, 'uint'},
|
||||
IntegerPromotionBoundaryCase{'42', Alias{ name_id: flat.source_type_id('Count'), base_type: &Type(i64_) }, 'i64'},
|
||||
]
|
||||
for c in cases {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut literal := a.add_val(.int_literal, c.value.trim_left('-'))
|
||||
if c.value.starts_with('-') {
|
||||
start := a.children.len
|
||||
a.children << literal
|
||||
literal = a.add_node(flat.Node{ kind: .prefix, op: .minus, children_start: start, children_count: 1 })
|
||||
}
|
||||
other := a.add_val(.ident, 'operand')
|
||||
tc := TypeChecker.new(&a)
|
||||
actual := tc.int_literal_promoted_infix_type(literal, other, builtin_int_type, c.other) or {
|
||||
panic('missing ${c.value} promotion to ${c.other.name()}')
|
||||
}
|
||||
assert actual.name() == c.expected, '${c.value}, ${c.other.name()}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_integer_promotion_guard_rejects_ineligible_operands_and_preserves_constant_expressions() {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.int_literal, '20')
|
||||
right := a.add_val(.int_literal, '22')
|
||||
a.children << left
|
||||
a.children << right
|
||||
expr := a.add_node(flat.Node{ kind: .infix, op: .plus, children_start: 0, children_count: 2 })
|
||||
constant := a.add_val(.ident, 'limit')
|
||||
unknown := a.add_val(.ident, 'missing')
|
||||
other := a.add_val(.ident, 'operand')
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.const_exprs['limit'] = expr
|
||||
for id in [expr, constant] {
|
||||
actual := tc.int_literal_promoted_infix_type(id, other, builtin_int_type, builtin_i64_type) or {
|
||||
panic('missing constant expression promotion')
|
||||
}
|
||||
assert actual.name() == 'i64'
|
||||
for typ in [Type(int_), String{}, Pointer{ base_type: &Type(int_) },
|
||||
Array{ elem_type: &Type(int_) }, OptionType{ base_type: &Type(int_) },
|
||||
Struct{ name_id: flat.source_type_id('Item') },
|
||||
Alias{ name_id: flat.source_type_id('Plain'), base_type: &Type(int_) }] {
|
||||
assert tc.int_literal_promoted_infix_type(id, other, builtin_int_type, typ) == none
|
||||
}
|
||||
}
|
||||
float_type := Type(Primitive{ props: .float, size: 64 })
|
||||
float_alias := Type(Alias{ name_id: flat.source_type_id('Fraction'), base_type: &float_type })
|
||||
for typ in [float_type, float_alias] {
|
||||
for id in [left, expr, constant] {
|
||||
actual := tc.int_literal_promoted_infix_type(id, other, builtin_int_type, typ) or {
|
||||
panic('missing constant promotion to ${typ.name()}')
|
||||
}
|
||||
assert actual.source_type_id() == typ.source_type_id()
|
||||
}
|
||||
assert tc.int_literal_promoted_infix_type(unknown, other, builtin_int_type, typ) == none
|
||||
assert tc.int_literal_promoted_infix_type(left, other, builtin_i64_type, typ) == none
|
||||
}
|
||||
assert tc.int_literal_promoted_infix_type(left, right, builtin_int_type, builtin_i64_type) == none
|
||||
assert tc.int_literal_promoted_infix_type(unknown, other, builtin_int_type, builtin_i64_type) == none
|
||||
assert tc.int_literal_promoted_infix_type(left, other, builtin_i64_type, builtin_i64_type) == none
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_known_symbol_alias_keeps_present_empty_binding() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
assert tc.type_from_known_symbol(flat.source_type_id('missing')) == none
|
||||
tc.type_aliases.set_id('Empty', 0)
|
||||
typ := tc.type_from_known_symbol(flat.source_type_id('Empty')) or { panic('lost present empty alias') }
|
||||
assert typ is Alias
|
||||
assert typ.name() == 'Empty'
|
||||
if typ is Alias {
|
||||
assert typ.base_type is Void
|
||||
}
|
||||
}
|
||||
|
||||
fn test_known_symbol_alias_precedes_other_type_kinds() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.type_aliases.set('Counter', 'int')
|
||||
tc.structs['Counter'] = []StructField{}
|
||||
typ := tc.type_from_known_symbol(flat.source_type_id('Counter')) or { panic('missing integer alias') }
|
||||
assert typ is Alias
|
||||
assert typ.name() == 'Counter'
|
||||
if typ is Alias {
|
||||
assert typ.base_type.name() == 'int'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
import v.token
|
||||
|
||||
struct MalformedConstQueryCase {
|
||||
source string
|
||||
offset int
|
||||
keyword_offset int = -1
|
||||
}
|
||||
|
||||
fn malformed_const_query_checker(source string, file_id int, offset int) (&TypeChecker, flat.NodeId) {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut files := token.FileSet.new()
|
||||
a.source_files[file_id] = files.add_file('typo.v', source.len)
|
||||
a.source_texts[file_id] = source
|
||||
id := a.add_node(flat.Node{
|
||||
kind: .ident
|
||||
pos: token.new_pos(file_id, offset)
|
||||
})
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.source_texts_by_file['typo.v'] = source
|
||||
return tc, id
|
||||
}
|
||||
|
||||
fn test_malformed_const_query_preserves_line_and_keyword_boundaries() {
|
||||
cases := [
|
||||
MalformedConstQueryCase{ source: '\t cosnt amount = 1\n', offset: 10, keyword_offset: 2 },
|
||||
MalformedConstQueryCase{ source: 'module main\n\tcosnt\tamount = 1', offset: 20, keyword_offset: 13 },
|
||||
MalformedConstQueryCase{ source: 'cosnt', offset: 0, keyword_offset: 0 },
|
||||
MalformedConstQueryCase{ source: 'cosnt\n', offset: 0, keyword_offset: 0 },
|
||||
MalformedConstQueryCase{ source: 'cosntamount = 1\n', offset: 0 },
|
||||
MalformedConstQueryCase{ source: '// cosnt amount = 1\n', offset: 10 },
|
||||
MalformedConstQueryCase{ source: 'const amount = "cosnt"\n', offset: 10 },
|
||||
MalformedConstQueryCase{ source: 'const amount = 1\n', offset: 10 },
|
||||
MalformedConstQueryCase{ source: 'cosnt amount = 1\namount = 2\n', offset: 18 },
|
||||
MalformedConstQueryCase{ source: 'cosnt amount = 1', offset: -1 },
|
||||
MalformedConstQueryCase{ source: 'cosnt amount = 1', offset: 100 },
|
||||
]
|
||||
for indexed in [false, true] {
|
||||
for item in cases {
|
||||
mut tc, id := malformed_const_query_checker(item.source, 1, item.offset)
|
||||
if indexed {
|
||||
tc.index_multiple_module_import_lines(tc.a)
|
||||
}
|
||||
if pos := tc.malformed_const_keyword_pos(id) {
|
||||
assert item.keyword_offset >= 0
|
||||
assert pos == token.new_span(1, item.keyword_offset, item.keyword_offset + 5)
|
||||
} else {
|
||||
assert item.keyword_offset == -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_malformed_const_query_reindexes_changed_sources_without_mutating_forks() {
|
||||
mut tc, id := malformed_const_query_checker('const amount = 1', 1, 10)
|
||||
tc.index_multiple_module_import_lines(tc.a)
|
||||
forked := tc.fork_for_parallel_check()
|
||||
assert tc.malformed_const_keyword_pos(id) == none
|
||||
assert forked.malformed_const_keyword_pos(id) == none
|
||||
tc.a.source_texts[1] = 'cosnt amount = 1'
|
||||
tc.index_multiple_module_import_lines(tc.a)
|
||||
assert (tc.malformed_const_keyword_pos(id) or { panic('missing typo after source reset') }).offset == 0
|
||||
assert forked.malformed_const_keyword_pos(id) == none
|
||||
}
|
||||
|
||||
fn test_malformed_const_query_indexes_added_files_and_retains_sparse_fallback() {
|
||||
mut tc, _ := malformed_const_query_checker('const amount = 1', 1, 10)
|
||||
tc.index_multiple_module_import_lines(tc.a)
|
||||
mut files := token.FileSet.new()
|
||||
for file_id in [2, 1000000000] {
|
||||
source := '\tcosnt amount = 1'
|
||||
tc.a.source_files[file_id] = files.add_file('typo_${file_id}.v', source.len)
|
||||
tc.a.source_texts[file_id] = source
|
||||
id := tc.a.add_node(flat.Node{
|
||||
kind: .ident
|
||||
pos: token.new_pos(file_id, 10)
|
||||
})
|
||||
tc.index_module_import_lines_of_new_files(tc.a)
|
||||
pos := tc.malformed_const_keyword_pos(id) or { panic('missing added-file typo') }
|
||||
assert pos == token.new_span(file_id, 1, 6)
|
||||
}
|
||||
assert tc.malformed_const_source_states.len < 128
|
||||
}
|
||||
|
||||
fn test_malformed_const_query_retains_filename_fallback_for_duplicate_ids() {
|
||||
for incremental in [false, true] {
|
||||
mut tc, first := malformed_const_query_checker('const amount = 1', 1, 10)
|
||||
if incremental {
|
||||
tc.index_multiple_module_import_lines(tc.a)
|
||||
} else {
|
||||
tc.source_texts_by_file.clear()
|
||||
}
|
||||
mut files := token.FileSet.new()
|
||||
tc.a.source_files[2] = files.add_file('typo.v', 16)
|
||||
tc.a.source_texts[2] = 'cosnt amount = 1'
|
||||
second := tc.a.add_node(flat.Node{
|
||||
kind: .ident
|
||||
pos: token.new_pos(2, 10)
|
||||
})
|
||||
tc.index_module_import_lines_of_new_files(tc.a)
|
||||
assert tc.malformed_const_source_states[1] == 0
|
||||
assert tc.malformed_const_source_states[2] == 0
|
||||
has_typo := tc.source_texts_by_file['typo.v'].starts_with('cosnt')
|
||||
if incremental {
|
||||
assert has_typo
|
||||
}
|
||||
for id in [first, second] {
|
||||
if pos := tc.malformed_const_keyword_pos(id) {
|
||||
assert has_typo
|
||||
assert pos == token.new_span(tc.a.nodes[int(id)].pos.id, 0, 5)
|
||||
} else {
|
||||
assert !has_typo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn noreturn_byte_search_reference(tc &TypeChecker, name string) bool {
|
||||
if resolved_name_never_returns(name) {
|
||||
return true
|
||||
}
|
||||
if name in tc.a.noreturn_fns {
|
||||
return true
|
||||
}
|
||||
mut has_dot := false
|
||||
for i := name.len - 1; i >= 0; i-- {
|
||||
if name[i] == `.` {
|
||||
if has_dot && tc.a.noreturn_fns[name[i + 1..]] {
|
||||
return true
|
||||
}
|
||||
has_dot = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn test_noreturn_word_search_matches_byte_search_with_long_spans() {
|
||||
mut a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
for declarations in [map[string]bool{}, {
|
||||
'pkg.fail': true
|
||||
'pkg.disabled': false
|
||||
'.fail': true
|
||||
'fail': true
|
||||
'': true
|
||||
}] {
|
||||
a.noreturn_fns = declarations.clone()
|
||||
for padding in 0 .. 48 {
|
||||
stem := 'a'.repeat(padding)
|
||||
for tail in ['', '.', '..', 'pkg.fail', 'pkg.disabled', 'notpkg.fail', '.fail', 'fail',
|
||||
'panic', 'exit', 'C.exit'] {
|
||||
for prefix in ['', '.', '..', stem, stem + '.', stem + '..', stem + '.long_module.'] {
|
||||
name := prefix + tail
|
||||
assert tc.name_never_returns(name) == noreturn_byte_search_reference(tc, name), name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_resolution_type_views_preserve_file_context_across_recent_hits_and_file_switches() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_file = 'caller.v'
|
||||
tc.cur_module = 'caller'
|
||||
tc.structs['left.Item'] = []StructField{}
|
||||
tc.structs['right.Item'] = []StructField{}
|
||||
tc.file_modules['left.v'] = 'left'
|
||||
tc.file_modules['right.v'] = 'right'
|
||||
for file in ['left.v', 'left.v'.clone(), 'right.v', 'right.v'.clone(), 'left.v'] {
|
||||
module_name := tc.file_modules[file] or { panic('missing file module') }
|
||||
for source in ['[]Item', '?Item', '&Item'] {
|
||||
fresh := tc.fork_type_parse_view(file, module_name)
|
||||
expected := fresh.parse_resolution_type(flat.source_type_id(source))
|
||||
assert tc.parse_resolution_type_in_file(flat.source_type_id(source), file) == expected
|
||||
}
|
||||
}
|
||||
assert tc.cur_file == 'caller.v'
|
||||
assert tc.cur_module == 'caller'
|
||||
}
|
||||
|
||||
fn test_reset_resolution_type_views_discards_recent_file_context() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.structs['left.Item'] = []StructField{}
|
||||
tc.structs['right.Item'] = []StructField{}
|
||||
tc.file_modules['source.v'] = 'left'
|
||||
source := flat.source_type_id('[]Item')
|
||||
assert tc.parse_resolution_type_in_file(source, 'source.v').name() == '[]left.Item'
|
||||
tc.file_modules['source.v'] = 'right'
|
||||
tc.reset_resolution_type_view_cache()
|
||||
assert tc.parse_resolution_type_in_file(source, 'source.v').name() == '[]right.Item'
|
||||
tc.disable_resolution_type_view_cache()
|
||||
tc.file_modules['source.v'] = 'left'
|
||||
assert tc.parse_resolution_type_in_file(source, 'source.v').name() == '[]left.Item'
|
||||
assert isnil(tc.resolution_type_views)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_resolved_references_keep_checked_storage_and_invalidation() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.ident, 'item')
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.reset_node_caches(a.nodes.len)
|
||||
tc.trust_checked_expr_types = true
|
||||
tc.remember_expr_type(id, Type(Array{ elem_type: &Type(int_) }))
|
||||
first := tc.resolve_type_reference(id)
|
||||
assert voidptr(first) == voidptr(tc.expr_type_values[int(id)])
|
||||
assert *first == tc.resolve_type(id)
|
||||
tc.invalidate_checked_expr_type(int(id))
|
||||
tc.remember_expr_type(id, Type(string_))
|
||||
assert *tc.resolve_type_reference(id) == Type(string_)
|
||||
assert first.name() == '[]int'
|
||||
}
|
||||
|
||||
fn test_resolved_references_preserve_sparse_and_probe_precedence() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.int_literal, '1')
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.reset_node_caches(a.nodes.len)
|
||||
tc.trust_checked_expr_types = true
|
||||
tc.remember_expr_type(id, Type(string_))
|
||||
tc.parallel_check_sparse = true
|
||||
tc.check_range_lo = 1
|
||||
tc.check_range_hi = 1
|
||||
assert *tc.resolve_type_reference(id) == tc.resolve_type(id)
|
||||
assert *tc.resolve_type_reference(id) == Type(int_)
|
||||
tc.sparse_expr_type_values[int(id)] = Type(bool_)
|
||||
mut probe := tc.fork_storage_observation_view()
|
||||
assert *probe.resolve_type_reference(id) == Type(bool_)
|
||||
probe.sparse_expr_type_values[int(id)] = Type(u64_)
|
||||
assert *probe.resolve_type_reference(id) == probe.resolve_type(id)
|
||||
assert *probe.resolve_type_reference(id) == Type(u64_)
|
||||
}
|
||||
|
||||
fn test_resolved_references_keep_integer_widening_and_shift_rules() {
|
||||
for op in [flat.Op.plus, .eq, .logical_and, .left_shift, .right_shift] {
|
||||
mut a := flat.FlatAst.new()
|
||||
left := a.add_val(.ident, 'left')
|
||||
right := a.add_val(.ident, 'right')
|
||||
a.children << [left, right]
|
||||
expr := a.add_node(flat.Node{ kind: .infix, op: op, children_count: 2 })
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.reset_node_caches(a.nodes.len)
|
||||
tc.trust_checked_expr_types = true
|
||||
tc.remember_expr_type(left, Type(u64_))
|
||||
tc.remember_expr_type(right, Type(u128_))
|
||||
tc.remember_expr_type(expr, Type(u64_))
|
||||
expected := if op == .plus { Type(u128_) } else { Type(u64_) }
|
||||
assert *tc.resolve_type_reference(expr) == tc.resolve_type(expr)
|
||||
assert *tc.resolve_type_reference(expr) == expected
|
||||
if op in [.left_shift, .right_shift] {
|
||||
tc.remember_expr_type(left, Type(i128_))
|
||||
assert *tc.resolve_type_reference(expr) == Type(i128_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_resolved_references_do_not_publish_provisional_unknowns() {
|
||||
mut a := flat.FlatAst.new()
|
||||
id := a.add_val(.ident, 'missing')
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.reset_node_caches(a.nodes.len)
|
||||
tc.trust_checked_expr_types = true
|
||||
tc.remember_expr_type(id, Type(Array{ elem_type: &Type(unknown_type('T')) }))
|
||||
tc.arm_body_resolve_memo(0, a.nodes.len - 1)
|
||||
first := tc.resolve_type_reference(id)
|
||||
assert *first == tc.resolve_type(id)
|
||||
assert tc.body_resolve_memo.filled[int(id)] == 0
|
||||
tc.invalidate_checked_expr_type(int(id))
|
||||
tc.cur_scope.insert('missing', Type(bool_))
|
||||
assert *tc.resolve_type_reference(id) == Type(bool_)
|
||||
assert first.name() != 'bool'
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
module types
|
||||
|
||||
import os
|
||||
import v.parser
|
||||
import v.pref
|
||||
|
||||
fn test_return_analysis_preserves_local_noreturn_shadowing() {
|
||||
root := os.join_path(os.vtmp_dir(), 'v3_return_tail_${os.getpid()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
path := os.join_path(root, 'main.v')
|
||||
os.write_file(path, 'fn implicit_exit() { value := 1; panic("stop") }
|
||||
fn shadowed_exit() { panic := fn (_ string) {}; panic("stop") }
|
||||
fn explicit_return() int { panic := fn (_ string) {}; panic("stop"); return 7 }
|
||||
')!
|
||||
mut p := parser.Parser.new(pref.new_preferences())
|
||||
a := p.parse_file(path)
|
||||
assert p.diagnostics.len == 0, p.diagnostics.str()
|
||||
mut tc := TypeChecker.new(a)
|
||||
tc.collect(a)
|
||||
tc.smartcasts['sentinel'] = builtin_int_type
|
||||
expected := {
|
||||
'implicit_exit': true
|
||||
'shadowed_exit': false
|
||||
'explicit_return': true
|
||||
}
|
||||
mut checked := 0
|
||||
for node in a.nodes {
|
||||
if node.kind != .fn_decl { continue }
|
||||
returns := expected[node.value] or { continue }
|
||||
assert tc.stmt_sequence_definitely_returns(&node, 0) == returns, node.value
|
||||
assert tc.smartcasts.len == 1
|
||||
assert 'sentinel' in tc.smartcasts
|
||||
checked++
|
||||
}
|
||||
assert checked == expected.len
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_scalar_child_call_helpers_preserve_saved_header_after_ast_growth() {
|
||||
mut a := flat.FlatAst.new()
|
||||
callee := a.add_val(.ident, 'saved')
|
||||
a.children << callee
|
||||
call := a.add_node(flat.Node{ kind: .call, children_start: 0, children_count: 1 })
|
||||
snapshot := a.nodes[int(call)]
|
||||
// Querying the saved header must not reload a header changed by a nested check.
|
||||
a.nodes[int(call)].children_count = 0
|
||||
growth := a.nodes.cap + 100
|
||||
for _ in 0 .. growth {
|
||||
a.add_val(.ident, 'padding')
|
||||
}
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.fn_ret_types['saved'] = builtin_int_type
|
||||
assert tc.call_display_name(snapshot) == 'saved'
|
||||
assert tc.call_argument_target_name(snapshot, CallInfo{}) == 'saved'
|
||||
assert tc.c_va_macro_call_name(snapshot) == ''
|
||||
direct := tc.direct_call_return_type(snapshot) or { panic('missing direct return type') }
|
||||
spawned := tc.spawn_child_call_return_type(snapshot) or { panic('missing spawn return type') }
|
||||
assert direct.source_type_id() == flat.source_type_int
|
||||
assert spawned.source_type_id() == flat.source_type_int
|
||||
assert tc.builtin_array_annotation_expected_type(snapshot, 1) is Void
|
||||
assert snapshot.children_count == 1
|
||||
}
|
||||
|
||||
fn test_scalar_child_function_wrappers_keep_saved_header_and_empty_sentinel() {
|
||||
mut a := flat.FlatAst.new()
|
||||
ident := a.add_val(.ident, 'saved')
|
||||
a.children << ident
|
||||
paren := a.add_node(flat.Node{ kind: .paren, children_start: 0, children_count: 1 })
|
||||
a.children << paren
|
||||
cast := a.add_node(flat.Node{ kind: .cast_expr, children_start: 1, children_count: 1 })
|
||||
snapshot := a.nodes[int(cast)]
|
||||
a.nodes[int(cast)].children_count = 0
|
||||
growth := a.nodes.cap + 100
|
||||
for _ in 0 .. growth {
|
||||
a.add_val(.ident, 'padding')
|
||||
}
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.fn_ret_types['saved'] = builtin_int_type
|
||||
tc.fn_param_types['saved'] = []Type{}
|
||||
assert tc.fn_value_key(snapshot) or { panic('missing wrapped function key') } == 'saved'
|
||||
for start in [-1, a.children.len, a.children.len + 100] {
|
||||
invalid := flat.Node{ kind: .call, children_start: start, children_count: 1 }
|
||||
assert tc.call_display_name(invalid) == ''
|
||||
assert tc.direct_call_return_type(invalid) == none
|
||||
}
|
||||
assert tc.call_display_name(flat.Node{ kind: .call }) == '<missing>'
|
||||
}
|
||||
|
||||
fn test_scalar_child_c_macro_and_signature_queries_keep_saved_headers() {
|
||||
mut a := flat.FlatAst.new()
|
||||
base := a.add_val(.ident, 'C')
|
||||
a.children << base
|
||||
selector := a.add_node(flat.Node{ kind: .selector, value: 'va_arg', children_start: 0, children_count: 1 })
|
||||
a.children << selector
|
||||
call := flat.Node{ kind: .call, children_start: 1, children_count: 1 }
|
||||
param := a.add_node(flat.Node{ kind: .param, value: 'arg', typ: flat.source_type_int })
|
||||
a.children << param
|
||||
decl := a.add_node(flat.Node{ kind: .fn_literal, typ: flat.source_type_bool, children_start: 2, children_count: 1 })
|
||||
signature := a.nodes[int(decl)]
|
||||
a.nodes[int(decl)].children_count = 0
|
||||
growth := a.nodes.cap + 100
|
||||
for _ in 0 .. growth {
|
||||
a.add_val(.ident, 'padding')
|
||||
}
|
||||
tc := TypeChecker.new(&a)
|
||||
assert tc.c_va_macro_call_name(call) == 'C.va_arg'
|
||||
assert tc.call_display_name(call) == 'C.va_arg'
|
||||
assert tc.fn_node_source_type_text(signature) == 'fn (arg int) bool'
|
||||
typ := tc.fn_literal_type(signature)
|
||||
assert typ is FnType
|
||||
if typ is FnType {
|
||||
assert typ.params.len == 1
|
||||
assert typ.params[0].typ.source_type_id() == flat.source_type_int
|
||||
assert typ.return_type.source_type_id() == flat.source_type_bool
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn selector_header_test_add(mut a flat.FlatAst, base flat.NodeId, name string) flat.NodeId {
|
||||
start := a.children.len
|
||||
a.children << base
|
||||
return a.add_node(flat.Node{ kind: .selector, value: name, children_start: start, children_count: 1 })
|
||||
}
|
||||
|
||||
fn test_enum_selector_base_helper_keeps_qualified_names_and_header_guards() {
|
||||
mut a := flat.FlatAst.new()
|
||||
module_name := a.add_val(.ident, 'dep')
|
||||
enum_name := selector_header_test_add(mut a, module_name, 'Color')
|
||||
selector := selector_header_test_add(mut a, enum_name, 'red')
|
||||
c_name := a.add_val(.ident, 'C')
|
||||
c_selector := selector_header_test_add(mut a, c_name, 'errno')
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_file = 'main.v'
|
||||
tc.cur_module = 'main'
|
||||
tc.register_file_import('dep', 'one')
|
||||
tc.enum_names['one.Color'] = true
|
||||
tc.enum_fields['one.Color'] = ['red']
|
||||
header := a.nodes[int(selector)]
|
||||
wrapped := tc.enum_selector_type(&header) or { panic('qualified wrapper missing') }
|
||||
assert wrapped.source_type_id() == flat.source_type_id('one.Color')
|
||||
direct := tc.selector_type(selector, header) or { panic('qualified direct missing') }
|
||||
assert direct.source_type_id() == flat.source_type_id('one.Color')
|
||||
mut invalid_kind := header
|
||||
invalid_kind.kind = .ident
|
||||
assert tc.enum_selector_type(&invalid_kind) == none
|
||||
mut invalid_count := header
|
||||
invalid_count.children_count = 0
|
||||
assert tc.enum_selector_type(&invalid_count) == none
|
||||
assert tc.selector_type(selector, invalid_count) == none
|
||||
mut missing := header
|
||||
missing.value = 'missing'
|
||||
assert tc.enum_selector_type(&missing) == none
|
||||
assert tc.selector_type(selector, missing) == none
|
||||
c_type := tc.selector_type(c_selector, a.nodes[int(c_selector)]) or { panic('C errno missing') }
|
||||
assert c_type.source_type_id() == flat.source_type_int
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_recent_sum_pattern_context_is_invalidated_by_clear() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_file = 'main.v'
|
||||
tc.cur_module = 'main'
|
||||
tc.sum_types.set('Choice', ['string'])
|
||||
sum_id := flat.source_type_id('Choice')
|
||||
variant_id := flat.source_type_int
|
||||
assert tc.sum_variant_type_for_pattern(sum_id, variant_id) == none
|
||||
assert !isnil(tc.type_cache.sum_variant_pattern_recent)
|
||||
tc.sum_types.set('Choice', ['int', 'string'])
|
||||
tc.type_cache.sum_variant_pattern_entries.clear()
|
||||
assert tc.sum_variant_type_for_pattern(sum_id, variant_id)? == variant_id
|
||||
assert tc.type_cache.sum_variant_pattern_recent_count == 1
|
||||
}
|
||||
|
||||
fn test_recent_sum_pattern_context_clear_and_repopulate_preserves_other_files() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_module = 'main'
|
||||
tc.sum_types.set('Choice', ['string'])
|
||||
sum_id := flat.source_type_id('Choice')
|
||||
variant_id := flat.source_type_int
|
||||
tc.cur_file = 'first.v'
|
||||
assert tc.sum_variant_type_for_pattern(sum_id, variant_id) == none
|
||||
tc.sum_types.set('Choice', ['int', 'string'])
|
||||
tc.type_cache.sum_variant_pattern_entries.clear()
|
||||
tc.cur_file = 'second.v'
|
||||
assert tc.sum_variant_type_for_pattern(sum_id, variant_id)? == variant_id
|
||||
assert tc.type_cache.sum_variant_pattern_entries.len == 1
|
||||
tc.cur_file = 'first.v'
|
||||
assert tc.sum_variant_type_for_pattern(sum_id, variant_id)? == variant_id
|
||||
assert tc.type_cache.sum_variant_pattern_entries.len == 2
|
||||
}
|
||||
|
||||
fn test_recent_sum_pattern_context_owns_file_and_module_guard_strings() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
mut file := 'first.v'.clone()
|
||||
mut mod_name := 'one'.clone()
|
||||
tc.cur_file = file
|
||||
tc.cur_module = mod_name
|
||||
tc.structs['one.Item'] = []StructField{}
|
||||
tc.structs['two.Item'] = []StructField{}
|
||||
tc.sum_types.set('one.Choice', ['one.Item'])
|
||||
tc.sum_types.set('two.Choice', ['two.Item'])
|
||||
sum_id := flat.source_type_id('Choice')
|
||||
variant_id := flat.source_type_id('Item')
|
||||
assert tc.sum_variant_type_for_pattern(sum_id, variant_id)? == flat.source_type_id('one.Item')
|
||||
// The directory owns the guard strings independently of the caller's spelling storage.
|
||||
unsafe {
|
||||
file.str[0] = `s`
|
||||
mod_name.str[0] = `t`
|
||||
mod_name.str[1] = `w`
|
||||
mod_name.str[2] = `o`
|
||||
}
|
||||
assert tc.sum_variant_type_for_pattern(sum_id, variant_id)? == flat.source_type_id('two.Item')
|
||||
assert tc.type_cache.sum_variant_pattern_entries.len == 2
|
||||
assert tc.type_cache.sum_variant_pattern_recent.file == 'sirst.v'
|
||||
assert tc.type_cache.sum_variant_pattern_recent.module == 'two'
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
module types
|
||||
|
||||
import v.flat
|
||||
|
||||
fn test_unqualified_builtin_type_candidates_keep_their_declared_owners() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_file = 'consumer.v'
|
||||
tc.cur_module = 'consumer'
|
||||
tc.struct_modules['Error'] = 'builtin'
|
||||
tc.struct_modules['User'] = 'other'
|
||||
tc.enum_names['BuiltinKind'] = true
|
||||
tc.flag_enums['BuiltinFlags'] = true
|
||||
assert tc.unqualified_type_symbol_is_builtin('Error')
|
||||
assert tc.unqualified_type_symbol_is_builtin('IError')
|
||||
assert tc.unqualified_type_symbol_is_builtin('BuiltinKind')
|
||||
assert tc.unqualified_type_symbol_is_builtin('BuiltinFlags')
|
||||
assert !tc.unqualified_type_symbol_is_builtin('User')
|
||||
assert !tc.unqualified_type_symbol_is_builtin('Unknown')
|
||||
assert !tc.unqualified_type_symbol_is_builtin('')
|
||||
// A non-builtin struct owner wins even if another table has the bare name.
|
||||
tc.enum_names['User'] = true
|
||||
assert !tc.unqualified_type_symbol_is_builtin('User')
|
||||
}
|
||||
|
||||
fn test_unqualified_builtin_types_are_shadowed_by_every_local_type_kind() {
|
||||
for kind in 0 .. 6 {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_file = 'consumer.v'
|
||||
tc.cur_module = 'consumer'
|
||||
tc.struct_modules['Error'] = 'builtin'
|
||||
assert tc.unqualified_type_symbol_is_builtin('Error')
|
||||
match kind {
|
||||
0 { tc.type_aliases.set('consumer.Error', 'int') }
|
||||
1 { tc.structs['consumer.Error'] = []StructField{} }
|
||||
2 { tc.interface_names['consumer.Error'] = true }
|
||||
3 { tc.flag_enums['consumer.Error'] = true }
|
||||
4 { tc.enum_names['consumer.Error'] = true }
|
||||
5 { tc.sum_types.set('consumer.Error', ['int', 'string']) }
|
||||
else {}
|
||||
}
|
||||
assert !tc.unqualified_type_symbol_is_builtin('Error'), 'type kind ${kind}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_unqualified_builtin_type_source_shadow_tracks_the_active_file() {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_file = 'consumer.v'
|
||||
tc.cur_module = 'consumer'
|
||||
tc.struct_modules['Error'] = 'builtin'
|
||||
tc.declared_type_scope_keys[scope_type_key('consumer.v', 'consumer', 'Error')] = true
|
||||
assert !tc.unqualified_type_symbol_is_builtin('Error')
|
||||
tc.cur_file = 'other.v'
|
||||
assert tc.unqualified_type_symbol_is_builtin('Error')
|
||||
}
|
||||
|
||||
fn test_unqualified_builtin_type_selective_imports_keep_shadow_priority() {
|
||||
for owner in ['other', 'builtin', 'ambiguous'] {
|
||||
a := flat.FlatAst.new()
|
||||
mut tc := TypeChecker.new(&a)
|
||||
tc.cur_file = 'consumer.v'
|
||||
tc.cur_module = 'consumer'
|
||||
tc.struct_modules['Error'] = 'builtin'
|
||||
candidates := if owner == 'ambiguous' { []string{} } else { ['${owner}.Error'] }
|
||||
tc.file_selective_imports[file_import_key(tc.cur_file, 'Error')] = candidates
|
||||
tc.file_imports_by_file[tc.cur_file] = &FileImportInfo{
|
||||
selective_imports: {
|
||||
'Error': candidates
|
||||
}
|
||||
}
|
||||
if owner != 'ambiguous' {
|
||||
tc.structs['${owner}.Error'] = []StructField{}
|
||||
tc.struct_modules['${owner}.Error'] = owner
|
||||
}
|
||||
assert tc.unqualified_type_symbol_is_builtin('Error') == (owner == 'builtin'), owner
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,88 @@ Every compiler fix must preserve or improve self-compilation efficiency. Agents
|
||||
retired instructions before and after compiler changes and resolve regressions before merging.
|
||||
Keep correctness tests and generated-output checks alongside performance measurements.
|
||||
|
||||
## Follow-up optimization, 2026-10-26
|
||||
|
||||
The pinned follow-up uses **8.0317% fewer retired instructions**,
|
||||
exceeding the accepted 7.5% target. This compares baseline
|
||||
`f7e9ee96c22ca6db47b56d28332e7553715c659f` with measured source revision
|
||||
`e71269ceddda4651e29b0363cae5f88bda0d4362`. The later integration with upstream
|
||||
`c2eda292429ae843a501180a9ad32eca7360e380` is validated separately and has not been
|
||||
remeasured; these numbers do not claim a reduction against that newer base.
|
||||
|
||||
The changes reuse immutable node snapshots, type references, and names; defer unnecessary
|
||||
checker, transform, markused, and C generation queries; and accelerate map, short-string, builder,
|
||||
and scanner operations. Regression tests cover cache lifetimes, custom map callbacks, overlapping
|
||||
builder writes, and borrowed storage. No language or CLI behavior change is intended.
|
||||
|
||||
| Median metric | Before | After |
|
||||
| --- | ---: | ---: |
|
||||
| Retired instructions | 59,509,416,234.5 | 54,729,825,042 |
|
||||
| Instructions/parsed line | 106,268.71 | 97,733.58 |
|
||||
| Elapsed time, ms | 15,262.58 | 12,667.81 |
|
||||
| Peak resident memory, MiB | 1,122.44 | 1,095.13 |
|
||||
| CPU cycles, billions | 28.2215 | 26.2491 |
|
||||
|
||||
One warmup per role precedes ten alternating measured pairs. All 22 runs succeed with exactly one
|
||||
positive retired-instruction counter each; warmups are excluded. **0/10 pairs regress**. The median
|
||||
paired instruction difference is `-4,629,942,000.5`.
|
||||
Elapsed time and cycles are contextual measurements under shared host load, without a controlled
|
||||
latency claim.
|
||||
|
||||
The fixture remains `2d1367e589ed30c03325e586a8ddfd3bc96c8c3d`, with all **8,498 file hashes
|
||||
unchanged**. Separate verbose inventories match: **559,990 physical lines in 562 parsed `.v`
|
||||
files**, no `.vh` files. Both roles use the same fixture, executable, and C output paths.
|
||||
The fixture executable is restored after the comparison.
|
||||
|
||||
Both compilers use the same compatible seed, then their own-source self build, then production,
|
||||
with Apple Clang 21 and **no PGO**. Restore each role to the same build path. Clear inherited
|
||||
`VFLAGS`, `VEXE`, `LLVM_PROFILE_FILE`, `CC`, `CFLAGS`, and `LDFLAGS`; use the environment below
|
||||
for `./v -cc /usr/bin/clang -gc none self` and the production build:
|
||||
|
||||
```sh
|
||||
env -u VFLAGS -u VEXE -u LLVM_PROFILE_FILE -u CC -u CFLAGS -u LDFLAGS \
|
||||
VJOBS=4 SOURCE_DATE_EPOCH=0 V_MACOS_V3_NO_FALLBACK=1 \
|
||||
V_MACOS_V3_VHASH=f7e9ee96c22ca6db47b56d28332e7553715c659f \
|
||||
./v -prod -cc /usr/bin/clang -gc none -no-memory-limit -prealloc \
|
||||
-compile-backend fastc -nocache -o ./v-perf cmd/v
|
||||
```
|
||||
|
||||
Install each production binary as `./v` in the fixed fixture, then measure serially:
|
||||
|
||||
```sh
|
||||
env -u VFLAGS -u VEXE -u LLVM_PROFILE_FILE -u CC -u CFLAGS -u LDFLAGS \
|
||||
VJOBS=1 SOURCE_DATE_EPOCH=0 V_MACOS_V3_NO_FALLBACK=1 \
|
||||
V_MACOS_V3_VHASH=2d1367e589ed30c03325e586a8ddfd3bc96c8c3d \
|
||||
/usr/bin/time -l -o "$run_log" \
|
||||
./v -nocache -gc none -prealloc -compile-backend fastc -building-v \
|
||||
-o /Users/alex/code/v-speed-next10-20261025/output.c cmd/v
|
||||
```
|
||||
|
||||
The common seed SHA-256 is
|
||||
`a17d929dbba4205a49f5fb0e0d178e70b3902b60074478765b37749a4bfb8d6d`.
|
||||
The baseline production SHA-256 is
|
||||
`e4e2c6b42f757a99a6159b21ebda12bd15af0aa927aaccd645e1dee762db6369`;
|
||||
the candidate production SHA-256 is
|
||||
`5d12d2dd03dab024837cf41d6ef4bd15f3967681f572d7ccf89342cfa0c8bec8`.
|
||||
|
||||
Generated C intentionally differs in audited local storage representations. Every sample matches
|
||||
its approved role output: baseline SHA-256
|
||||
`35f76ab8160d9172bfced3fea28a4970226b71f3e2da5e8445695c6e3ada8d32`, candidate
|
||||
`1fcc487b2a39c089dea4e511dd4d14c6878b00e7046acaceef038ddd90c191a3`.
|
||||
Both outputs pass Clang syntax checks and native linking. Both linked compilers generate
|
||||
byte-identical second-generation C, SHA-256
|
||||
`4b3998af7880683073a01a21ff3f726c61f2058e6c933bd8bf7f50838f46cb27`.
|
||||
|
||||
Evidence remains under `/Users/alex/code/v-speed-next10-20261025/`:
|
||||
`c8-integrated-next/final-ten-pair/` contains all raw logs, commands, environment, inventories,
|
||||
samples, summary, and integrity checks; its sibling build and output directories retain binaries
|
||||
and generated C. Baseline build records are in `c8-integrated-original/`.
|
||||
`c9-final-validation/` pins the measured source commit and records 26 passing build/execution
|
||||
commands, with 70 test-function invocations across final-source and exact-production checks.
|
||||
Latest-base integration checks are recorded separately in `pr-integration-evidence/` and the
|
||||
`pr-integration-validation-*` directories. The full suite and `test-all` are skipped at the user's
|
||||
explicit request. Historical broader validation below belongs to its recorded revisions.
|
||||
|
||||
## Upstream integration, 2026-10-25
|
||||
|
||||
A fresh comparison of the merged implementation against upstream
|
||||
|
||||
Reference in new issue
Block a user