Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/ci_regressions.yml
Original file line number Diff line number Diff line change
@@ -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 }
7 changes: 6 additions & 1 deletion cmd/v/toolcache_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions vlib/gg/canvas/gpu_clip_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand Down
25 changes: 17 additions & 8 deletions vlib/v/compiler_tests/checker_assignment_review_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
26 changes: 26 additions & 0 deletions vlib/v/tests/generics/generic_callback_field_payload_test.v
Original file line number Diff line number Diff line change
@@ -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 }
}
11 changes: 11 additions & 0 deletions vlib/v/transform/monomorphize.v
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading