From d8f531b15209c4911c41e07dfe85003ae2dc3294 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Nov 2026 07:45:17 +0300 Subject: [PATCH 1/5] transform: materialize generic callback field payload types --- .../generic_callback_field_payload_test.v | 26 +++++++++++++++++++ vlib/v/transform/monomorphize.v | 11 ++++++++ 2 files changed, 37 insertions(+) create mode 100644 vlib/v/tests/generics/generic_callback_field_payload_test.v diff --git a/vlib/v/tests/generics/generic_callback_field_payload_test.v b/vlib/v/tests/generics/generic_callback_field_payload_test.v new file mode 100644 index 00000000000000..716c588593e826 --- /dev/null +++ b/vlib/v/tests/generics/generic_callback_field_payload_test.v @@ -0,0 +1,26 @@ +struct CallbackInput[T] { + value T +} + +struct CallbackResult[T] { + value T +} + +type BatchCallback[T] = fn (input &CallbackInput[T]) !CallbackResult[T] + +type OptionalBatchCallback[T] = fn (input CallbackInput[T]) ?CallbackResult[T] + +struct CallbackHolder[T] { + value T + batch BatchCallback[T] = unsafe { nil } + optional OptionalBatchCallback[T] = unsafe { nil } +} + +fn test_generic_callback_field_materializes_its_payload_types() { + // Neither callback is called: only the field signatures require the concrete + // CallbackInput[f64] and CallbackResult[f64] declarations. + holder := CallbackHolder[f64]{ value: 3.5 } + assert holder.value == 3.5 + assert holder.batch == unsafe { nil } + assert holder.optional == unsafe { nil } +} diff --git a/vlib/v/transform/monomorphize.v b/vlib/v/transform/monomorphize.v index 563108a7a20e34..ede378324f59ea 100644 --- a/vlib/v/transform/monomorphize.v +++ b/vlib/v/transform/monomorphize.v @@ -3816,6 +3816,17 @@ fn (mut t Transformer) collect_generic_struct_spec_from_type(typ_id u32, module_ t.collect_generic_struct_spec_from_type(syntax.raw_base(), module_name, file_name, decls, mut specs) return } + if shape.has(.function_compact) || shape.has(.function_spaced) { + // A concrete callback field can be the only use of its parameter and + // return specializations, including a result hidden behind a generic alias. + if syntax.function_closed() { + for param in syntax.function_params() { + t.collect_generic_struct_spec_from_type(flat.source_type_syntax(param).legacy_parameter_mut_type(), module_name, file_name, decls, mut specs) + } + t.collect_generic_struct_spec_from_type(syntax.function_result(), module_name, file_name, decls, mut specs) + } + return + } if shape.has(.map_type) { if syntax.bracket_closed() { t.collect_generic_struct_spec_from_type(syntax.bracket_raw_inner(), module_name, file_name, decls, mut specs) From 4ec4fe9a77ecb741a7b54cf1ac52e8b5723263b1 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Nov 2026 07:45:19 +0300 Subject: [PATCH 2/5] canvas: test clip restoration without initializing a GPU --- vlib/gg/canvas/gpu_clip_test.v | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vlib/gg/canvas/gpu_clip_test.v b/vlib/gg/canvas/gpu_clip_test.v index 0d1881e2f983ea..d5f2840f5e44dc 100644 --- a/vlib/gg/canvas/gpu_clip_test.v +++ b/vlib/gg/canvas/gpu_clip_test.v @@ -61,16 +61,15 @@ fn test_intersect_rrect_and_rect_combine_into_one_element() { } fn test_save_and_restore_remove_and_revalidate_elements() { - mut ctx := new_gpu_context() or { - eprintln('skipping: ${err}') - return - } - defer { - ctx.free() - } - mut s := ctx.new_surface(width: 64, height: 48) or { panic(err) } - defer { - s.free() + // Saving and restoring the clip only records CPU state. Keep this test + // independent of GPU drivers, which are not instrumented by MemorySanitizer. + mut s := GpuSurface{ + width: 64 + height: 48 + sample_count: 1 + transform: identity_transform() + bounds: new_bounds_manager(64, 48) + clip: new_clip_stack(64, 48) } s.clip_rect(Rect.ltrb(10, 10, 20, 20), .difference, false) or { panic(err) } s.save() @@ -82,6 +81,7 @@ fn test_save_and_restore_remove_and_revalidate_elements() { s.restore() assert s.clip.saves.len == 1 && s.clip.elements.len == 1 assert s.clip.current().oldest_valid == 0 + assert !s.clip.elements[0].is_invalid() // A save without clips is deferred and leaves no save record. s.save() s.restore() From fc3120e1facd2baf04d43a1d4bdbbd377357d501 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Nov 2026 07:45:20 +0300 Subject: [PATCH 3/5] tests: isolate assignment review compiler executables --- .../checker_assignment_review_test.v | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/vlib/v/compiler_tests/checker_assignment_review_test.v b/vlib/v/compiler_tests/checker_assignment_review_test.v index 8ba5a12364bd0c..620af50bf19f60 100644 --- a/vlib/v/compiler_tests/checker_assignment_review_test.v +++ b/vlib/v/compiler_tests/checker_assignment_review_test.v @@ -6,8 +6,11 @@ const checker_assignment_v3_dir = os.dir(checker_assignment_tests_dir) const checker_assignment_vlib_dir = os.dir(checker_assignment_v3_dir) const checker_assignment_v3_src = os.join_path(checker_assignment_v3_dir, 'v.v') -fn checker_assignment_build_v3() string { - v3_bin := os.join_path(os.temp_dir(), 'v3_checker_assignment_review_${os.getpid()}') +fn checker_assignment_build_v3(name string) string { + // Keep each test's compiler separate: Windows can still hold the previous + // executable open when the next test starts building its compiler. + suffix := $if windows { '.exe' } $else { '' } + v3_bin := os.join_path(os.temp_dir(), 'v3_checker_assignment_${name}_${os.getpid()}${suffix}') os.rm(v3_bin) or {} build := os.exec([checker_assignment_vexe, '-gc', 'none', '-path', @@ -44,7 +47,8 @@ fn checker_assignment_run_good(v3_bin string, name string, source string) string } fn test_assignment_invalidates_written_lhs_smartcast() { - v3_bin := checker_assignment_build_v3() + v3_bin := checker_assignment_build_v3(@FN) + defer { os.rm(v3_bin) or {} } checker_assignment_run_bad(v3_bin, 'bad_assignment_stale_smartcast', 'struct Foo { foo int } @@ -67,7 +71,8 @@ fn main() { } fn test_optional_assignment_keeps_wrapper_and_qualified_optional_cast_is_known() { - v3_bin := checker_assignment_build_v3() + v3_bin := checker_assignment_build_v3(@FN) + defer { os.rm(v3_bin) or {} } out := checker_assignment_run_good(v3_bin, 'optional_assignment_and_qualified_cast', 'import time type Values = []int @@ -106,7 +111,8 @@ fn main() { } fn test_enum_alias_cast_contextualizes_short_enum_value() { - v3_bin := checker_assignment_build_v3() + v3_bin := checker_assignment_build_v3(@FN) + defer { os.rm(v3_bin) or {} } out := checker_assignment_run_good(v3_bin, 'enum_alias_short_value_cast', 'enum Number { one = 1 two @@ -124,7 +130,8 @@ fn main() { } fn test_container_stored_capturing_fn_literals() { - v3_bin := checker_assignment_build_v3() + v3_bin := checker_assignment_build_v3(@FN) + defer { os.rm(v3_bin) or {} } alias_out := checker_assignment_run_good(v3_bin, 'append_alias_capturing_fn_literal', 'fn main() { x := 1 f := fn [x] () int { @@ -149,7 +156,8 @@ fn test_container_stored_capturing_fn_literals() { } fn test_nonlocal_assigned_capturing_fn_literals() { - v3_bin := checker_assignment_build_v3() + v3_bin := checker_assignment_build_v3(@FN) + defer { os.rm(v3_bin) or {} } direct_out := checker_assignment_run_good(v3_bin, 'field_assign_direct_capturing_fn_literal', 'struct Holder { mut: cb fn () int @@ -212,7 +220,8 @@ fn main() { // A local cannot be redefined in a nested block, so the same name is bound in // sibling blocks instead: each use must resolve to the binding of its own block. fn test_capturing_fn_literal_marker_uses_nearest_binding() { - v3_bin := checker_assignment_build_v3() + v3_bin := checker_assignment_build_v3(@FN) + defer { os.rm(v3_bin) or {} } out := checker_assignment_run_good(v3_bin, 'capturing_fn_literal_then_plain_fn', 'fn plain() int { return 2 } From 9b75c3db811d704e646e4f22c41811bfa3efd453 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Nov 2026 07:45:22 +0300 Subject: [PATCH 4/5] tests: shorten Windows tool cache fixture paths --- cmd/v/toolcache_test.v | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/v/toolcache_test.v b/cmd/v/toolcache_test.v index 2c89dfece011c5..6636e9c678c44f 100644 --- a/cmd/v/toolcache_test.v +++ b/cmd/v/toolcache_test.v @@ -148,7 +148,12 @@ fn prune_stale_tool_binaries(entry ToolCacheEntry) { } fn toolcache_test_dir(name string) string { - directory := os.join_path(os.vtmp_dir(), 'v_toolcache_test', '${name}_${os.getpid()}') + mut directory := os.join_path(os.vtmp_dir(), 'v_toolcache_test', '${name}_${os.getpid()}') + $if windows { + // The MSVC probe is not longPathAware. Leave room for a complete cache + // key, staging directory, executable name, and application manifest. + directory = os.join_path(os.temp_dir(), 'vtc_${os.getpid()}_${name}') + } os.rmdir_all(directory) or {} os.mkdir_all(directory) or { panic(err) } // Make the test folder private (0700). The cache refuses folders that the group can From 652a1d195412cf345e32c48e379af90f380e286d Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Nov 2026 07:56:22 +0300 Subject: [PATCH 5/5] ci: diagnose Windows regressions on this pull request --- .github/workflows/ci_regressions.yml | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/workflows/ci_regressions.yml diff --git a/.github/workflows/ci_regressions.yml b/.github/workflows/ci_regressions.yml new file mode 100644 index 00000000000000..a6d7bfb4706122 --- /dev/null +++ b/.github/workflows/ci_regressions.yml @@ -0,0 +1,103 @@ +name: Windows CI regressions + +on: + pull_request: + paths: + - '.github/workflows/ci_regressions.yml' + - '.github/actions/setup-v/**' + - '.github/workflows/prepare_v.ps1' + - '.github/workflows/prepare_v.sh' + - '.github/workflows/setup_windows_tool_cache.ps1' + - 'makev.bat' + - 'cmd/v/**' + - 'vlib/v/driver/**' + - 'vlib/v/gen/c/**' + - 'vlib/v/transform/**' + - 'vlib/v/compiler_tests/checker_assignment_review_test.v' + - 'vlib/v/tests/generics/generic_callback_field_payload_test.v' + - 'vlib/net/**' + - 'vlib/sync/**' + - 'thirdparty/mbedtls/**' + workflow_dispatch: + +concurrency: + group: windows-ci-regressions-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + windows-regressions: + # Match the native toolchains used by the full Windows TCC and MSVC lanes. + runs-on: windows-2022 + timeout-minutes: 45 + defaults: + run: + shell: pwsh + env: + VJOBS: '2' + VTEST_FAIL_FAST: '1' + V_MACOS_V3_NO_FALLBACK: '1' + steps: + - uses: actions/checkout@v7 + timeout-minutes: 3 + with: + persist-credentials: false + - name: Prepare private tool cache + timeout-minutes: 2 + run: .\.github\workflows\setup_windows_tool_cache.ps1 + - name: Initialize MSVC tools + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 + timeout-minutes: 3 + with: + arch: x64 + - name: Build the optimized V compiler + id: build + uses: ./.github/actions/setup-v + timeout-minutes: 15 + with: + use-prebuilt: 'false' + symlink: 'false' + - name: Check generic callback payloads with TCC + if: ${{ !cancelled() && steps.build.outcome == 'success' }} + timeout-minutes: 3 + env: + VFLAGS: -cc tcc -no-retry-compilation + run: | + .\v.exe -stats vlib/v/tests/generics/generic_callback_field_payload_test.v + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Check generic callback payloads with MSVC + if: ${{ !cancelled() && steps.build.outcome == 'success' }} + timeout-minutes: 3 + env: + VFLAGS: -cc msvc -no-retry-compilation + run: | + .\v.exe -stats vlib/v/tests/generics/generic_callback_field_payload_test.v + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Check nested checker compiler lifetimes with TCC + if: ${{ !cancelled() && steps.build.outcome == 'success' }} + timeout-minutes: 10 + env: + VFLAGS: -cc tcc -no-retry-compilation + run: | + .\v.exe -stats vlib/v/compiler_tests/checker_assignment_review_test.v + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Check tool cache manifest staging with MSVC + if: ${{ !cancelled() && steps.build.outcome == 'success' }} + timeout-minutes: 8 + env: + VFLAGS: -cc msvc -no-retry-compilation + run: | + .\v.exe -stats cmd/v/toolcache_test.v + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Check HTTP/2 transport with TCC + if: ${{ !cancelled() && steps.build.outcome == 'success' }} + timeout-minutes: 5 + env: + VFLAGS: -cc tcc -no-retry-compilation + # -stats reports completed functions. Trace entries flush to stderr so + # a timeout also identifies the function that has not returned. + run: | + .\v.exe -stats -trace-calls -trace-fns 'test_*' vlib/net/http/transport_h2_test.v + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }