cache: reuse bootstrap builtin objects in test builds (#31219)
* cache: retain builtin objects across test compilations * checker: recognize builtin lifecycle attributes * cache: stabilize ignored local names across source reparsing * transform: preserve generic struct map return types * cache: preserve error dispatch IDs and portable make warmups
This commit is contained in:
1 parent
7ec48941f8
commit
75365168ad
14 files changed
+300
-28
No files matched your search
@@ -291,6 +291,12 @@ endif
|
||||
@echo "V has been successfully built"
|
||||
@$(VEXE)$(EXE_EXT) -version
|
||||
@$(VEXE)$(EXE_EXT) $(POST_BOOTSTRAP_CCOMPILER_VFLAG) -new-compiler -gc none run .github/problem-matchers/register_all.vsh
|
||||
@set -e; \
|
||||
cache_gcflags=; \
|
||||
if [ ! -f "$(TMPTCC)/lib/libgc.a" ] && [ ! -f "$(TMPTCC)/lib/libgc.dylib" ]; then \
|
||||
cache_gcflags='-gc none'; \
|
||||
fi; \
|
||||
$(VEXE)$(EXE_EXT) $$cache_gcflags $(VFLAGS) $(POST_BOOTSTRAP_CCOMPILER_VFLAG) -new-compiler cmd/tools/cache_basic_modules_test.v
|
||||
|
||||
v1:
|
||||
ifdef LEGACY
|
||||
|
||||
@@ -148,7 +148,12 @@ v:
|
||||
./v -new-compiler run ./cmd/tools/detect_tcc.v; \
|
||||
echo "V has been successfully built"; \
|
||||
./v version; \
|
||||
./v -new-compiler run .github/problem-matchers/register_all.vsh
|
||||
./v -new-compiler run .github/problem-matchers/register_all.vsh; \
|
||||
cache_gcflags=; \
|
||||
if [ ! -f thirdparty/tcc/lib/libgc.a ] && [ ! -f thirdparty/tcc/lib/libgc.dylib ]; then \
|
||||
cache_gcflags='-gc none'; \
|
||||
fi; \
|
||||
./v $$cache_gcflags $(VFLAGS) -new-compiler cmd/tools/cache_basic_modules_test.v
|
||||
|
||||
v1:
|
||||
@set -e; \
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// `make` runs this file to populate the default test configuration as well as
|
||||
// the program configuration used by its bootstrap helpers. Tests can then link
|
||||
// the builtin, strings, strconv, hash and bits bundle without rebuilding it.
|
||||
import strconv
|
||||
import strings
|
||||
|
||||
fn test_cached_basic_modules() {
|
||||
assert strconv.atoi('42')! == 42
|
||||
assert strings.repeat(120, 2) == 'xx'
|
||||
}
|
||||
+14
-1
@@ -69,6 +69,17 @@ Different build flags, program or test modes, and import sequences can require
|
||||
separate checks. Pass `v up -v` to see each checked configuration.
|
||||
Cached module headers preserve struct field attribute groups, so valid deprecation
|
||||
attributes do not introduce warnings during refresh.
|
||||
|
||||
`make` also warms the default test configuration's builtin module bundle, including
|
||||
`strings`, `strconv`, `hash` and `bits`. Compatible test builds reuse these objects
|
||||
when the program introduces new error types, retaining builtin error dispatch ID dependencies.
|
||||
Dispatch hash collisions retain the full error-type dependency. Without a bundled collector,
|
||||
the warmup uses `-gc none`; an explicit `-gc` setting takes precedence.
|
||||
A missing object for another module rebuilds that module while retaining compatible cached
|
||||
modules. Ignored local names remain stable when cached headers replace source files, so
|
||||
reparsed modules can reuse identical C objects. Compiler flags, source changes and dependency
|
||||
changes still invalidate the affected objects.
|
||||
|
||||
Ordinary builds report cache progress on standard error. Refreshes retain absolute
|
||||
module search paths, resolved C compiler commands, and build modes. They omit
|
||||
linker-only flags, including operands embedded in `-cflags`. Relative `V3CACHE`
|
||||
@@ -1590,6 +1601,7 @@ println(upper_fn) // ['HELLO', 'WORLD']
|
||||
`it` is a builtin variable which refers to the element currently being
|
||||
processed in filter/map methods.
|
||||
|
||||
User-defined struct methods named `map` keep their declared return type, including when chained.
|
||||
Additionally, `.any()` and `.all()` can be used to conveniently test
|
||||
for elements that satisfy a condition.
|
||||
|
||||
@@ -8335,7 +8347,8 @@ Call-style attributes can also use named arguments.
|
||||
The C backend emits `@[_constructor]` and `@[_destructor]` as GNU function attributes before the
|
||||
function declarator, so definitions compile with GCC and Clang. When supported by the C compiler,
|
||||
these hooks run before `main` and after it returns. They must not depend on V's runtime being
|
||||
initialized. MSVC ignores these attributes.
|
||||
initialized. Both are builtin attributes and require no user-defined attribute schema.
|
||||
MSVC ignores these attributes.
|
||||
|
||||
```v
|
||||
// @[flag] enables Enum types to be used as bitfields
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
import time
|
||||
import v.cmdexec
|
||||
|
||||
fn test_cached_builtin_survives_new_error_implementers_and_module_misses() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'v_cache_builtin_reuse_${os.getpid()}_${time.now().unix_nano()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
// Keep the compiler configuration and imported modules fixed while changing
|
||||
// the program's error implementations. Each file is a fresh test program.
|
||||
for index in 0 .. 3 {
|
||||
name := 'PayloadError${index}'
|
||||
source := os.join_path(root, 'program_${index}_test.v')
|
||||
os.write_file(source, '
|
||||
import os
|
||||
import strconv
|
||||
struct ${name} {
|
||||
Error
|
||||
value int
|
||||
}
|
||||
|
||||
fn (e ${name}) msg() string { return "payload " + e.value.str() }
|
||||
fn (e ${name}) code() int { return e.value }
|
||||
fn test_error_dispatch() {
|
||||
err := IError(${name}{value: 42})
|
||||
assert err.msg() == "payload 42"
|
||||
assert err.code() == 42
|
||||
assert err.str().contains("payload 42")
|
||||
assert strconv.atoi("42")! == 42
|
||||
assert os.getpid() > 0
|
||||
}
|
||||
')!
|
||||
result := cmdexec.run_in('env', ['-u', 'VEXE', '-u', 'VFLAGS', 'V3_CACHE_TRACE=1',
|
||||
'V3CACHE=${root}/cache', @VEXE, '-new-compiler', '-v', source], root)
|
||||
assert result.exit_code == 0, result.output
|
||||
if index == 0 {
|
||||
assert result.output.contains('Caching module builtin...'), result.output
|
||||
} else {
|
||||
assert !result.output.contains('Caching module builtin...'), result.output
|
||||
assert result.output.contains('object miss: module=os'), result.output
|
||||
// An imported object's interface signature can miss; that retry must
|
||||
// still parse the builtin declaration header rather than its sources.
|
||||
assert result.output.contains('.vh files:'), result.output
|
||||
assert !result.output.contains('/builtin/array.v'), result.output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cached_builtin_invalidates_for_colliding_error_dispatch_ids() {
|
||||
$if windows {
|
||||
return
|
||||
}
|
||||
root := os.join_path(os.vtmp_dir(), 'v_cache_error_collision_${os.getpid()}_${time.now().unix_nano()}')
|
||||
os.mkdir_all(root)!
|
||||
defer { os.rmdir_all(root) or {} }
|
||||
// The added names collide with Error, MessageError and None__, respectively.
|
||||
for index, declarations in ['',
|
||||
'
|
||||
struct ABuecGy { Error }
|
||||
struct A8n7fFM { Error }
|
||||
struct AH8ogMV { Error }
|
||||
'] {
|
||||
source := os.join_path(root, 'collision_${index}_test.v')
|
||||
os.write_file(source, declarations + '
|
||||
struct ErrorHolder { err IError }
|
||||
fn test_builtin_error_dispatch() {
|
||||
plain := IError(Error{})
|
||||
assert plain is Error
|
||||
assert plain.msg() == ""
|
||||
assert plain.code() == 0
|
||||
message := error_with_code("detail", 37)
|
||||
assert message is MessageError
|
||||
assert message.str() == "detail; code: 37"
|
||||
assert message.msg() == "detail"
|
||||
assert message.code() == 37
|
||||
none_error := ErrorHolder{err: none}
|
||||
assert none_error.err.str() == "none"
|
||||
}
|
||||
')!
|
||||
result := cmdexec.run_in('env', ['-u', 'VEXE', '-u', 'VFLAGS', 'V3CACHE=${root}/cache',
|
||||
@VEXE, '-new-compiler', '-v', source], root)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert result.output.contains('Caching module builtin...'), result.output
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
module driver
|
||||
|
||||
import v.flat
|
||||
import v.types
|
||||
|
||||
// The object of a cached module is found under the implementer lists that the
|
||||
// code of the module can depend on. These tests give module_signature() the
|
||||
@@ -68,6 +69,49 @@ fn test_module_signature_changes_with_an_implementer_of_a_visible_interface() {
|
||||
assert scopes.module_signature(visible_modules('io')) != other.module_signature(visible_modules('io'))
|
||||
}
|
||||
|
||||
fn test_builtin_object_signature_does_not_depend_on_error_implementers() {
|
||||
signature := scope_signature + '\n#reach MyErr=Shape'
|
||||
scopes := parse_v3_interface_scopes(signature)
|
||||
other := parse_v3_interface_scopes(signature.replace('IError=Error,MessageError,io.Eof,MyErr',
|
||||
'IError=Error,MessageError,io.Eof,OtherErr').replace('Shape=Circle,Square',
|
||||
'Shape=Circle,Square,Triangle') + '\n#module OtherErr=main\n#reach OtherErr=Shape')
|
||||
assert scopes.object_signature(visible_modules(), true) == '#builtin IError=Error=1938660593,MessageError=71273906'
|
||||
assert scopes.object_signature(visible_modules(), true) == other.object_signature(visible_modules(), true)
|
||||
// Imported module objects can inspect error payloads and retain their scope.
|
||||
assert scopes.module_signature(visible_modules()) != other.module_signature(visible_modules())
|
||||
// Other visible interfaces still affect the bundle's generated helpers.
|
||||
changed_reader := parse_v3_interface_scopes(signature.replace('io.Reader=io.BufferedReader,os.File,Feed',
|
||||
'io.Reader=io.BufferedReader,os.File,Feed,OtherReader'))
|
||||
assert scopes.object_signature(visible_modules('io'), true) != changed_reader.object_signature(visible_modules('io'), true)
|
||||
// A project module named `hash` is not the builtin bundle.
|
||||
state := V3ModuleCacheState{}
|
||||
assert v3_cached_object_alias_signature('', scopes, state, ['hash'], []) != v3_cached_object_alias_signature('', other, state, [
|
||||
'hash',
|
||||
], [])
|
||||
}
|
||||
|
||||
fn test_builtin_object_signature_retains_error_dispatch_collisions() {
|
||||
// These names have the same dispatch hashes as Error, MessageError and None__.
|
||||
for builtin_name, colliding_name in {
|
||||
'Error': 'ABuecGy'
|
||||
'MessageError': 'A8n7fFM'
|
||||
'None__': 'AH8ogMV'
|
||||
} {
|
||||
builtin_id := flat.source_type_id(builtin_name)
|
||||
collision_id := flat.source_type_id(colliding_name)
|
||||
ids := types.stable_interface_type_ids([collision_id, builtin_id])
|
||||
assert ids[builtin_id] != types.stable_interface_type_ids([builtin_id])[builtin_id]
|
||||
original := 'IError=${builtin_name}\n#module ${builtin_name}=builtin\n#module IError=builtin'
|
||||
scopes := parse_v3_interface_scopes(original)
|
||||
changed := parse_v3_interface_scopes(original.replace('IError=${builtin_name}',
|
||||
'IError=${colliding_name},${builtin_name}') + '\n#module ${colliding_name}=main')
|
||||
assert scopes.object_signature(visible_modules(), true) != changed.object_signature(visible_modules(), true)
|
||||
assert changed.object_signature(visible_modules(), true) == changed.module_signature(visible_modules())
|
||||
}
|
||||
unknown := parse_v3_interface_scopes('IError=Mystery\n#module IError=builtin')
|
||||
assert unknown.object_signature(visible_modules(), true) == 'IError=Mystery'
|
||||
}
|
||||
|
||||
// An implementer that the module cannot name brings the interfaces among its fields
|
||||
// with it: the code that tells the implementers of `io.Reader` apart, to compare or
|
||||
// to print them, handles the `Shape` inside a `Feed` as well.
|
||||
|
||||
+64
-12
@@ -191,6 +191,7 @@ mut:
|
||||
objects_only bool // the program unit is compiled and linked with the objects directly: no dylib, no program plan
|
||||
no_program_declarations bool // nothing reads the declarations of the program unit on its own: no dev dylib, no generic or incremental plan
|
||||
force_source bool
|
||||
force_source_modules []string
|
||||
bundle_valid bool
|
||||
calls_recover bool // the code of a module of this build calls `recover()`, see cache_module_calls_recover
|
||||
module_sources map[string][]string
|
||||
@@ -13184,6 +13185,7 @@ pub fn run(args []string) {
|
||||
bundle_sources: bundle_sources
|
||||
bundle_source_paths: module_cache_source_path_set(p.a, bundle_sources)
|
||||
force_source: force_cache_source
|
||||
force_source_modules: os.getenv(v3_internal_cache_force_source_env).split_into_lines()
|
||||
module_sources: map[string][]string{}
|
||||
module_import_paths: map[string]string{}
|
||||
module_dependencies: map[string][]string{}
|
||||
@@ -13208,7 +13210,7 @@ pub fn run(args []string) {
|
||||
cache_state.module_sources['builtin'] = builtin_files
|
||||
mut files := []string{}
|
||||
mut loaded_cached_bundle := false
|
||||
if !force_cache_source {
|
||||
if !force_cache_source && 'builtin' !in cache_state.force_source_modules {
|
||||
if bundle_object := cache_manager.valid_object('builtin', bundle_sources) {
|
||||
if builtin_header := cache_manager.valid_header('builtin', builtin_files) {
|
||||
record_v3_cached_source_digests(mut cache_state, builtin_header.source_digests)
|
||||
@@ -16578,7 +16580,6 @@ pub fn run(args []string) {
|
||||
compile_signature += program_suffix
|
||||
objects := cache_state.manager.valid_cgen_prepared_objects(cgen_cache_entry, compile_signature) or {
|
||||
if resolve_flag_specific_cache_objects(mut cache_state, a, object_flags_signature + program_suffix, parse_v3_interface_scopes(interface_impl_signature)) {
|
||||
os.setenv(v3_internal_cache_force_source_env, '1', true)
|
||||
restart_v3_after_cache_invalidation(cc_dir)
|
||||
}
|
||||
resolved_objects := cache_object_paths(cache_state.objects)
|
||||
@@ -18380,7 +18381,6 @@ fn v3_incremental_program_main_source(cached_prefix string, body_source string)
|
||||
|
||||
fn prepare_v3_incremental_cached_body(body_path string, prefix_path string, tcc_declarations_path string, cached_prefix string, object_base string, interface_impl_signature string, a &flat.FlatAst, mut state V3ModuleCacheState) !V3PreparedModuleCache {
|
||||
if resolve_flag_specific_cache_objects(mut state, a, object_base, parse_v3_interface_scopes(interface_impl_signature)) {
|
||||
os.setenv(v3_internal_cache_force_source_env, '1', true)
|
||||
restart_v3_after_cache_invalidation(state.c_build_dir)
|
||||
}
|
||||
objects := cache_object_paths(state.objects)
|
||||
@@ -18404,7 +18404,6 @@ fn prepare_v3_cached_generic_body(generated_source string, cached_prefix string,
|
||||
return error('v3 module cache directory is unavailable')
|
||||
}
|
||||
if resolve_flag_specific_cache_objects(mut state, a, object_base, parse_v3_interface_scopes(interface_impl_signature)) {
|
||||
os.setenv(v3_internal_cache_force_source_env, '1', true)
|
||||
restart_v3_after_cache_invalidation(state.c_build_dir)
|
||||
}
|
||||
incremental_c_function_sections(generated_source) or {
|
||||
@@ -18452,7 +18451,6 @@ fn prepare_v3_module_cache(generated_source string, cache_used_fns &map[string]b
|
||||
mut has_declarations := false
|
||||
scopes := parse_v3_interface_scopes(interface_impl_signature)
|
||||
if resolve_flag_specific_cache_objects(mut state, tc.a, object_base, scopes) {
|
||||
os.setenv(v3_internal_cache_force_source_env, '1', true)
|
||||
restart_v3_after_cache_invalidation(state.c_build_dir)
|
||||
}
|
||||
main_body := split.modules['main'] or { '' }
|
||||
@@ -18637,7 +18635,6 @@ fn prepare_v3_module_cache(generated_source string, cache_used_fns &map[string]b
|
||||
// Refresh stops before the program link could discover that mismatch.
|
||||
state.objects = object_paths.clone()
|
||||
if resolve_flag_specific_cache_objects(mut state, tc.a, object_base, scopes) {
|
||||
os.setenv(v3_internal_cache_force_source_env, '1', true)
|
||||
restart_v3_after_cache_invalidation(state.c_build_dir)
|
||||
}
|
||||
}
|
||||
@@ -20037,9 +20034,44 @@ fn (scopes &V3InterfaceScopes) declaring_module(name_id u32) string {
|
||||
// An interface or an implementer that the signature says too little about makes
|
||||
// the module depend on every list, as all of them did before.
|
||||
fn (scopes &V3InterfaceScopes) module_signature(visible map[string]bool) string {
|
||||
return scopes.object_signature(visible, false)
|
||||
}
|
||||
|
||||
fn (scopes &V3InterfaceScopes) builtin_error_signature(name u32) ?string {
|
||||
impls := scopes.impls[name]
|
||||
ids := types.stable_interface_type_ids(impls)
|
||||
mut builtin_ids := []string{}
|
||||
for impl in impls {
|
||||
// Retain the full dependency when hashes collide. The signature can also
|
||||
// contain open generic types that code generation leaves out of dispatch.
|
||||
seed := types.stable_interface_type_ids([impl])
|
||||
if ids[impl] != seed[impl] {
|
||||
return none
|
||||
}
|
||||
owner := scopes.declaring_module(impl)
|
||||
if owner == '?' {
|
||||
return none
|
||||
}
|
||||
if owner == 'builtin' {
|
||||
builtin_ids << '${flat.source_type_name(impl)}=${ids[impl]}'
|
||||
}
|
||||
}
|
||||
return '#builtin ${flat.source_type_name(name)}=${builtin_ids.join(',')}'
|
||||
}
|
||||
|
||||
fn (scopes &V3InterfaceScopes) object_signature(visible map[string]bool, builtin_bundle bool) string {
|
||||
mut in_scope := map[u32]bool{}
|
||||
mut pending := []u32{cap: scopes.names.len}
|
||||
mut lines := []string{cap: scopes.names.len}
|
||||
for i, name in scopes.names {
|
||||
// Dispatch stubs belong to the program, but builtin error boxing and the
|
||||
// None__ check embed builtin dispatch IDs. Preserve those dependencies.
|
||||
if builtin_bundle && flat.source_type_name(name) in ['IError', 'builtin.IError'] {
|
||||
if signature := scopes.builtin_error_signature(name) {
|
||||
lines << signature
|
||||
continue
|
||||
}
|
||||
}
|
||||
owner := scopes.owners[i]
|
||||
// The code of a module cannot name an interface of the program.
|
||||
if owner == 'main' {
|
||||
@@ -20079,7 +20111,6 @@ fn (scopes &V3InterfaceScopes) module_signature(visible map[string]bool) string
|
||||
}
|
||||
}
|
||||
}
|
||||
mut lines := []string{cap: pending.len}
|
||||
for name in scopes.names {
|
||||
if in_scope[name] {
|
||||
lines << scopes.lines[name]
|
||||
@@ -20127,7 +20158,8 @@ fn cache_object_visible_modules(state &V3ModuleCacheState, roots []string, commo
|
||||
// module from its header finds the object under this signature.
|
||||
fn v3_cached_object_alias_signature(object_base string, scopes &V3InterfaceScopes, state &V3ModuleCacheState, roots []string, common []string) string {
|
||||
visible := cache_object_visible_modules(state, roots, common)
|
||||
return '${object_base}\ninterfaces=${scopes.module_signature(visible)}'
|
||||
builtin_bundle := 'builtin' in roots && roots.all(it in modulecache.builtin_bundle_modules)
|
||||
return '${object_base}\ninterfaces=${scopes.object_signature(visible, builtin_bundle)}'
|
||||
}
|
||||
|
||||
// v3_cached_object_content_signature identifies a module object by how it is
|
||||
@@ -20448,6 +20480,7 @@ fn resolve_flag_specific_cache_objects(mut state V3ModuleCacheState, a &flat.Fla
|
||||
return false
|
||||
}
|
||||
common := cache_common_visible_modules(state, a)
|
||||
mut missing := false
|
||||
for object_name in state.objects.keys() {
|
||||
roots := if object_name == 'builtin' {
|
||||
cache_builtin_bundle_roots(state, a)
|
||||
@@ -20464,7 +20497,9 @@ fn resolve_flag_specific_cache_objects(mut state V3ModuleCacheState, a &flat.Fla
|
||||
if os.getenv('V3_CACHE_TRACE') != '' {
|
||||
eprintln(' V3 module cache object miss: module=${object_name} reason=no object for these C compilation flags and interface implementations')
|
||||
}
|
||||
return true
|
||||
force_v3_cache_object_sources(object_name)
|
||||
missing = true
|
||||
continue
|
||||
}
|
||||
dependency_inputs := cache_object_dependency_signatures(state, a, roots)
|
||||
if entry := state.manager.valid_object_for_compile_signature(object_name, source_files, v3_cached_object_content_signature(object_base, content_key), dependency_inputs) {
|
||||
@@ -20473,10 +20508,26 @@ fn resolve_flag_specific_cache_objects(mut state V3ModuleCacheState, a &flat.Fla
|
||||
if os.getenv('V3_CACHE_TRACE') != '' {
|
||||
eprintln(' V3 module cache object miss: module=${object_name}')
|
||||
}
|
||||
return true
|
||||
force_v3_cache_object_sources(object_name)
|
||||
missing = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
return missing
|
||||
}
|
||||
|
||||
// force_v3_cache_object_sources reparses only the objects whose configuration
|
||||
// changed. Other cached modules keep their headers and objects on the retry.
|
||||
fn force_v3_cache_object_sources(object_name string) {
|
||||
current := os.getenv(v3_internal_cache_force_source_env)
|
||||
if current == '1' {
|
||||
return
|
||||
}
|
||||
mut modules := current.split_into_lines().filter(it.len > 0)
|
||||
if object_name !in modules {
|
||||
modules << object_name
|
||||
}
|
||||
os.setenv(v3_internal_cache_force_source_env, modules.join('\n'), true)
|
||||
}
|
||||
|
||||
fn (state &V3ModuleCacheState) print_module_cache_notice(module_name string) {
|
||||
@@ -25713,7 +25764,8 @@ fn resolve_imports(mut a flat.FlatAst, mut p parser.Parser, prefs &pref.Preferen
|
||||
cache_state.parsed_from_source[cache_module] = true
|
||||
cache_state.source_body_modules[cache_module] = true
|
||||
}
|
||||
} else if !cache_state.force_source {
|
||||
} else if !cache_state.force_source
|
||||
&& cache_module !in cache_state.force_source_modules {
|
||||
if cached := cache_state.manager.valid_entry_with_metadata_cache(cache_module, mod_files, mut cache_state.dependency_metadata) {
|
||||
record_v3_cached_source_digests(mut cache_state, cached.source_digests)
|
||||
cache_module_calls_recover(mut cache_state, modulecache.header_calls_recover(cached))
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
module flat
|
||||
|
||||
import os
|
||||
import hash
|
||||
import v.token
|
||||
|
||||
// discard_name identifies an ignored local by its source file and byte span.
|
||||
// File table indices change when cached headers replace parsed source files.
|
||||
// Synthetic positions without a named source retain a separate index namespace.
|
||||
pub fn (a &FlatAst) discard_name(pos token.Pos) string {
|
||||
if file := a.source_files[pos.id] {
|
||||
if file.name.len > 0 {
|
||||
return '__discard_${hash.sum64_string(file.name, 0)}_${pos.offset}_${pos.end}'
|
||||
}
|
||||
}
|
||||
return '__discard_synthetic_${pos.id}_${pos.offset}_${pos.end}'
|
||||
}
|
||||
|
||||
// record_source_path returns os.real_path(path) through the table of resolved
|
||||
// source paths. Until resolve_source_paths freezes the table it also adds the
|
||||
|
||||
@@ -3,6 +3,30 @@ module flat
|
||||
import os
|
||||
import v.token
|
||||
|
||||
fn test_discard_names_survive_source_file_renumbering() {
|
||||
mut file_set := token.FileSet.new()
|
||||
mut a := FlatAst.new()
|
||||
a.source_files[1] = file_set.add_file('library/first.v', 20)
|
||||
a.source_files[2] = file_set.add_file('library/second.v', 20)
|
||||
first := a.discard_name(token.new_span(1, 4, 5))
|
||||
second := a.discard_name(token.new_span(2, 4, 5))
|
||||
assert first != second
|
||||
assert first != a.discard_name(token.new_span(1, 6, 7))
|
||||
assert first != a.discard_name(token.new_span(1, 4, 6))
|
||||
a.source_files.clear()
|
||||
// A warm compilation can replace earlier files with a single cached header.
|
||||
a.source_files[17] = file_set.add_file('library/first.v', 20)
|
||||
a.source_files[3] = file_set.add_file('library/second.v', 20)
|
||||
assert first == a.discard_name(token.new_span(17, 4, 5))
|
||||
assert second == a.discard_name(token.new_span(3, 4, 5))
|
||||
// Missing and unnamed source entries use the same explicit fallback.
|
||||
missing := a.discard_name(token.new_span(9, 4, 5))
|
||||
a.source_files[9] = file_set.add_file('', 20)
|
||||
assert missing == a.discard_name(token.new_span(9, 4, 5))
|
||||
assert missing != a.discard_name(token.new_span(10, 4, 5))
|
||||
assert missing != first
|
||||
}
|
||||
|
||||
// real_source_path must answer exactly like os.real_path, from the table for
|
||||
// parsed sources and directly for anything else, and must never add to the
|
||||
// table: threads read it concurrently once it is built.
|
||||
|
||||
+3
-4
@@ -10361,11 +10361,10 @@ fn (mut g FlatGen) decl_lhs_str(id flat.NodeId) string {
|
||||
return g.expr_to_string(id)
|
||||
}
|
||||
|
||||
// discard_name derives ignored-local names from their source span. NodeIds for
|
||||
// generated AST nodes depend on parallel append-region sizes and are not stable.
|
||||
// discard_name derives ignored-local names from their source file and span.
|
||||
// NodeIds and file table indices both change when cached modules replace sources.
|
||||
fn (g &FlatGen) discard_name(id flat.NodeId) string {
|
||||
pos := g.a.nodes[int(id)].pos
|
||||
return '__discard_${pos.id}_${pos.offset}_${pos.end}'
|
||||
return g.a.discard_name(g.a.nodes[int(id)].pos)
|
||||
}
|
||||
|
||||
// The name a current parameter is read under. The identifier path reads one that
|
||||
|
||||
@@ -30,3 +30,17 @@ fn test_generics_method_chaining_call() {
|
||||
println(s)
|
||||
assert s.ar == [3, 4]
|
||||
}
|
||||
|
||||
fn test_generics_method_chaining_changes_receiver_and_result_types() {
|
||||
s := Seq[int]{[1, 22]}
|
||||
.map[string](fn (element int) string {
|
||||
return element.str()
|
||||
})
|
||||
.map[bool](fn (element string) bool {
|
||||
return element.len > 1
|
||||
})
|
||||
.map[string](fn (element bool) string {
|
||||
return if element { 'long' } else { 'short' }
|
||||
})
|
||||
assert s.ar == ['short', 'long']
|
||||
}
|
||||
@@ -1066,7 +1066,7 @@ fn (mut t Transformer) lower_range_for_in(id flat.NodeId, node flat.Node, key_id
|
||||
loop_name := if escaping_value {
|
||||
t.new_temp('range_index')
|
||||
} else if key.value == '_' {
|
||||
'__discard_${key.pos.id}_${key.pos.offset}_${key.pos.end}'
|
||||
t.a.discard_name(key.pos)
|
||||
} else {
|
||||
key.value
|
||||
}
|
||||
|
||||
@@ -2462,6 +2462,16 @@ fn (t &Transformer) array_map_call_type_name(id flat.NodeId, node flat.Node) ?u3
|
||||
if fn_node.kind != .selector || fn_node.value != 'map' || fn_node.children_count == 0 {
|
||||
return none
|
||||
}
|
||||
// User-defined map methods retain their declared return type.
|
||||
base_id := t.a.child(fn_node, 0)
|
||||
mut base_type0 := t.node_type(base_id)
|
||||
if !flat.source_type_shape(t.trim_pointer_type(base_type0)).has(.array_type) {
|
||||
base_type0 = t.original_expr_type(base_id)
|
||||
}
|
||||
base_type := t.trim_pointer_type(base_type0)
|
||||
if !flat.source_type_shape(base_type).has(.array_type) {
|
||||
return none
|
||||
}
|
||||
map_expr_id := t.a.child(&node, 1)
|
||||
if callback_ret_source_id := t.array_map_callback_return_type_name(map_expr_id) {
|
||||
callback_ret := flat.source_type_name(callback_ret_source_id)
|
||||
@@ -2475,15 +2485,6 @@ fn (t &Transformer) array_map_call_type_name(id flat.NodeId, node flat.Node) ?u3
|
||||
return checker_type_source_id
|
||||
}
|
||||
}
|
||||
base_id := t.a.child(fn_node, 0)
|
||||
mut base_type0 := t.node_type(base_id)
|
||||
if !flat.source_type_shape(t.trim_pointer_type(base_type0)).has(.array_type) {
|
||||
base_type0 = t.original_expr_type(base_id)
|
||||
}
|
||||
base_type := t.trim_pointer_type(base_type0)
|
||||
if !flat.source_type_shape(base_type).has(.array_type) {
|
||||
return none
|
||||
}
|
||||
mut elem_type_source_id := if callback_ret_source_id := t.array_map_callback_return_type_name(map_expr_id) {
|
||||
callback_ret_source_id
|
||||
} else if checker_type_source_id := t.checker_expr_type_name(map_expr_id) {
|
||||
|
||||
@@ -18216,6 +18216,8 @@ fn is_attribute_target_name(name string) bool {
|
||||
// exact and case-sensitive: a name differing only in casing is unknown.
|
||||
const builtin_attribute_names = [
|
||||
'_allow_multiple_values',
|
||||
'_constructor',
|
||||
'_destructor',
|
||||
'_linker_section',
|
||||
'_naked',
|
||||
'_pack',
|
||||
|
||||
Reference in new issue
Block a user