From 007876c6975a0f06087c53ff4bc2590a3169d613 Mon Sep 17 00:00:00 2001 From: Hyper66666 <2247081184@qq.com> Date: Wed, 10 Jun 2026 18:00:54 +0800 Subject: [PATCH 1/7] Make documented core forms reliable enough to gate releases The core conformance wave now enforces explicit mutability, constructs enum values across native and JIT backends, and runs strict compile-link-execute coverage. The review fixes also align enum payload ABI with async frame round-trips and route impossible match defaults through valid unreachable CFG edges. Constraint: Preserve existing public Sengoo syntax and defer payload-carrying enums across await. Rejected: Keep fixed i64 enum payload slots | typed and multi-field payloads require layout-sized byte storage. Rejected: Send exhaustive match defaults to the phi join | creates an invalid predecessor set and crashes LLVM codegen. Confidence: high Scope-risk: broad Reversibility: clean Directive: Treat enum payload layout, immutable-assignment diagnostics, and strict conformance failures as release contracts. Tested: cargo build --workspace --locked; cargo clippy --workspace --all-targets --locked -- -D warnings; cargo test --workspace --locked; cargo fmt --all -- --check; OpenSpec strict validation; independent review. Not-tested: Linux GitHub Actions pending. --- .github/workflows/core-conformance.yml | 32 + .github/workflows/perf-smoke.yml | 4 +- .github/workflows/realworld-e2e.yml | 8 +- .gitignore | 2 +- Cargo.lock | 3349 +++++++++++++++++ Cargo.toml | 3 +- PROGRESS.md | 13 + README.md | 2 +- README.zh-CN.md | 2 +- compiler/src/ast/stmt.rs | 2 + compiler/src/codegen/common.rs | 79 +- compiler/src/codegen/instruction_helpers.rs | 138 +- .../src/codegen/jit/aggregate_instructions.rs | 54 +- compiler/src/codegen/jit/functions.rs | 64 + compiler/src/codegen/jit/instructions.rs | 132 + compiler/src/codegen/jit/mod.rs | 11 + compiler/src/codegen/jit/terminators.rs | 29 + compiler/src/hir/expr.rs | 7 + compiler/src/hir/item.rs | 3 + compiler/src/hir/lower/expressions.rs | 69 +- compiler/src/mir/async_cfg_helpers.rs | 6 +- compiler/src/mir/async_frame_helpers.rs | 8 +- compiler/src/mir/async_lowering.rs | 2 +- compiler/src/mir/async_poll_helpers.rs | 3 + compiler/src/mir/concrete_type_helpers.rs | 5 + compiler/src/mir/direct_call_helpers.rs | 5 + .../src/mir/hir_specialization_helpers.rs | 14 + .../src/mir/lowering/body_dispatch_methods.rs | 6 + .../src/mir/lowering/enum_expr_helpers.rs | 48 + .../src/mir/lowering/match_expr_helpers.rs | 4 +- compiler/src/mir/lowering/mod.rs | 2 + compiler/src/mir/lowering_helpers.rs | 5 + compiler/src/parser/stmt.rs | 2 + compiler/src/tests/array_assign_tests.rs | 4 +- compiler/src/tests/async_cfg_helpers_tests.rs | 15 + compiler/src/tests/async_tests.rs | 2 +- compiler/src/tests/codegen_stream_tests.rs | 4 +- .../tests/core_language_correctness_tests.rs | 356 ++ compiler/src/tests/for_loop_tests.rs | 4 +- compiler/src/tests/mod.rs | 1 + compiler/src/tests/parser_ambiguity_tests.rs | 2 +- compiler/src/tests/phi_tests.rs | 4 +- compiler/src/tests/property_tests.rs | 32 +- compiler/src/tests/while_loop_tests.rs | 41 +- compiler/src/typeck/borrow.rs | 2 +- compiler/src/typeck/check/call_helpers.rs | 6 + compiler/src/typeck/check/decl_helpers.rs | 17 +- compiler/src/typeck/check/expr_helpers.rs | 176 +- compiler/src/typeck/check/stmt_helpers.rs | 8 +- compiler/src/typeck/env.rs | 15 +- compiler/src/typeck/ty.rs | 27 + docs/language-features.md | 12 +- docs/let-mut-migration.md | 24 + examples/05_loop.sg | 2 +- examples/conformance/01_scalars_control.sg | 19 + examples/conformance/02_recursion.sg | 15 + examples/conformance/03_array_write.sg | 9 + .../conformance/04_closure_multi_capture.sg | 10 + examples/conformance/05_enum_payload.sg | 16 + examples/conformance/06_enum_multi_payload.sg | 16 + examples/ergonomics/03_enum_match.sg | 2 +- .../core-language-correctness/VERIFICATION.md | 81 + .../core-language-correctness/design.md | 15 + .../core-language-correctness/proposal.md | 4 +- .../specs/core-language-correctness/spec.md | 2 +- .../core-language-correctness/tasks.md | 62 +- packages/sggame/examples/snake.sg | 28 +- packages/sggui/examples/counter.sg | 6 +- packages/sggui/tests/hit_test.sg | 6 +- packages/sgplatform/examples/blank_window.sg | 2 +- runtime/src/net/tls.rs | 2 +- rust-toolchain.toml | 4 + .../interface/generic_instances/collector.rs | 4 +- tools/sgc/src/pipeline/ast_pruning.rs | 13 +- tools/sgc/src/pipeline/hir_pruning.rs | 18 +- tools/sgc/src/tests.rs | 263 +- tools/sgfmt/src/expressions.rs | 23 +- tools/sgfmt/src/lib.rs | 7 + tools/sglsp/src/diagnostics.rs | 95 + tools/stdlib/collections.sg | 20 +- tools/stdlib/string.sg | 4 +- 81 files changed, 5348 insertions(+), 265 deletions(-) create mode 100644 .github/workflows/core-conformance.yml create mode 100644 Cargo.lock create mode 100644 compiler/src/mir/lowering/enum_expr_helpers.rs create mode 100644 compiler/src/tests/core_language_correctness_tests.rs create mode 100644 docs/let-mut-migration.md create mode 100644 examples/conformance/01_scalars_control.sg create mode 100644 examples/conformance/02_recursion.sg create mode 100644 examples/conformance/03_array_write.sg create mode 100644 examples/conformance/04_closure_multi_capture.sg create mode 100644 examples/conformance/05_enum_payload.sg create mode 100644 examples/conformance/06_enum_multi_payload.sg create mode 100644 openspec/changes/core-language-correctness/VERIFICATION.md create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/core-conformance.yml b/.github/workflows/core-conformance.yml new file mode 100644 index 0000000..793e8e4 --- /dev/null +++ b/.github/workflows/core-conformance.yml @@ -0,0 +1,32 @@ +name: core-conformance + +on: + pull_request: + push: + branches: + - main + - codex/** + +jobs: + core-language: + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.94.0 + components: rustfmt, clippy + + - name: Install clang + run: sudo apt-get update && sudo apt-get install -y clang + + - name: Validate OpenSpec + run: npx --yes openspec validate core-language-correctness --strict + + - name: Run core conformance examples + run: cargo test --locked -p sgc core_conformance_examples_compile_link_and_run -- --nocapture + + - name: Run workspace tests + run: cargo test --workspace --locked diff --git a/.github/workflows/perf-smoke.yml b/.github/workflows/perf-smoke.yml index 3e983b6..3d77162 100644 --- a/.github/workflows/perf-smoke.yml +++ b/.github/workflows/perf-smoke.yml @@ -15,7 +15,9 @@ jobs: - uses: actions/checkout@v4 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.94.0 - name: Build sgc (release) run: cargo build -p sgc --release diff --git a/.github/workflows/realworld-e2e.yml b/.github/workflows/realworld-e2e.yml index c8e7069..cd6d85a 100644 --- a/.github/workflows/realworld-e2e.yml +++ b/.github/workflows/realworld-e2e.yml @@ -16,7 +16,9 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.94.0 - name: Install LLVM clang (Linux) if: runner.os == 'Linux' @@ -60,7 +62,9 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.94.0 - name: Install LLVM clang (Linux) if: runner.os == 'Linux' diff --git a/.gitignore b/.gitignore index 71b38e8..6d755ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Rust build artifacts /target/ **/target/ -Cargo.lock +# Applications in this workspace use a committed lockfile for reproducible builds. # Editor / IDE .idea/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3687ba2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3349 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli 0.32.3", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a41b85213deedf877555a7878ca9fb680ccba8183611c4bb8030ed281b2ad83" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-bitset" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "690d8ae6c73748e5ce3d8fe59034dceadb8823e6c8994ba324141c5eae909b0e" + +[[package]] +name = "cranelift-codegen" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce027a7b16f8b86f60ff6819615273635186d607a0c225ee6ac340d7d18f978" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli 0.28.1", + "hashbrown 0.14.5", + "log", + "regalloc2", + "rustc-hash", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a2d2ab65e6cbf91f81781d8da65ec2005510f18300eff21a99526ed6785863" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efcff860573cf3db9ae98fbd949240d78b319df686cc306872e7fab60e9c84d7" + +[[package]] +name = "cranelift-control" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d70e5b75c2d5541ef80a99966ccd97aaa54d2a6af19ea31759a28538e1685a" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d21d3089714278920030321829090d9482c91e5ff2339f2f697f8425bffdcba3" +dependencies = [ + "cranelift-bitset", +] + +[[package]] +name = "cranelift-frontend" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7308482930f2a2fad4fe25a06054f6f9a4ee1ab97264308c661b037cb60001a3" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c59e259dab0e6958dabcc536b30845574f027ba6e5000498cdaf7e7ed2d30" + +[[package]] +name = "cranelift-jit" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d4961d70f58f9f2fb89adfb847fe2a08b049f33dc5dd81d01b7e5ac46d40bf" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-module", + "cranelift-native", + "libc", + "log", + "region", + "target-lexicon", + "wasmtime-jit-icache-coherence", + "windows-sys 0.52.0", +] + +[[package]] +name = "cranelift-module" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "215f383d347e0f170d32ce5e8d9eae6336279865a9418853c8946118c54bdb43" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", +] + +[[package]] +name = "cranelift-native" +version = "0.110.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77ac3dfb61ef3159998105116acdfeaec75e4296c43ee2dcc4ea39838c0080e" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dissimilar" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "expect-test" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" +dependencies = [ + "dissimilar", + "once_cell", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "insta" +version = "1.47.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "logos" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "lsp-types" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.0", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "pyo3" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "sengoo-compiler" +version = "0.1.0" +dependencies = [ + "expect-test", + "insta", + "logos", + "miette", + "proptest", + "serde", + "thiserror", +] + +[[package]] +name = "sengoo-runtime" +version = "0.1.0" +dependencies = [ + "native-tls", + "pyo3", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sgc" +version = "0.1.0" +dependencies = [ + "clap", + "cranelift-codegen", + "cranelift-frontend", + "cranelift-jit", + "cranelift-module", + "miette", + "sengoo-compiler", + "sengoo-runtime", + "serde", + "serde_json", + "thiserror", + "tokio", + "toml", + "tracing", + "tracing-subscriber", + "which", +] + +[[package]] +name = "sgfmt" +version = "0.1.0" +dependencies = [ + "clap", + "miette", + "sengoo-compiler", + "serde", + "similar", + "thiserror", + "toml", +] + +[[package]] +name = "sglsp" +version = "0.1.0" +dependencies = [ + "miette", + "sengoo-compiler", + "serde", + "serde_json", + "sgfmt", + "thiserror", + "tokio", + "tower-lsp", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "sgpm" +version = "0.1.0" +dependencies = [ + "clap", + "flate2", + "miette", + "reqwest", + "semver", + "serde", + "serde_json", + "sha2", + "tar", + "thiserror", + "tokio", + "toml", + "walkdir", + "which", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower 0.5.3", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-lsp" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" +dependencies = [ + "async-trait", + "auto_impl", + "bytes", + "dashmap", + "futures", + "httparse", + "lsp-types", + "memchr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tower 0.4.13", + "tower-lsp-macros", + "tracing", +] + +[[package]] +name = "tower-lsp-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "23.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1a826e4ccd0803b2f7463289cad104f40d09d06bc8acf1a614230a47b4d96f" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "web-sys" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.13.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 8c94d41..94fa33b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,10 +30,9 @@ logos = "0.15" chumsky = "1.0" # 代码生成 -inkwell = { version = "0.5", features = ["llvm18-0"] } -llvm-sys = "180" # Python互操作 +# Retained for sengoo-runtime's optional `python` feature. pyo3 = { version = "0.23", features = ["auto-initialize"] } # 序列化 diff --git a/PROGRESS.md b/PROGRESS.md index 1f5dda7..876fbe1 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -1,5 +1,18 @@ # Sengoo 编译器开发进度 +## Verified core conformance (2026-06-10) + +- Fixed-size array read, write, and `for` iteration compile, link, and run. +- Environment-capturing closures compile, including multiple captured locals. +- Locals are immutable by default; reassignment requires `let mut`. +- Enum variants are constructible values, including checked payload variants. +- `cargo test -p sengoo-compiler --lib` passes the full compiler library suite. +- `cargo test -p sgc core_conformance_examples_compile_link_and_run` executes + the pinned scalar/control-flow, recursion, struct/method, array, closure, and + enum examples. +- Native code generation emits textual LLVM IR for `clang`; Cranelift remains + an optional fast path. The workspace does not use `inkwell` or `llvm-sys`. + ## 已完成的特性 ### 1. 数组类型 ✅ diff --git a/README.md b/README.md index 74db93e..35d0362 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Sengoo is a self-developed compiled language focused on practical engineering ou - Python interoperability for gradual migration from existing ecosystems - Fast full and incremental compile loops for day-to-day development -- Native execution through an LLVM backend +- Native execution from textual LLVM IR compiled and linked by `clang`, plus a Cranelift fast path - Optional non-invasive reflection with sidecar metadata Sengoo is still in active development, but the CLI workflow is already usable for real local projects. diff --git a/README.zh-CN.md b/README.zh-CN.md index bab153f..e6952fd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,7 +6,7 @@ Sengoo 是一门自研编译型语言,重点放在工程实践结果上: - 通过 Python 互操作支持从现有生态渐进迁移 - 为日常开发提供快速的全量与增量编译反馈 -- 通过 LLVM 后端提供原生执行路径 +- 通过 `clang` 编译和链接文本 LLVM IR 提供原生执行路径,并提供 Cranelift 快路径 - 通过 sidecar 元数据提供可选的非侵入式反射能力 Sengoo 仍处于积极开发阶段,但 CLI 工作流已经可以用于真实本地项目。 diff --git a/compiler/src/ast/stmt.rs b/compiler/src/ast/stmt.rs index 69c7eeb..971f41c 100644 --- a/compiler/src/ast/stmt.rs +++ b/compiler/src/ast/stmt.rs @@ -21,6 +21,7 @@ impl Stmt { name, ty, value: value.map(Box::new), + is_mut: false, }, span, ) @@ -70,6 +71,7 @@ pub enum StmtKind { name: Ident, ty: Option, value: Option>, + is_mut: bool, }, /// Const 绑定 `const X: Type = expr;` diff --git a/compiler/src/codegen/common.rs b/compiler/src/codegen/common.rs index 67cab31..372cc0b 100644 --- a/compiler/src/codegen/common.rs +++ b/compiler/src/codegen/common.rs @@ -41,8 +41,8 @@ pub fn mir_type_to_llvm_str(ty: &MIRType) -> String { format!("%{}", name) } MIRType::Enum { .. } => { - // Enums are represented as { discriminant, payload } - "{ i64, i64 }".to_string() + let payload_size = enum_payload_storage_size(ty); + format!("{{ i64, [{payload_size} x i8] }}") } MIRType::Future(_) => { // Future is an opaque i64 handle at runtime @@ -51,6 +51,63 @@ pub fn mir_type_to_llvm_str(ty: &MIRType) -> String { } } +fn align_to(value: u64, alignment: u64) -> u64 { + let alignment = alignment.max(1); + value.div_ceil(alignment) * alignment +} + +fn aggregate_layout<'a>(fields: impl IntoIterator) -> (u64, u64) { + let mut size = 0; + let mut alignment = 1; + for field in fields { + let (field_size, field_alignment) = mir_type_size_align(field); + size = align_to(size, field_alignment); + size += field_size; + alignment = alignment.max(field_alignment); + } + (align_to(size, alignment), alignment) +} + +pub fn mir_type_size_align(ty: &MIRType) -> (u64, u64) { + match ty { + MIRType::Unit | MIRType::Never => (0, 1), + MIRType::Bool => (1, 1), + MIRType::Int(bits) | MIRType::Float(bits) => { + let size = (u64::from(*bits).div_ceil(8)).max(1); + (size, size.next_power_of_two().min(8)) + } + MIRType::Ref(_) | MIRType::Ptr(_) | MIRType::Fn { .. } | MIRType::Future(_) => (8, 8), + MIRType::Array(element, len) => { + let (element_size, element_alignment) = mir_type_size_align(element); + ( + align_to(element_size, element_alignment) * *len, + element_alignment, + ) + } + MIRType::Tuple(fields) => aggregate_layout(fields), + MIRType::Struct { fields, .. } => { + aggregate_layout(fields.iter().map(|(_, field_ty)| field_ty)) + } + MIRType::Enum { .. } => { + let payload_size = enum_payload_storage_size(ty); + (align_to(8 + payload_size, 8), 8) + } + } +} + +pub fn enum_payload_storage_size(ty: &MIRType) -> u64 { + let MIRType::Enum { variants, .. } = ty else { + return 1; + }; + variants + .iter() + .filter_map(|(_, payload)| payload.as_ref()) + .map(|payload| mir_type_size_align(payload).0) + .max() + .unwrap_or(0) + .max(1) +} + pub fn mir_type_bit_width(ty: &MIRType) -> Option { match ty { MIRType::Bool => Some(1), @@ -295,7 +352,23 @@ mod tests { discr_type: Box::new(MIRType::Int(64)), variants: vec![(0, None), (1, Some(MIRType::Int(64)))], }; - assert_eq!(mir_type_to_llvm_str(&enum_ty), "{ i64, i64 }"); + assert_eq!(mir_type_to_llvm_str(&enum_ty), "{ i64, [8 x i8] }"); + } + + #[test] + fn test_enum_payload_storage_uses_largest_variant_layout() { + let enum_ty = MIRType::Enum { + discr_type: Box::new(MIRType::Int(64)), + variants: vec![ + (0, None), + ( + 1, + Some(MIRType::Tuple(vec![MIRType::Int(64), MIRType::Bool])), + ), + ], + }; + assert_eq!(enum_payload_storage_size(&enum_ty), 16); + assert_eq!(mir_type_to_llvm_str(&enum_ty), "{ i64, [16 x i8] }"); } #[test] diff --git a/compiler/src/codegen/instruction_helpers.rs b/compiler/src/codegen/instruction_helpers.rs index e906e6f..37d4e60 100644 --- a/compiler/src/codegen/instruction_helpers.rs +++ b/compiler/src/codegen/instruction_helpers.rs @@ -429,29 +429,47 @@ impl Codegen { MIRType::Enum { .. } => { let llvm_ty = self.mir_type_to_llvm_cached(ty); + let payload_size = common::enum_payload_storage_size(ty); + let payload_storage_llvm = format!("[{payload_size} x i8]"); let discr_local = fields.first().copied().ok_or_else(|| { "enum aggregate missing discriminant field".to_string() })?; let discr_val = self.operand_value(discr_local, mir_fn); - let discr_temp = format!("{}.discr", dest); + let slot = format!("{dest}.slot"); + let discr_ptr = format!("{dest}.discr.ptr"); self.emit_indent(); - self.ir.push_str(&format!( - "{} = insertvalue {} undef, i64 {}, 0\n", - discr_temp, llvm_ty, discr_val - )); - - let payload_val = if let Some(payload_local) = fields.get(1).copied() { - self.operand_value(payload_local, mir_fn) - } else { - "0".to_string() - }; - + self.ir.push_str(&format!("{slot} = alloca {llvm_ty}\n")); self.emit_indent(); self.ir.push_str(&format!( - "{} = insertvalue {} {}, i64 {}, 1\n", - dest, llvm_ty, discr_temp, payload_val + "{discr_ptr} = getelementptr {llvm_ty}, {llvm_ty}* {slot}, i32 0, i32 0\n" )); + self.emit_indent(); + self.ir + .push_str(&format!("store i64 {discr_val}, i64* {discr_ptr}\n")); + + if let Some(payload_local) = fields.get(1).copied() { + let payload_ty = self.get_local_type(mir_fn, payload_local).clone(); + let payload_llvm = self.mir_type_to_llvm_cached(&payload_ty); + let payload_val = self.operand_value(payload_local, mir_fn); + let payload_bytes = format!("{dest}.payload.bytes"); + let payload_ptr = format!("{dest}.payload.ptr"); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_bytes} = getelementptr {llvm_ty}, {llvm_ty}* {slot}, i32 0, i32 1\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_ptr} = bitcast {payload_storage_llvm}* {payload_bytes} to {payload_llvm}*\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "store {payload_llvm} {payload_val}, {payload_llvm}* {payload_ptr}\n" + )); + } + self.emit_indent(); + self.ir + .push_str(&format!("{dest} = load {llvm_ty}, {llvm_ty}* {slot}\n")); } _ => { @@ -579,16 +597,13 @@ impl Codegen { source, } => { - // Construct an enum as `{ discr, payload }`, leaving payload undef when absent. let dest = self.local_name(*destination); - - let src = self.local_name(*source); - - // ExtractValue reads a field from an aggregate source value. - + let source_ty = self.get_local_type(mir_fn, *source).clone(); + let source_llvm = self.mir_type_to_llvm_cached(&source_ty); + let src = self.operand_value(*source, mir_fn); self.ir.push_str(&format!( - "{} = extractvalue {{ i64, i64 }} {}, 0\n", - dest, src + "{} = extractvalue {} {}, 0\n", + dest, source_llvm, src )); } @@ -599,40 +614,46 @@ impl Codegen { payload, - enum_type: _, + enum_type, } => { - // Enum construction is represented as an LLVM aggregate literal. let dest = self.local_name(*destination); - - // Materialize the discriminant first. - let discr_value = format!("{}.discr", dest); - + let enum_llvm = self.mir_type_to_llvm_cached(enum_type); + let payload_size = common::enum_payload_storage_size(enum_type); + let payload_storage_llvm = format!("[{payload_size} x i8]"); + let slot = format!("{dest}.slot"); + let discr_ptr = format!("{dest}.discr.ptr"); + self.emit_indent(); + self.ir.push_str(&format!("{slot} = alloca {enum_llvm}\n")); self.emit_indent(); - self.ir.push_str(&format!( - "{} = insertvalue {{ i64, i64 }} undef, i64 {}, 0\n", - discr_value, discriminant + "{discr_ptr} = getelementptr {enum_llvm}, {enum_llvm}* {slot}, i32 0, i32 0\n" )); + self.emit_indent(); + self.ir + .push_str(&format!("store i64 {discriminant}, i64* {discr_ptr}\n")); - // Fill payload slot 1 when the enum variant carries data. if let Some(payload_local) = payload { + let payload_ty = self.get_local_type(mir_fn, *payload_local).clone(); + let payload_llvm = self.mir_type_to_llvm_cached(&payload_ty); let payload_val = self.operand_value(*payload_local, mir_fn); - + let payload_bytes = format!("{dest}.payload.bytes"); + let payload_ptr = format!("{dest}.payload.ptr"); self.emit_indent(); - self.ir.push_str(&format!( - "{} = insertvalue {{ i64, i64 }} {}, i64 {}, 1\n", - dest, discr_value, payload_val + "{payload_bytes} = getelementptr {enum_llvm}, {enum_llvm}* {slot}, i32 0, i32 1\n" )); - } else { - // Keep payload slot 1 as undef for payloadless variants. self.emit_indent(); - self.ir.push_str(&format!( - "{} = insertvalue {{ i64, i64 }} {}, i64 undef, 1\n", - dest, discr_value + "{payload_ptr} = bitcast {payload_storage_llvm}* {payload_bytes} to {payload_llvm}*\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "store {payload_llvm} {payload_val}, {payload_llvm}* {payload_ptr}\n" )); } + self.emit_indent(); + self.ir + .push_str(&format!("{dest} = load {enum_llvm}, {enum_llvm}* {slot}\n")); } mir::Instruction::ExtractPayload { @@ -640,16 +661,35 @@ impl Codegen { source, } => { - // Bitcast reinterprets the source bits as the destination type. let dest = self.local_name(*destination); - - let src = self.local_name(*source); - - // The cast emits one conversion instruction into the destination local. - + let source_ty = self.get_local_type(mir_fn, *source).clone(); + let source_llvm = self.mir_type_to_llvm_cached(&source_ty); + let payload_size = common::enum_payload_storage_size(&source_ty); + let payload_storage_llvm = format!("[{payload_size} x i8]"); + let destination_ty = self.get_local_type(mir_fn, *destination).clone(); + let destination_llvm = self.mir_type_to_llvm_cached(&destination_ty); + let src = self.operand_value(*source, mir_fn); + let slot = format!("{dest}.enum.slot"); + let payload_bytes = format!("{dest}.payload.bytes"); + let payload_ptr = format!("{dest}.payload.ptr"); + self.emit_indent(); + self.ir + .push_str(&format!("{slot} = alloca {source_llvm}\n")); + self.emit_indent(); + self.ir.push_str(&format!( + "store {source_llvm} {src}, {source_llvm}* {slot}\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_bytes} = getelementptr {source_llvm}, {source_llvm}* {slot}, i32 0, i32 1\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_ptr} = bitcast {payload_storage_llvm}* {payload_bytes} to {destination_llvm}*\n" + )); + self.emit_indent(); self.ir.push_str(&format!( - "{} = extractvalue {{ i64, i64 }} {}, 1\n", - dest, src + "{dest} = load {destination_llvm}, {destination_llvm}* {payload_ptr}\n" )); } diff --git a/compiler/src/codegen/jit/aggregate_instructions.rs b/compiler/src/codegen/jit/aggregate_instructions.rs index d9cbdeb..ba5afea 100644 --- a/compiler/src/codegen/jit/aggregate_instructions.rs +++ b/compiler/src/codegen/jit/aggregate_instructions.rs @@ -111,19 +111,6 @@ impl JITCodegen { discr_loaded, discr_reg )); - let payload_loaded = if let Some(payload_local) = fields.get(1) { - let payload_reg = self.local_reg(*payload_local); - let payload_loaded = format!("%.enum.payload.{}", destination.id); - self.emit_indent(); - self.ir.push_str(&format!( - "{} = load i64, i64* {}\n", - payload_loaded, payload_reg - )); - payload_loaded - } else { - "0".to_string() - }; - self.emit_indent(); self.ir .push_str(&format!("{} = alloca {}\n", dest, llvm_enum_ty)); @@ -138,17 +125,36 @@ impl JITCodegen { self.ir .push_str(&format!("store i64 {}, i64* {}\n", discr_loaded, discr_ptr)); - let payload_ptr = format!("%.ptr.{}.1", destination.id); - self.emit_indent(); - self.ir.push_str(&format!( - "{} = getelementptr {}, {}* {}, i32 0, i32 1\n", - payload_ptr, llvm_enum_ty, llvm_enum_ty, dest - )); - self.emit_indent(); - self.ir.push_str(&format!( - "store i64 {}, i64* {}\n", - payload_loaded, payload_ptr - )); + if let Some(payload_local) = fields.get(1) { + let payload_ty = self.get_local_type(mir_fn, *payload_local); + let llvm_payload_ty = self.mir_type_to_llvm_str(&payload_ty); + let payload_reg = self.local_reg(*payload_local); + let payload_loaded = format!("%.enum.payload.{}", destination.id); + self.emit_indent(); + self.ir.push_str(&format!( + "{} = load {}, {}* {}\n", + payload_loaded, llvm_payload_ty, llvm_payload_ty, payload_reg + )); + + let payload_bytes = format!("%.ptr.{}.1", destination.id); + self.emit_indent(); + self.ir.push_str(&format!( + "{} = getelementptr {}, {}* {}, i32 0, i32 1\n", + payload_bytes, llvm_enum_ty, llvm_enum_ty, dest + )); + let payload_ptr = format!("%.ptr.{}.1.typed", destination.id); + let payload_size = crate::codegen::common::enum_payload_storage_size(ty); + self.emit_indent(); + self.ir.push_str(&format!( + "{} = bitcast [{} x i8]* {} to {}*\n", + payload_ptr, payload_size, payload_bytes, llvm_payload_ty + )); + self.emit_indent(); + self.ir.push_str(&format!( + "store {} {}, {}* {}\n", + llvm_payload_ty, payload_loaded, llvm_payload_ty, payload_ptr + )); + } } _ => { // 鍏朵粬鑱氬悎绫诲瀷锛堢粨鏋勪綋绛夛級 diff --git a/compiler/src/codegen/jit/functions.rs b/compiler/src/codegen/jit/functions.rs index 3f68abf..1f78f4a 100644 --- a/compiler/src/codegen/jit/functions.rs +++ b/compiler/src/codegen/jit/functions.rs @@ -4,6 +4,7 @@ use crate::mir::{self, MirFunction}; impl JITCodegen { /// 鐢熸垚鍑芥暟瀹氫箟 pub(super) fn codegen_function(&mut self, mir_fn: &MirFunction) -> Result<(), String> { + self.prepare_phi_incoming_loads(mir_fn); self.ir.push_str(&format!("; Function: {}\n", mir_fn.name)); // main 鍑芥暟杩斿洖 i32锛屽叾浠栧嚱鏁颁娇鐢ㄥ疄闄呰繑鍥炵被鍨? @@ -85,6 +86,8 @@ impl JITCodegen { self.codegen_instruction(inst, mir_fn)?; } + self.emit_phi_incoming_loads_for_block(bb.id); + // 生成终止符 if let Some(terminator) = &bb.terminator { self.codegen_terminator(terminator, mir_fn)?; @@ -94,6 +97,67 @@ impl JITCodegen { Ok(()) } + fn prepare_phi_incoming_loads(&mut self, mir_fn: &MirFunction) { + self.phi_incoming_loads_by_block.clear(); + self.phi_incoming_values.clear(); + + for block in &mir_fn.basic_blocks { + for inst_id in &block.instructions { + let mir::Instruction::Phi { + destination, + incoming, + } = mir_fn.instruction(*inst_id) + else { + continue; + }; + + for (local, predecessor) in incoming { + let Some((_, ty)) = mir_fn.locals.get(local.index()) else { + continue; + }; + let name = format!( + "%.phi.load.{}.{}.{}", + destination.index(), + predecessor, + local.index() + ); + self.phi_incoming_values.insert( + (destination.index(), *predecessor, local.index()), + name.clone(), + ); + self.phi_incoming_loads_by_block + .entry(*predecessor) + .or_default() + .push(super::PhiIncomingLoad { + name, + local: *local, + ty: ty.clone(), + }); + } + } + } + } + + fn emit_phi_incoming_loads_for_block(&mut self, block_id: usize) { + let loads = self + .phi_incoming_loads_by_block + .get(&block_id) + .cloned() + .unwrap_or_default(); + + for load in loads { + let llvm_ty = self.mir_type_to_llvm_str(&load.ty); + self.emit_indent(); + self.ir.push_str(&format!( + "{} = load {}, {}* {}\n", + load.name, + llvm_ty, + llvm_ty, + self.local_reg(load.local) + )); + } + } + /// 发射 main 包装器 pub(super) fn emit_main_wrapper(&mut self) { self.ir.push_str("; Main wrapper\n"); diff --git a/compiler/src/codegen/jit/instructions.rs b/compiler/src/codegen/jit/instructions.rs index 5a0f52b..149efcf 100644 --- a/compiler/src/codegen/jit/instructions.rs +++ b/compiler/src/codegen/jit/instructions.rs @@ -487,6 +487,138 @@ impl JITCodegen { } => { self.codegen_aggregate_instruction(*destination, fields, ty, mir_fn)?; } + mir::Instruction::Discriminant { + destination, + source, + } => { + let source_ty = self.get_local_type(mir_fn, *source); + let source_llvm = self.mir_type_to_llvm_str(&source_ty); + let discr_ptr = format!("%.enum.discr.ptr.{}", destination.id); + let discr_value = format!("%.enum.discr.{}", destination.id); + self.emit_indent(); + self.ir.push_str(&format!( + "{discr_ptr} = getelementptr {source_llvm}, {source_llvm}* {}, i32 0, i32 0\n", + self.local_reg(*source) + )); + self.emit_indent(); + self.ir + .push_str(&format!("{discr_value} = load i64, i64* {discr_ptr}\n")); + self.emit_indent(); + self.ir.push_str(&format!( + "store i64 {discr_value}, i64* {}\n", + self.local_reg(*destination) + )); + } + mir::Instruction::EnumConstruct { + destination, + discriminant, + payload, + enum_type, + } => { + let enum_llvm = self.mir_type_to_llvm_str(enum_type); + let discr_ptr = format!("%.enum.discr.ptr.{}", destination.id); + self.emit_indent(); + self.ir.push_str(&format!( + "{discr_ptr} = getelementptr {enum_llvm}, {enum_llvm}* {}, i32 0, i32 0\n", + self.local_reg(*destination) + )); + self.emit_indent(); + self.ir + .push_str(&format!("store i64 {discriminant}, i64* {discr_ptr}\n")); + + if let Some(payload_local) = payload { + let payload_ty = self.get_local_type(mir_fn, *payload_local); + let payload_llvm = self.mir_type_to_llvm_str(&payload_ty); + let payload_value = format!("%.enum.payload.{}", destination.id); + let payload_bytes = format!("%.enum.payload.bytes.{}", destination.id); + let payload_ptr = format!("%.enum.payload.ptr.{}", destination.id); + let payload_size = common::enum_payload_storage_size(enum_type); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_value} = load {payload_llvm}, {payload_llvm}* {}\n", + self.local_reg(*payload_local) + )); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_bytes} = getelementptr {enum_llvm}, {enum_llvm}* {}, i32 0, i32 1\n", + self.local_reg(*destination) + )); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_ptr} = bitcast [{payload_size} x i8]* {payload_bytes} to {payload_llvm}*\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "store {payload_llvm} {payload_value}, {payload_llvm}* {payload_ptr}\n" + )); + } + } + mir::Instruction::ExtractPayload { + destination, + source, + } => { + let source_ty = self.get_local_type(mir_fn, *source); + let source_llvm = self.mir_type_to_llvm_str(&source_ty); + let destination_ty = self.get_local_type(mir_fn, *destination); + let destination_llvm = self.mir_type_to_llvm_str(&destination_ty); + let payload_size = common::enum_payload_storage_size(&source_ty); + let payload_bytes = format!("%.enum.payload.bytes.{}", destination.id); + let payload_ptr = format!("%.enum.payload.ptr.{}", destination.id); + let payload_value = format!("%.enum.payload.{}", destination.id); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_bytes} = getelementptr {source_llvm}, {source_llvm}* {}, i32 0, i32 1\n", + self.local_reg(*source) + )); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_ptr} = bitcast [{payload_size} x i8]* {payload_bytes} to {destination_llvm}*\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "{payload_value} = load {destination_llvm}, {destination_llvm}* {payload_ptr}\n" + )); + self.emit_indent(); + self.ir.push_str(&format!( + "store {destination_llvm} {payload_value}, {destination_llvm}* {}\n", + self.local_reg(*destination) + )); + } + mir::Instruction::Phi { + destination, + incoming, + } => { + let ty = self.get_local_type(mir_fn, *destination); + if matches!(ty, MIRType::Unit | MIRType::Never) + || matches!(&ty, MIRType::Tuple(fields) if fields.is_empty()) + { + return Ok(()); + } + + let llvm_ty = self.mir_type_to_llvm_str(&ty); + let phi_value = format!("%.phi.{}", destination.id); + let entries = incoming + .iter() + .map(|(local, block)| { + let value = self + .phi_incoming_values + .get(&(destination.index(), *block, local.index())) + .cloned() + .unwrap_or_else(|| self.local_reg(*local)); + format!("[ {value}, %bb_{block} ]") + }) + .collect::>(); + self.emit_indent(); + self.ir.push_str(&format!( + "{phi_value} = phi {llvm_ty} {}\n", + entries.join(", ") + )); + self.emit_indent(); + self.ir.push_str(&format!( + "store {llvm_ty} {phi_value}, {llvm_ty}* {}\n", + self.local_reg(*destination) + )); + } _ => { self.emit_indent(); self.ir diff --git a/compiler/src/codegen/jit/mod.rs b/compiler/src/codegen/jit/mod.rs index 2a62d98..d707f5b 100644 --- a/compiler/src/codegen/jit/mod.rs +++ b/compiler/src/codegen/jit/mod.rs @@ -6,6 +6,13 @@ use super::common; use crate::mir::{MIRType, MirFunction}; use std::collections::HashMap; +#[derive(Debug, Clone)] +struct PhiIncomingLoad { + name: String, + local: crate::mir::Local, + ty: MIRType, +} + mod aggregate_instructions; mod casts; mod declaration_helpers; @@ -33,6 +40,8 @@ pub struct JITCodegen { /// 临时变量计数器(用于生成唯一名称) /// 所有函数的签名(用于类型转换) function_signatures: HashMap, MIRType)>, + phi_incoming_loads_by_block: HashMap>, + phi_incoming_values: HashMap<(usize, usize, usize), String>, } impl JITCodegen { @@ -46,6 +55,8 @@ impl JITCodegen { string_counter: 0, current_block_id: 0, function_signatures: HashMap::new(), + phi_incoming_loads_by_block: HashMap::new(), + phi_incoming_values: HashMap::new(), }; cg.emit_header(); cg diff --git a/compiler/src/codegen/jit/terminators.rs b/compiler/src/codegen/jit/terminators.rs index e27dff2..e2a2497 100644 --- a/compiler/src/codegen/jit/terminators.rs +++ b/compiler/src/codegen/jit/terminators.rs @@ -106,6 +106,32 @@ impl JITCodegen { else_block )); } + mir::Terminator::Switch { + discr, + targets, + otherwise, + } => { + let discr_ty = self.get_local_type(mir_fn, *discr); + let discr_llvm = self.mir_type_to_llvm_str(&discr_ty); + let discr_value = format!("{}.switch", self.local_name(*discr)); + self.ir.push_str(&format!( + "{discr_value} = load {discr_llvm}, {discr_llvm}* {}\n", + self.local_reg(*discr) + )); + self.emit_indent(); + self.ir.push_str(&format!( + "switch {discr_llvm} {discr_value}, label %bb_{otherwise} [" + )); + for (value, target) in targets { + self.ir.push('\n'); + self.emit_indent(); + self.ir + .push_str(&format!(" {discr_llvm} {value}, label %bb_{target}")); + } + self.ir.push('\n'); + self.emit_indent(); + self.ir.push_str("]\n"); + } mir::Terminator::Break { target } => { // break 璺宠浆鍒扮洰鏍囧潡 self.ir.push_str(&format!("br label %bb_{}\n", target)); @@ -114,6 +140,9 @@ impl JITCodegen { // continue 璺宠浆鍒扮洰鏍囧潡 self.ir.push_str(&format!("br label %bb_{}\n", target)); } + mir::Terminator::Unreachable => { + self.ir.push_str("unreachable\n"); + } _ => { self.ir .push_str(&format!("; unhandled terminator: {:?}\n", terminator)); diff --git a/compiler/src/hir/expr.rs b/compiler/src/hir/expr.rs index 64de9a4..b52bd20 100644 --- a/compiler/src/hir/expr.rs +++ b/compiler/src/hir/expr.rs @@ -65,6 +65,13 @@ pub enum HIRExpr { site_lo: Option, }, + EnumConstruct { + enum_name: String, + variant_name: String, + discriminant: u32, + args: Vec, + }, + /// 方法调用 MethodCall { receiver: Box, diff --git a/compiler/src/hir/item.rs b/compiler/src/hir/item.rs index d4d6e6b..b8e0a74 100644 --- a/compiler/src/hir/item.rs +++ b/compiler/src/hir/item.rs @@ -19,6 +19,9 @@ pub struct HIRTypeParamBound { /// HIR 项 #[derive(Debug, Clone)] +// Boxing HIRFunction would ripple through the compiler ownership model; the +// top-level item list is short-lived and benefits from the current direct API. +#[allow(clippy::large_enum_variant)] pub enum HIRItem { Function(HIRFunction), ExternBlock(HIRExternBlock), diff --git a/compiler/src/hir/lower/expressions.rs b/compiler/src/hir/lower/expressions.rs index c6630f2..3b802bc 100644 --- a/compiler/src/hir/lower/expressions.rs +++ b/compiler/src/hir/lower/expressions.rs @@ -35,7 +35,10 @@ pub(super) fn lower_body(block: &ast::Block, type_env: &TypeEnv) -> HIRBody { for stmt in stmts_to_process { match &stmt.kind { ast::StmtKind::Let { - name, ty, value, .. + name, + ty, + value, + is_mut, } => { // 如果有显式类型注解,使用它;否则从值表达式推断 let hir_ty = if let Some(type_annotation) = ty { @@ -53,7 +56,7 @@ pub(super) fn lower_body(block: &ast::Block, type_env: &TypeEnv) -> HIRBody { symbol: name.symbol, ty: hir_ty, value: hir_value, - is_mut: false, + is_mut: *is_mut, }); } ast::StmtKind::Const { name, ty, value } => { @@ -99,7 +102,14 @@ pub(super) fn lower_expr(expr: &ast::Expr, type_env: &TypeEnv) -> Result { - if let Some(ident) = path.as_simple() { + if let Some((enum_name, variant_name, discriminant)) = enum_constructor(path) { + HIRExpr::EnumConstruct { + enum_name, + variant_name, + discriminant, + args: Vec::new(), + } + } else if let Some(ident) = path.as_simple() { HIRExpr::Var { name: ident.name.clone(), symbol: ident.symbol, @@ -188,14 +198,39 @@ pub(super) fn lower_expr(expr: &ast::Expr, type_env: &TypeEnv) -> Result HIRExpr::Call { - func: Box::new(lower_expr(func, type_env)?), - args: args - .iter() - .filter_map(|a| lower_expr(a, type_env).ok()) - .collect(), - site_lo: Some(expr.span.lo), - }, + ast::ExprKind::Call { func, args } => { + if let ast::ExprKind::Path(path) = &func.kind { + if let Some((enum_name, variant_name, discriminant)) = enum_constructor(path) { + HIRExpr::EnumConstruct { + enum_name, + variant_name, + discriminant, + args: args + .iter() + .filter_map(|arg| lower_expr(arg, type_env).ok()) + .collect(), + } + } else { + HIRExpr::Call { + func: Box::new(lower_expr(func, type_env)?), + args: args + .iter() + .filter_map(|arg| lower_expr(arg, type_env).ok()) + .collect(), + site_lo: Some(expr.span.lo), + } + } + } else { + HIRExpr::Call { + func: Box::new(lower_expr(func, type_env)?), + args: args + .iter() + .filter_map(|arg| lower_expr(arg, type_env).ok()) + .collect(), + site_lo: Some(expr.span.lo), + } + } + } ast::ExprKind::MethodCall { receiver, method, @@ -387,7 +422,7 @@ fn lower_assign_op(op: &ast::AssignOp) -> HIRBinaryOp { fn scrutinee_enum_name(scrutinee: &ast::Expr, type_env: &TypeEnv) -> Option { match &scrutinee.kind { ast::ExprKind::Ident(ident) => type_env.lookup(&ident.name).and_then(|symbol| { - if let SymbolKind::Var(ty) = &symbol.kind { + if let SymbolKind::Var { ty, .. } = &symbol.kind { if let TyKind::Adt { name, .. } = &ty.kind { return Some(name.clone()); } @@ -426,6 +461,16 @@ fn enum_variant_pattern( }) } +fn enum_constructor(path: &ast::Path) -> Option<(String, String, u32)> { + if path.segments.len() != 2 { + return None; + } + let enum_name = path.segments[0].name.clone(); + let variant_name = path.segments[1].name.clone(); + let discriminant = enum_index::variant_discriminant(&enum_name, &variant_name)?; + Some((enum_name, variant_name, discriminant)) +} + /// 降低模式 fn lower_pattern( pat: &ast::pattern::Pattern, diff --git a/compiler/src/mir/async_cfg_helpers.rs b/compiler/src/mir/async_cfg_helpers.rs index 6d7bf2d..368d8e3 100644 --- a/compiler/src/mir/async_cfg_helpers.rs +++ b/compiler/src/mir/async_cfg_helpers.rs @@ -110,7 +110,7 @@ pub(crate) fn compute_live_in_user_locals( } live } - Terminator::Return(_) => HashSet::new(), + Terminator::Return(_) | Terminator::Unreachable => HashSet::new(), other => { return Err(CompileError::MirLower(format!( "unsupported terminator in async liveness: {:?}", @@ -284,10 +284,10 @@ pub(crate) fn build_async_cfg_plan( suspend_points, )?; } - Terminator::Return(_) => {} + Terminator::Return(_) | Terminator::Unreachable => {} other => { return Err(AsyncCfgPlanError::new(format!( - "block {} uses unsupported `{}` terminator; async frame lowering currently expects await control flow built from suspend, goto, if, switch, and return edges", + "block {} uses unsupported `{}` terminator; async frame lowering currently expects await control flow built from suspend, goto, if, switch, return, and unreachable edges", block, async_cfg_terminator_name(&other) ))) diff --git a/compiler/src/mir/async_frame_helpers.rs b/compiler/src/mir/async_frame_helpers.rs index 5b90442..86988b3 100644 --- a/compiler/src/mir/async_frame_helpers.rs +++ b/compiler/src/mir/async_frame_helpers.rs @@ -478,13 +478,7 @@ pub(crate) fn push_frame_load_typed( match &storage_ty { MIRType::Enum { .. } if enum_is_payloadless(&storage_ty) => { let discr = push_frame_load(f, block, handle, offset, MIR_I64); - let zero_payload = push_i64_const(f, block, 0); - Ok(push_aggregate_value( - f, - block, - storage_ty, - vec![discr, zero_payload], - )) + Ok(push_aggregate_value(f, block, storage_ty, vec![discr])) } MIRType::Enum { .. } => Err(unsupported_async_frame_type( &storage_ty, diff --git a/compiler/src/mir/async_lowering.rs b/compiler/src/mir/async_lowering.rs index 1865f6f..1265521 100644 --- a/compiler/src/mir/async_lowering.rs +++ b/compiler/src/mir/async_lowering.rs @@ -344,7 +344,7 @@ fn synthesize_poll( Err(reason) => { let _ = (bb0, state, result_storage_ty, n_states); Err(CompileError::Codegen(format!( - "async frame lowering requires await control flow that can be expressed with suspend points, self-looping pending blocks, and goto/if/switch/return edges; {}", + "async frame lowering requires await control flow that can be expressed with suspend points, self-looping pending blocks, and goto/if/switch/return/unreachable edges; {}", reason.describe() ))) } diff --git a/compiler/src/mir/async_poll_helpers.rs b/compiler/src/mir/async_poll_helpers.rs index 7c390b7..443b31e 100644 --- a/compiler/src/mir/async_poll_helpers.rs +++ b/compiler/src/mir/async_poll_helpers.rs @@ -613,6 +613,9 @@ pub(crate) fn synthesize_cfg_poll( &rebase_pointer_locals, )?; } + Terminator::Unreachable => { + f.basic_blocks[translated].set_terminator(Terminator::Unreachable); + } other => { return Err(CompileError::MirLower(format!( "unsupported terminator in async poll plan: {:?}", diff --git a/compiler/src/mir/concrete_type_helpers.rs b/compiler/src/mir/concrete_type_helpers.rs index bedf597..009b52b 100644 --- a/compiler/src/mir/concrete_type_helpers.rs +++ b/compiler/src/mir/concrete_type_helpers.rs @@ -105,6 +105,11 @@ pub(crate) fn collect_concrete_named_types_from_expr( collect_concrete_named_types_from_expr(arg, known_named_types, out); } } + HIRExpr::EnumConstruct { args, .. } => { + for arg in args { + collect_concrete_named_types_from_expr(arg, known_named_types, out); + } + } HIRExpr::MethodCall { receiver, args, .. } => { collect_concrete_named_types_from_expr(receiver, known_named_types, out); for arg in args { diff --git a/compiler/src/mir/direct_call_helpers.rs b/compiler/src/mir/direct_call_helpers.rs index a121e31..eea2b5d 100644 --- a/compiler/src/mir/direct_call_helpers.rs +++ b/compiler/src/mir/direct_call_helpers.rs @@ -66,6 +66,11 @@ pub(crate) fn collect_direct_calls_in_expr(expr: &HIRExpr, out: &mut HashSet { + for arg in args { + collect_direct_calls_in_expr(arg, out); + } + } HIRExpr::MethodCall { receiver, args, .. } => { collect_direct_calls_in_expr(receiver, out); for arg in args { diff --git a/compiler/src/mir/hir_specialization_helpers.rs b/compiler/src/mir/hir_specialization_helpers.rs index 2099b6c..c75b54b 100644 --- a/compiler/src/mir/hir_specialization_helpers.rs +++ b/compiler/src/mir/hir_specialization_helpers.rs @@ -160,6 +160,20 @@ pub(crate) fn substitute_hir_expr(expr: &HIRExpr, subst: &HashMap HIRExpr::EnumConstruct { + enum_name: enum_name.clone(), + variant_name: variant_name.clone(), + discriminant: *discriminant, + args: args + .iter() + .map(|arg| substitute_hir_expr(arg, subst)) + .collect(), + }, HIRExpr::MethodCall { receiver, method, diff --git a/compiler/src/mir/lowering/body_dispatch_methods.rs b/compiler/src/mir/lowering/body_dispatch_methods.rs index 56c75ca..c5d9ffe 100644 --- a/compiler/src/mir/lowering/body_dispatch_methods.rs +++ b/compiler/src/mir/lowering/body_dispatch_methods.rs @@ -118,6 +118,12 @@ impl<'a> LoweringContext<'a> { args, site_lo, } => lower_call_expr(self, func, args, *site_lo), + HIRExpr::EnumConstruct { + enum_name, + variant_name, + discriminant, + args, + } => lower_enum_construct_expr(self, enum_name, variant_name, *discriminant, args), HIRExpr::And(left, right) => lower_logical_and_expr(self, left, right), HIRExpr::Or(left, right) => lower_logical_or_expr(self, left, right), HIRExpr::Break(value) => lower_break_expr(self, value.as_deref()), diff --git a/compiler/src/mir/lowering/enum_expr_helpers.rs b/compiler/src/mir/lowering/enum_expr_helpers.rs new file mode 100644 index 0000000..ca4258b --- /dev/null +++ b/compiler/src/mir/lowering/enum_expr_helpers.rs @@ -0,0 +1,48 @@ +use super::*; + +pub(super) fn lower_enum_construct_expr( + ctx: &mut LoweringContext<'_>, + enum_name: &str, + variant_name: &str, + discriminant: u32, + args: &[HIRExpr], +) -> Local { + let Some(enum_def) = ctx.options.enum_defs.get(enum_name).cloned() else { + ctx.errors + .push(format!("undefined enum during MIR lowering: `{enum_name}`")); + return ctx.add_local(None, LocalKind::Temp, MIR_UNIT); + }; + let payload_ty = enum_def + .variants + .iter() + .find(|(_, name, _)| name == variant_name) + .and_then(|(_, _, payload)| payload.clone()); + + let lowered_args = args + .iter() + .map(|arg| ctx.lower_expr(arg)) + .collect::>(); + let payload = match (payload_ty, lowered_args.as_slice()) { + (None, _) | (_, []) => None, + (Some(_), [only]) => Some(*only), + (Some(ty), fields) => { + let aggregate = ctx.add_local(None, LocalKind::Temp, ty.clone()); + ctx.push_inst(Instruction::Aggregate { + destination: aggregate, + fields: fields.to_vec(), + ty, + }); + Some(aggregate) + } + }; + + let enum_type = enum_def.mir_type(); + let destination = ctx.add_local(None, LocalKind::Temp, enum_type.clone()); + ctx.push_inst(Instruction::EnumConstruct { + destination, + discriminant, + payload, + enum_type, + }); + destination +} diff --git a/compiler/src/mir/lowering/match_expr_helpers.rs b/compiler/src/mir/lowering/match_expr_helpers.rs index b3dab7f..2fbb1ed 100644 --- a/compiler/src/mir/lowering/match_expr_helpers.rs +++ b/compiler/src/mir/lowering/match_expr_helpers.rs @@ -69,14 +69,16 @@ pub(super) fn lower_enum_match_expr( let arm_blocks: Vec = arms.iter().map(|_| ctx.new_block()).collect(); let join_block = ctx.new_block(); + let unreachable_block = ctx.new_block(); - let switch_plan = build_match_switch_plan(arms, &arm_blocks, join_block); + let switch_plan = build_match_switch_plan(arms, &arm_blocks, unreachable_block); ctx.set_terminator(Terminator::Switch { discr: discr_local, targets: switch_plan.targets, otherwise: switch_plan.otherwise_block, }); + ctx.mir_fn.basic_blocks[unreachable_block].set_terminator(Terminator::Unreachable); let mut incoming_values: Vec<(Local, usize)> = Vec::new(); for (i, arm) in arms.iter().enumerate() { diff --git a/compiler/src/mir/lowering/mod.rs b/compiler/src/mir/lowering/mod.rs index 056d1d6..e88e702 100644 --- a/compiler/src/mir/lowering/mod.rs +++ b/compiler/src/mir/lowering/mod.rs @@ -61,6 +61,7 @@ mod call_target_helpers; mod context_methods; mod contract_methods; mod entry; +mod enum_expr_helpers; mod for_expr_helpers; mod function_lowering; mod if_expr_helpers; @@ -91,6 +92,7 @@ use self::call_emission_helpers::emit_call_from_plan; use self::call_expr_helpers::lower_call_expr; use self::call_invocation_helpers::build_call_invocation_plan; use self::call_target_helpers::{CallTargetPlan, CallTargetResolution}; +use self::enum_expr_helpers::lower_enum_construct_expr; use self::for_expr_helpers::lower_for_expr; use self::function_lowering::lower_function; use self::if_expr_helpers::lower_if_expr; diff --git a/compiler/src/mir/lowering_helpers.rs b/compiler/src/mir/lowering_helpers.rs index 7a051b0..1324b5c 100644 --- a/compiler/src/mir/lowering_helpers.rs +++ b/compiler/src/mir/lowering_helpers.rs @@ -49,6 +49,11 @@ pub(crate) fn collect_named_symbols(expr: &HIRExpr, target_name: &str, out: &mut collect_named_symbols(arg, target_name, out); } } + HIRExpr::EnumConstruct { args, .. } => { + for arg in args { + collect_named_symbols(arg, target_name, out); + } + } HIRExpr::MethodCall { receiver, args, .. } => { collect_named_symbols(receiver, target_name, out); for arg in args { diff --git a/compiler/src/parser/stmt.rs b/compiler/src/parser/stmt.rs index 279bf7f..8ab4d29 100644 --- a/compiler/src/parser/stmt.rs +++ b/compiler/src/parser/stmt.rs @@ -34,6 +34,7 @@ impl<'source> Parser<'source> { Some(token) => match &token.kind { TokenKind::LetKw => { self.advance(); + let is_mut = self.consume(TokenKind::MutKw).is_some(); let name = self.expect_ident()?; let ty = if self.consume(TokenKind::Colon).is_some() { @@ -54,6 +55,7 @@ impl<'source> Parser<'source> { name, ty, value: value.map(Box::new), + is_mut, } } TokenKind::ConstKw => { diff --git a/compiler/src/tests/array_assign_tests.rs b/compiler/src/tests/array_assign_tests.rs index cac29b7..31a01ec 100644 --- a/compiler/src/tests/array_assign_tests.rs +++ b/compiler/src/tests/array_assign_tests.rs @@ -17,7 +17,7 @@ use crate::compile_to_ir; fn test_array_constant_index_assign_generates_getelementptr_and_store() { let source = r#" def main() -> i64 { - let arr = [0, 0, 0]; + let mut arr = [0, 0, 0]; arr[0] = 42; arr[0] } @@ -44,7 +44,7 @@ def main() -> i64 { fn test_array_variable_index_assign_compiles_successfully() { let source = r#" def main() -> i64 { - let arr = [10, 20, 30]; + let mut arr = [10, 20, 30]; let i = 0; let j = 2; arr[i] = arr[j]; diff --git a/compiler/src/tests/async_cfg_helpers_tests.rs b/compiler/src/tests/async_cfg_helpers_tests.rs index 5754249..a92f283 100644 --- a/compiler/src/tests/async_cfg_helpers_tests.rs +++ b/compiler/src/tests/async_cfg_helpers_tests.rs @@ -1,5 +1,6 @@ use crate::compile_to_mir; use crate::mir::async_cfg_helpers::{build_async_cfg_plan, compute_live_in_user_locals}; +use crate::mir::{MirFunction, Terminator, MIR_I64}; #[test] fn async_cfg_helpers_plan_and_liveness_simple_multi_await_body() { @@ -28,3 +29,17 @@ async def main() -> i64 { "simple async body should produce live-in state" ); } + +#[test] +fn async_cfg_helpers_accept_unreachable_leaf_blocks() { + let mut function = MirFunction::new("main".to_string(), vec![], MIR_I64); + function.is_async = true; + function.basic_blocks[function.start_block].set_terminator(Terminator::Unreachable); + + let plan = build_async_cfg_plan(&function).expect("unreachable is a terminal CFG edge"); + let live_in = + compute_live_in_user_locals(&function, &plan).expect("unreachable has no live successors"); + + assert_eq!(plan.ordered_blocks, vec![function.start_block]); + assert!(live_in[&function.start_block].is_empty()); +} diff --git a/compiler/src/tests/async_tests.rs b/compiler/src/tests/async_tests.rs index 7dc97cb..2506d99 100644 --- a/compiler/src/tests/async_tests.rs +++ b/compiler/src/tests/async_tests.rs @@ -1449,7 +1449,7 @@ fn loop_with_await_polls_child_future_without_reinvoking_body() { let source = r#" async def step() -> i64 { 1 } async def main() -> i64 { - let x = 0; + let mut x = 0; while x < 2 { let y = await step(); x = x + y; diff --git a/compiler/src/tests/codegen_stream_tests.rs b/compiler/src/tests/codegen_stream_tests.rs index 5c45243..65fea4f 100644 --- a/compiler/src/tests/codegen_stream_tests.rs +++ b/compiler/src/tests/codegen_stream_tests.rs @@ -41,8 +41,8 @@ def abs(x: i64) -> i64 { } def sum_to(n: i64) -> i64 { - let i = 0; - let acc = 0; + let mut i = 0; + let mut acc = 0; while i < n { acc = acc + i; i = i + 1; diff --git a/compiler/src/tests/core_language_correctness_tests.rs b/compiler/src/tests/core_language_correctness_tests.rs new file mode 100644 index 0000000..94c40a8 --- /dev/null +++ b/compiler/src/tests/core_language_correctness_tests.rs @@ -0,0 +1,356 @@ +//! Regression coverage for the core-language-correctness conformance wave. + +use crate::ast::{DeclKind, StmtKind}; +use crate::codegen::{Codegen, JITCodegen}; +use crate::error::CompileError; +use crate::mir::{Instruction, Terminator}; +use crate::{compile_to_mir, Parser, TypeChecker}; + +fn parse(source: &str) -> crate::ast::Program { + Parser::parse(source).expect("source should parse") +} + +fn typeck_error(source: &str) -> crate::typeck::TypeckError { + let program = parse(source); + let mut checker = TypeChecker::new(); + match checker.check_program(&program) { + Err(CompileError::TypeckError(error)) => error, + Err(other) => panic!("expected type-check error, got {other}"), + Ok(()) => panic!("expected type-check failure"), + } +} + +#[test] +fn let_mut_parses_and_marks_the_local_binding_mutable() { + let program = parse( + r#" +def main() -> i64 { + let mut value = 1; + value +} +"#, + ); + let main = program + .decls + .iter() + .find_map(|decl| match &decl.kind { + DeclKind::Function(function) if function.name.name == "main" => Some(function), + _ => None, + }) + .expect("main function"); + + assert!(matches!( + main.body.stmts[0].kind, + StmtKind::Let { is_mut: true, .. } + )); +} + +#[test] +fn mutable_local_can_be_reassigned() { + let program = parse( + r#" +def main() -> i64 { + let mut value = 1; + value = value + 1; + value +} +"#, + ); + TypeChecker::new() + .check_program(&program) + .expect("mutable assignment should type-check"); +} + +#[test] +fn mutable_function_parameter_can_be_reassigned() { + let program = parse( + r#" +def increment(mut value: i64) -> i64 { + value = value + 1; + value +} +def main() -> i64 { increment(1) } +"#, + ); + TypeChecker::new() + .check_program(&program) + .expect("mutable parameter assignment should type-check"); +} + +#[test] +fn immutable_function_parameter_assignment_is_rejected() { + let error = typeck_error( + r#" +def increment(value: i64) -> i64 { + value = value + 1; + value +} +def main() -> i64 { increment(1) } +"#, + ); + assert_eq!(error.stable_code(), Some("immutable-assignment")); +} + +#[test] +fn let_mut_requires_a_binding_name() { + let error = Parser::parse( + r#" +def main() -> i64 { + let mut = 1; + 0 +} +"#, + ) + .expect_err("missing binding name should be rejected"); + + assert!(error.to_string().contains("identifier")); +} + +#[test] +fn immutable_local_assignment_has_stable_diagnostic_and_span() { + let error = typeck_error( + r#" +def main() -> i64 { + let value = 1; + value = value + 1; + value +} +"#, + ); + + assert_eq!(error.stable_code(), Some("immutable-assignment")); + assert!( + error.span().is_some(), + "diagnostic should identify the target" + ); + assert!(error.to_string().contains("let mut")); +} + +#[test] +fn fieldless_enum_variant_is_a_value_and_lowers_to_enum_construct() { + let functions = compile_to_mir( + r#" +enum Color { Red, Green } + +def label(color: Color) -> i64 { + match color { + Color::Red => 1, + Color::Green => 2, + } +} + +def main() -> i64 { + label(Color::Green) +} +"#, + ) + .expect("fieldless enum construction should compile"); + + let main = functions + .iter() + .find(|function| function.name == "main") + .expect("main MIR"); + assert!(main.instructions.iter().any(|instruction| matches!( + instruction, + Instruction::EnumConstruct { + discriminant: 1, + payload: None, + .. + } + ))); +} + +#[test] +fn payload_enum_variant_is_constructed_with_checked_payload() { + let functions = compile_to_mir( + r#" +enum Maybe { Vacant, Value(i64) } + +def unwrap(value: Maybe) -> i64 { + match value { + Maybe::Vacant => 0, + Maybe::Value(inner) => inner, + } +} + +def main() -> i64 { + unwrap(Maybe::Value(42)) +} +"#, + ) + .expect("payload enum construction should compile"); + + let main = functions + .iter() + .find(|function| function.name == "main") + .expect("main MIR"); + assert!(main.instructions.iter().any(|instruction| matches!( + instruction, + Instruction::EnumConstruct { + discriminant: 1, + payload: Some(_), + .. + } + ))); +} + +#[test] +fn jit_codegen_lowers_enum_construction_and_match_without_fallback_comments() { + let functions = compile_to_mir( + r#" +enum Maybe { Empty, Value(i64) } + +def main() -> i64 { + let value = Maybe::Value(42); + match value { + Maybe::Empty => 0, + Maybe::Value(inner) => inner, + } +} +"#, + ) + .expect("enum match should compile to MIR"); + + let mut jit = JITCodegen::new(); + let ir = jit + .generate(&functions) + .expect("JIT codegen should lower enum construction and match"); + + assert!( + !ir.contains("unhandled"), + "JIT enum lowering must not emit fallback comments:\n{ir}" + ); + for unsupported in [ + "unhandled instruction: EnumConstruct", + "unhandled instruction: Discriminant", + "unhandled instruction: ExtractPayload", + "unhandled instruction: Phi", + "unhandled terminator: Switch", + ] { + assert!( + !ir.contains(unsupported), + "JIT enum lowering must not fall back to comments ({unsupported}):\n{ir}" + ); + } + assert!(ir.contains("switch i64"), "expected enum switch:\n{ir}"); + assert!(ir.contains("phi i64"), "expected match result phi:\n{ir}"); + + let mut native = Codegen::new(); + native + .codegen(&functions) + .expect("native codegen should still accept the same MIR"); +} + +#[test] +fn exhaustive_enum_match_routes_unknown_discriminants_to_unreachable() { + let functions = compile_to_mir( + r#" +enum Maybe { Empty, Value(i64) } + +def main() -> i64 { + let value = Maybe::Value(42); + match value { + Maybe::Empty => 0, + Maybe::Value(inner) => inner, + } +} +"#, + ) + .expect("exhaustive enum match should compile to MIR"); + + let main = functions + .iter() + .find(|function| function.name == "main") + .expect("main MIR"); + let otherwise = main + .basic_blocks + .iter() + .find_map(|block| match block.terminator.as_ref() { + Some(Terminator::Switch { otherwise, .. }) => Some(*otherwise), + _ => None, + }) + .expect("enum match switch"); + + assert!( + matches!( + main.basic_blocks[otherwise].terminator, + Some(Terminator::Unreachable) + ), + "exhaustive enum match default edge must not enter the phi join" + ); +} + +#[test] +fn unknown_enum_variant_has_stable_diagnostic() { + let error = typeck_error( + r#" +enum Color { Red } +def main() -> Color { Color::Blue } +"#, + ); + assert_eq!(error.stable_code(), Some("unknown-enum-variant")); +} + +#[test] +fn enum_variant_arity_mismatch_has_stable_diagnostic() { + let error = typeck_error( + r#" +enum Maybe { Value(i64) } +def main() -> Maybe { Maybe::Value() } +"#, + ); + assert_eq!(error.stable_code(), Some("enum-variant-arity")); +} + +#[test] +fn enum_variant_payload_type_mismatch_has_stable_diagnostic() { + let error = typeck_error( + r#" +enum Maybe { Value(i64) } +def main() -> Maybe { Maybe::Value(true) } +"#, + ); + assert_eq!(error.stable_code(), Some("enum-variant-type")); +} + +#[test] +fn fixed_array_constant_out_of_bounds_has_stable_diagnostic() { + let error = typeck_error( + r#" +def main() -> i64 { + let values = [1, 2, 3]; + values[3] +} +"#, + ); + assert_eq!(error.stable_code(), Some("array-index-out-of-bounds")); + assert!(error.span().is_some()); +} + +#[test] +fn fixed_array_non_integer_index_has_stable_diagnostic() { + let error = typeck_error( + r#" +def main() -> i64 { + let values = [1, 2, 3]; + values[true] +} +"#, + ); + assert_eq!(error.stable_code(), Some("invalid-array-index")); + assert!(error.span().is_some()); +} + +#[test] +fn duplicate_closure_parameter_has_stable_diagnostic() { + let error = typeck_error( + r#" +def main() -> i64 { + let invalid = |value, value| value; + invalid(1, 2) +} +"#, + ); + assert_eq!(error.stable_code(), Some("duplicate-closure-parameter")); + assert!(error.span().is_some()); +} diff --git a/compiler/src/tests/for_loop_tests.rs b/compiler/src/tests/for_loop_tests.rs index 5d80f01..8f73784 100644 --- a/compiler/src/tests/for_loop_tests.rs +++ b/compiler/src/tests/for_loop_tests.rs @@ -65,7 +65,7 @@ def main() -> i64 { fn test_nested_for_loops_compile() { let source = r#" def main() -> i64 { - let sum = 0; + let mut sum = 0; for x in [1, 2, 3] { for y in [4, 5] { sum = sum + x + y; @@ -91,7 +91,7 @@ def main() -> i64 { fn test_for_loop_break_and_continue_compile() { let source = r#" def main() -> i64 { - let acc = 0; + let mut acc = 0; for x in [1, 2, 3, 4, 5] { if x > 3 { break; diff --git a/compiler/src/tests/mod.rs b/compiler/src/tests/mod.rs index 5feebde..80c3e73 100644 --- a/compiler/src/tests/mod.rs +++ b/compiler/src/tests/mod.rs @@ -16,6 +16,7 @@ pub mod concrete_type_helpers_tests; mod concurrent_async_tests; pub mod constant_folding_tests; pub mod contracts_tests; +pub mod core_language_correctness_tests; pub mod derive_macro_tests; pub mod diagnostics_tests; pub mod direct_call_helpers_tests; diff --git a/compiler/src/tests/parser_ambiguity_tests.rs b/compiler/src/tests/parser_ambiguity_tests.rs index 57c6442..eef65a4 100644 --- a/compiler/src/tests/parser_ambiguity_tests.rs +++ b/compiler/src/tests/parser_ambiguity_tests.rs @@ -61,7 +61,7 @@ def main() -> i64 { fn test_while_less_than_comparison_parses_as_binary_lt() { let source = r#" def main() -> i64 { - let x = 0; + let mut x = 0; while x < 10 { x = x + 1; } diff --git a/compiler/src/tests/phi_tests.rs b/compiler/src/tests/phi_tests.rs index a35c055..6f53ffd 100644 --- a/compiler/src/tests/phi_tests.rs +++ b/compiler/src/tests/phi_tests.rs @@ -59,8 +59,8 @@ def main() -> i64 { fn test_nested_statement_if_else_does_not_generate_phi_void() { let source = r#" def main() -> i64 { - let lo = 0; - let hi = 10; + let mut lo = 0; + let mut hi = 10; let mid = 5; if mid < hi { diff --git a/compiler/src/tests/property_tests.rs b/compiler/src/tests/property_tests.rs index afe2f2b..91711fb 100644 --- a/compiler/src/tests/property_tests.rs +++ b/compiler/src/tests/property_tests.rs @@ -226,7 +226,7 @@ fn for_loop_ssa_program_strategy() -> impl Strategy { .collect::>() .join(", "); format!( - "def main() -> i64 {{\n let acc = 0;\n for x in [{}] {{\n acc = acc + x;\n }}\n acc\n}}", + "def main() -> i64 {{\n let mut acc = 0;\n for x in [{}] {{\n acc = acc + x;\n }}\n acc\n}}", elems ) }) @@ -452,12 +452,12 @@ proptest! { // **Validates: Requirements 2.1, 2.2** // ============================================================================ -/// Strategy to generate programs with a while loop: `def main() -> i64 { let x = 0; while x < N { x = x + 1; } x }` +/// Strategy to generate programs with a while loop: `def main() -> i64 { let mut x = 0; while x < N { x = x + 1; } x }` /// where N is a random positive integer. The loop iterates x from 0 up to N. fn while_loop_program_strategy() -> impl Strategy { (1u32..=100).prop_map(|n| { format!( - "def main() -> i64 {{ let x = 0; while x < {} {{ x = x + 1; }} x }}", + "def main() -> i64 {{ let mut x = 0; while x < {} {{ x = x + 1; }} x }}", n ) }) @@ -527,7 +527,7 @@ proptest! { // ============================================================================ /// Strategy to generate programs with nested while loops containing break. -/// Pattern: `def main() -> i64 { let x = 0; while x < N { let y = 0; while y < M { if y > K { break; } y = y + 1; } x = x + 1; } x }` +/// Pattern: `def main() -> i64 { let mut x = 0; while x < N { let mut y = 0; while y < M { if y > K { break; } y = y + 1; } x = x + 1; } x }` /// where N, M, K are random positive integers with K < M to ensure the break is reachable. fn nested_while_break_program_strategy() -> impl Strategy { (1u32..=20, 2u32..=20).prop_flat_map(|(n, m)| { @@ -536,14 +536,14 @@ fn nested_while_break_program_strategy() -> impl Strategy { (Just(n), Just(m), 0u32..=k_max) }).prop_map(|(n, m, k)| { format!( - "def main() -> i64 {{ let x = 0; while x < {} {{ let y = 0; while y < {} {{ if y > {} {{ break; }} y = y + 1; }} x = x + 1; }} x }}", + "def main() -> i64 {{ let mut x = 0; while x < {} {{ let mut y = 0; while y < {} {{ if y > {} {{ break; }} y = y + 1; }} x = x + 1; }} x }}", n, m, k ) }) } /// Strategy to generate programs with nested while loops containing continue. -/// Pattern: `def main() -> i64 { let x = 0; while x < N { let y = 0; while y < M { y = y + 1; if y > K { continue; } } x = x + 1; } x }` +/// Pattern: `def main() -> i64 { let mut x = 0; while x < N { let mut y = 0; while y < M { y = y + 1; if y > K { continue; } } x = x + 1; } x }` /// where N, M, K are random positive integers with K < M. fn nested_while_continue_program_strategy() -> impl Strategy { (1u32..=20, 2u32..=20).prop_flat_map(|(n, m)| { @@ -551,14 +551,14 @@ fn nested_while_continue_program_strategy() -> impl Strategy { (Just(n), Just(m), 0u32..=k_max) }).prop_map(|(n, m, k)| { format!( - "def main() -> i64 {{ let x = 0; let total = 0; while x < {} {{ let y = 0; while y < {} {{ y = y + 1; if y > {} {{ continue; }} total = total + 1; }} x = x + 1; }} total }}", + "def main() -> i64 {{ let mut x = 0; let mut total = 0; while x < {} {{ let mut y = 0; while y < {} {{ y = y + 1; if y > {} {{ continue; }} total = total + 1; }} x = x + 1; }} total }}", n, m, k ) }) } /// Strategy to generate programs with mixed nested loops (while + loop) containing break. -/// Pattern: `def main() -> i64 { let x = 0; while x < N { loop { x = x + 1; if x > K { break; } } } x }` +/// Pattern: `def main() -> i64 { let mut x = 0; while x < N { loop { x = x + 1; if x > K { break; } } } x }` /// where N and K are random positive integers with K < N. fn while_loop_break_program_strategy() -> impl Strategy { (2u32..=20).prop_flat_map(|n| { @@ -566,7 +566,7 @@ fn while_loop_break_program_strategy() -> impl Strategy { (Just(n), 1u32..=k_max) }).prop_map(|(n, k)| { format!( - "def main() -> i64 {{ let x = 0; while x < {} {{ loop {{ x = x + 1; if x > {} {{ break; }} }} }} x }}", + "def main() -> i64 {{ let mut x = 0; while x < {} {{ loop {{ x = x + 1; if x > {} {{ break; }} }} }} x }}", n, k ) }) @@ -1241,7 +1241,7 @@ fn diverse_valid_program_strategy() -> impl Strategy { // While loop (1u32..=50).prop_map(|n| { format!( - "def main() -> i64 {{ let x = 0; while x < {} {{ x = x + 1; }} x }}", + "def main() -> i64 {{ let mut x = 0; while x < {} {{ x = x + 1; }} x }}", n ) }), @@ -1250,14 +1250,14 @@ fn diverse_valid_program_strategy() -> impl Strategy { (Just(n), 1u32..n) }).prop_map(|(n, k)| { format!( - "def main() -> i64 {{ let x = 0; while x < {} {{ if x > {} {{ break; }} x = x + 1; }} x }}", + "def main() -> i64 {{ let mut x = 0; while x < {} {{ if x > {} {{ break; }} x = x + 1; }} x }}", n, k ) }), // Nested while loops (1u32..=20, 1u32..=20).prop_map(|(n, m)| { format!( - "def main() -> i64 {{ let x = 0; let total = 0; while x < {} {{ let y = 0; while y < {} {{ y = y + 1; total = total + 1; }} x = x + 1; }} total }}", + "def main() -> i64 {{ let mut x = 0; let mut total = 0; while x < {} {{ let mut y = 0; while y < {} {{ y = y + 1; total = total + 1; }} x = x + 1; }} total }}", n, m ) }), @@ -1382,13 +1382,13 @@ fn unsupported_construct_strategy() -> impl Strategy { // While loops with complex conditions (1u32..=20).prop_map(|n| { format!( - "def main() -> i64 {{ let x = 0; while x < {} {{ x = x + 1; }} x }}", + "def main() -> i64 {{ let mut x = 0; while x < {} {{ x = x + 1; }} x }}", n ) }), // Loop with break returning a value - Just("def main() -> i64 { let x = 0; loop { if x > 10 { break; } x = x + 1; } x }".to_string()), + Just("def main() -> i64 { let mut x = 0; loop { if x > 10 { break; } x = x + 1; } x }".to_string()), // Struct with method call on unknown method Just("def main() -> i64 { let x: i64 = 5; x.unknown_method() }".to_string()), @@ -1636,7 +1636,7 @@ fn name_cache_program_strategy() -> impl Strategy { // Function with while loop (generates many locals) (1u32..=20).prop_map(|n| { format!( - "def main() -> i64 {{ let x = 0; while x < {} {{ x = x + 1; }} x }}", + "def main() -> i64 {{ let mut x = 0; while x < {} {{ x = x + 1; }} x }}", n ) }), @@ -2043,7 +2043,7 @@ fn array_assign_program_strategy() -> impl Strategy { let elems: Vec = (0..size).map(|_| "0".to_string()).collect(); let arr_literal = elems.join(", "); format!( - "def main() -> i64 {{\n let arr = [{}];\n arr[{}] = {};\n arr[{}]\n}}", + "def main() -> i64 {{\n let mut arr = [{}];\n arr[{}] = {};\n arr[{}]\n}}", arr_literal, index, value, index ) }) diff --git a/compiler/src/tests/while_loop_tests.rs b/compiler/src/tests/while_loop_tests.rs index e8d8dad..52dc8aa 100644 --- a/compiler/src/tests/while_loop_tests.rs +++ b/compiler/src/tests/while_loop_tests.rs @@ -14,7 +14,7 @@ use crate::compile_to_ir; /// _Requirements: 2.1, 2.2_ #[test] fn test_simple_while_loop_compiles() { - let source = r#"def main() -> i64 { let x = 0; while x < 10 { x = x + 1; } x }"#; + let source = r#"def main() -> i64 { let mut x = 0; while x < 10 { x = x + 1; } x }"#; let ir = compile_to_ir(source).expect("simple while loop should compile successfully"); // The IR should contain: @@ -47,8 +47,8 @@ fn test_simple_while_loop_compiles() { fn test_while_loop_with_if_body_compiles() { let source = r#" def main() -> i64 { - let x = 0; - let y = 0; + let mut x = 0; + let mut y = 0; while x < 10 { if x < 5 { y = y + 1; @@ -78,7 +78,7 @@ def main() -> i64 { /// _Requirements: 2.1, 2.2_ #[test] fn test_while_loop_false_condition_compiles() { - let source = r#"def main() -> i64 { let x = 100; while x < 10 { x = x + 1; } x }"#; + let source = r#"def main() -> i64 { let mut x = 100; while x < 10 { x = x + 1; } x }"#; let ir = compile_to_ir(source).expect("while loop with false condition should compile"); // Should still have the conditional branch structure @@ -96,7 +96,8 @@ fn test_while_loop_false_condition_compiles() { /// _Requirements: 2.3_ #[test] fn test_loop_with_break_compiles() { - let source = r#"def main() -> i64 { let x = 0; loop { x = x + 1; if x > 5 { break; } } x }"#; + let source = + r#"def main() -> i64 { let mut x = 0; loop { x = x + 1; if x > 5 { break; } } x }"#; let ir = compile_to_ir(source).expect("loop with break should compile successfully"); // The IR should contain: @@ -150,10 +151,10 @@ fn test_loop_immediate_break_compiles() { fn test_nested_while_inner_break_exits_inner_only() { let source = r#" def main() -> i64 { - let x = 0; - let y = 0; + let mut x = 0; + let mut y = 0; while x < 3 { - let z = 0; + let mut z = 0; while z < 5 { if z > 2 { break; @@ -198,8 +199,8 @@ def main() -> i64 { fn test_while_containing_loop_break_exits_loop_not_while() { let source = r#" def main() -> i64 { - let x = 0; - let result = 0; + let mut x = 0; + let mut result = 0; while x < 5 { loop { result = result + 1; @@ -238,9 +239,9 @@ def main() -> i64 { fn test_loop_containing_while_continue_targets_while_condition() { let source = r#" def main() -> i64 { - let x = 0; + let mut x = 0; loop { - let y = 0; + let mut y = 0; while y < 3 { y = y + 1; continue; @@ -276,10 +277,10 @@ def main() -> i64 { fn test_nested_while_continue_targets_inner_condition() { let source = r#" def main() -> i64 { - let x = 0; - let total = 0; + let mut x = 0; + let mut total = 0; while x < 3 { - let y = 0; + let mut y = 0; while y < 5 { y = y + 1; if y > 3 { @@ -315,8 +316,8 @@ def main() -> i64 { fn test_loop_continue_targets_body_start() { let source = r#" def main() -> i64 { - let x = 0; - let y = 0; + let mut x = 0; + let mut y = 0; loop { x = x + 1; if x > 10 { @@ -352,10 +353,10 @@ def main() -> i64 { fn test_three_level_nested_loops_break_targets_innermost() { let source = r#" def main() -> i64 { - let a = 0; - let result = 0; + let mut a = 0; + let mut result = 0; while a < 2 { - let b = 0; + let mut b = 0; while b < 2 { loop { result = result + 1; diff --git a/compiler/src/typeck/borrow.rs b/compiler/src/typeck/borrow.rs index bcef5f2..5c6edcc 100644 --- a/compiler/src/typeck/borrow.rs +++ b/compiler/src/typeck/borrow.rs @@ -300,7 +300,7 @@ impl BorrowChecker { self._env .lookup(name) .and_then(|symbol| match &symbol.kind { - crate::typeck::env::SymbolKind::Var(ty) => Some(ty.clone()), + crate::typeck::env::SymbolKind::Var { ty, .. } => Some(ty.clone()), _ => None, }) } diff --git a/compiler/src/typeck/check/call_helpers.rs b/compiler/src/typeck/check/call_helpers.rs index 0c585e7..a057bc0 100644 --- a/compiler/src/typeck/check/call_helpers.rs +++ b/compiler/src/typeck/check/call_helpers.rs @@ -328,6 +328,12 @@ impl TypeChecker { } pub(super) fn check_call(&mut self, func: &Expr, args: &[Expr]) -> TyResult { + if let ExprKind::Path(path) = &func.kind { + if let Some(result) = self.check_enum_variant_constructor(path, args) { + return result; + } + } + let builtin_name = match &func.kind { ExprKind::Ident(ident) => Some(ident.name.as_str()), ExprKind::Path(path) if path.segments.len() == 1 => { diff --git a/compiler/src/typeck/check/decl_helpers.rs b/compiler/src/typeck/check/decl_helpers.rs index e43507d..d188ed3 100644 --- a/compiler/src/typeck/check/decl_helpers.rs +++ b/compiler/src/typeck/check/decl_helpers.rs @@ -66,7 +66,11 @@ impl TypeChecker { let mut param_types = Vec::new(); for param in &fn_decl.params { let ty = self.check_type(¶m.ty).map_err(CompileError::from)?; - self.env.insert_var(param.name.name.clone(), ty.clone()); + self.env.insert_var_with_mutability( + param.name.name.clone(), + ty.clone(), + param.is_mut, + ); param_types.push(ty); } @@ -103,7 +107,8 @@ impl TypeChecker { let mut param_types = Vec::new(); for param in &fn_decl.params { let ty = self.check_type(¶m.ty)?; - self.env.insert_var(param.name.name.clone(), ty.clone()); + self.env + .insert_var_with_mutability(param.name.name.clone(), ty.clone(), param.is_mut); param_types.push(ty); } @@ -349,7 +354,7 @@ impl TypeChecker { self.env.push_scope(); self.bind_type_params_with_meta(&method.type_params)?; - if method.self_param.is_some() { + if let Some(self_param) = method.self_param { let self_ty = self .env .lookup(class_name) @@ -361,12 +366,14 @@ impl TypeChecker { args: vec![], }) }); - self.env.insert_var("self".to_string(), self_ty); + self.env + .insert_var_with_mutability("self".to_string(), self_ty, self_param.is_mut()); } for param in &method.params { let ty = self.check_type(¶m.ty)?; - self.env.insert_var(param.name.name.clone(), ty); + self.env + .insert_var_with_mutability(param.name.name.clone(), ty, param.is_mut); } let ret_ty = if let Some(ret) = &method.return_type { diff --git a/compiler/src/typeck/check/expr_helpers.rs b/compiler/src/typeck/check/expr_helpers.rs index e9999af..e80aada 100644 --- a/compiler/src/typeck/check/expr_helpers.rs +++ b/compiler/src/typeck/check/expr_helpers.rs @@ -46,6 +46,8 @@ impl TypeChecker { pub(super) fn check_path(&mut self, path: &Path) -> TyResult { if let Some(ident) = path.as_simple() { self.check_ident(ident) + } else if let Some(result) = self.check_enum_variant_constructor(path, &[]) { + result } else { Err(TypeckError::UndefinedVariable { name: path @@ -58,6 +60,83 @@ impl TypeChecker { } } + pub(super) fn check_enum_variant_constructor( + &mut self, + path: &Path, + args: &[Expr], + ) -> Option> { + if path.segments.len() != 2 { + return None; + } + let enum_name = &path.segments[0].name; + let variant_name = &path.segments[1].name; + let variants = self.enum_variants.get(enum_name)?.clone(); + let span_lo = path.segments[0].span.lo; + let span_hi = path.segments[1].span.hi; + + if !variants.iter().any(|variant| variant == variant_name) { + return Some(Err(TypeckError::diagnostic( + "unknown-enum-variant", + format!("enum `{enum_name}` has no variant `{variant_name}`"), + span_lo, + span_hi, + ))); + } + + let field_tys = self + .enum_variant_field_tys + .get(enum_name) + .and_then(|variants| variants.get(variant_name)) + .cloned() + .unwrap_or_default(); + if field_tys.len() != args.len() { + return Some(Err(TypeckError::diagnostic( + "enum-variant-arity", + format!( + "enum variant `{enum_name}::{variant_name}` expects {} argument(s), found {}", + field_tys.len(), + args.len() + ), + span_lo, + span_hi, + ))); + } + + for (index, (expected, arg)) in field_tys.iter().zip(args.iter()).enumerate() { + let actual = match self.check_expr(arg) { + Ok(ty) => ty, + Err(error) => return Some(Err(error)), + }; + if self.infer.unify(expected, &actual).is_err() { + let (span_lo, span_hi) = expression_subject_span(arg); + return Some(Err(TypeckError::diagnostic( + "enum-variant-type", + format!( + "argument {} for `{enum_name}::{variant_name}` has type {}, expected {}", + index + 1, + actual.kind, + expected.kind + ), + span_lo, + span_hi, + ))); + } + } + + let enum_ty = self + .env + .lookup(enum_name) + .and_then(|symbol| symbol.get_ty()) + .cloned() + .unwrap_or_else(|| { + self.env.new_ty(TyKind::Adt { + name: enum_name.clone(), + args: Vec::new(), + }) + }); + Some(Ok(enum_ty)) + } + pub(super) fn check_binary(&mut self, op: &BinOp, left: &Expr, right: &Expr) -> TyResult { let left_ty = self.check_expr(left)?; let right_ty = self.check_expr(right)?; @@ -168,6 +247,7 @@ impl TypeChecker { } pub(super) fn check_assign(&mut self, target: &Expr, value: &Expr) -> TyResult { + self.ensure_assignable_target(target)?; let target_ty = self.check_expr(target)?; let value_ty = self.check_expr(value)?; self.infer.unify(&target_ty, &value_ty)?; @@ -180,21 +260,72 @@ impl TypeChecker { target: &Expr, value: &Expr, ) -> TyResult { + self.ensure_assignable_target(target)?; let target_ty = self.check_expr(target)?; let value_ty = self.check_expr(value)?; self.infer.unify(&target_ty, &value_ty)?; Ok(self.env.unit_ty()) } + fn ensure_assignable_target(&self, target: &Expr) -> TyResult<()> { + let binding = match &target.kind { + ExprKind::Ident(ident) => Some((ident.name.as_str(), ident.span)), + ExprKind::Path(path) => path + .as_simple() + .map(|ident| (ident.name.as_str(), ident.span)), + ExprKind::Index { base, .. } | ExprKind::Field { base, .. } => { + return self.ensure_assignable_target(base); + } + _ => None, + }; + + let Some((name, span)) = binding else { + return Ok(()); + }; + let Some(symbol) = self.env.lookup(name) else { + return Ok(()); + }; + let SymbolKind::Var { is_mut, .. } = &symbol.kind else { + return Ok(()); + }; + if *is_mut { + return Ok(()); + } + + Err(TypeckError::diagnostic( + "immutable-assignment", + format!("cannot assign to immutable binding `{name}`; declare it with `let mut`"), + span.lo, + span.hi, + )) + } + pub(super) fn check_index(&mut self, base: &Expr, index: &Expr) -> TyResult { let base_ty = self.check_expr(base)?; let index_ty = self.check_expr(index)?; if !index_ty.is_int() { - return Err(TypeckError::TypeMismatch { - expected: TyKind::Int(IntKind::ISize), - found: index_ty.kind.clone(), - }); + let (span_lo, span_hi) = expression_subject_span(index); + return Err(TypeckError::diagnostic( + "invalid-array-index", + format!("array index must be an integer, found {}", index_ty.kind), + span_lo, + span_hi, + )); + } + + if let (TyKind::Array(_, len), ExprKind::Literal(Literal::Int(value))) = + (&base_ty.kind, &index.kind) + { + if *value < 0 || (*value as usize) >= *len { + let (span_lo, span_hi) = expression_subject_span(index); + return Err(TypeckError::diagnostic( + "array-index-out-of-bounds", + format!("array index {value} is out of bounds for length {len}"), + span_lo, + span_hi, + )); + } } Ok(match &base_ty.kind { @@ -296,6 +427,22 @@ impl TypeChecker { } pub(super) fn check_lambda(&mut self, params: &[Ident], body: &Expr) -> TyResult { + let mut seen = std::collections::HashSet::new(); + if let Some(duplicate) = params + .iter() + .find(|param| !seen.insert(param.name.as_str())) + { + return Err(TypeckError::diagnostic( + "duplicate-closure-parameter", + format!( + "closure parameter `{}` is declared more than once", + duplicate.name + ), + duplicate.span.lo, + duplicate.span.hi, + )); + } + let param_tys: Vec = params.iter().map(|_| self.infer.fresh_ty_var()).collect(); self.env.push_scope(); @@ -319,6 +466,27 @@ fn tuple_field_index(field: &str) -> Option { }) } +fn expression_subject_span(expr: &Expr) -> (u32, u32) { + match &expr.kind { + ExprKind::Ident(ident) => (ident.span.lo, ident.span.hi), + ExprKind::Path(path) if !path.segments.is_empty() => ( + path.segments[0].span.lo, + path.segments + .last() + .map_or(expr.span.hi, |ident| ident.span.hi), + ), + ExprKind::Literal(Literal::Bool(value)) => { + let len = if *value { 4 } else { 5 }; + (expr.span.lo, expr.span.lo + len) + } + ExprKind::Literal(Literal::Int(value)) => { + let len = value.to_string().len() as u32; + (expr.span.lo, expr.span.lo + len) + } + _ => (expr.span.lo, expr.span.hi), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/src/typeck/check/stmt_helpers.rs b/compiler/src/typeck/check/stmt_helpers.rs index 0a18f4c..935b476 100644 --- a/compiler/src/typeck/check/stmt_helpers.rs +++ b/compiler/src/typeck/check/stmt_helpers.rs @@ -27,7 +27,10 @@ impl TypeChecker { pub(super) fn check_stmt(&mut self, stmt: &Stmt) -> TyResult> { match &stmt.kind { StmtKind::Let { - name, ty, value, .. + name, + ty, + value, + is_mut, } => { let var_ty = if let Some(ty) = ty { self.check_type(ty)? @@ -46,7 +49,8 @@ impl TypeChecker { } self.infer.unify(&var_ty, &value_ty)?; - self.env.insert_var(name.name.clone(), var_ty); + self.env + .insert_var_with_mutability(name.name.clone(), var_ty, *is_mut); Ok(None) } StmtKind::Const { name, ty, value } => { diff --git a/compiler/src/typeck/env.rs b/compiler/src/typeck/env.rs index 6139781..8e39756 100644 --- a/compiler/src/typeck/env.rs +++ b/compiler/src/typeck/env.rs @@ -19,7 +19,7 @@ pub struct Symbol { #[derive(Debug, Clone)] pub enum SymbolKind { /// 变量 - Var(Ty), + Var { ty: Ty, is_mut: bool }, /// 函数 Function { ty: Ty }, /// 类型(结构体、枚举等) @@ -40,9 +40,13 @@ pub enum SymbolKind { impl Symbol { pub fn var(name: String, ty: Ty) -> Self { + Self::var_with_mutability(name, ty, false) + } + + pub fn var_with_mutability(name: String, ty: Ty, is_mut: bool) -> Self { Self { name, - kind: SymbolKind::Var(ty), + kind: SymbolKind::Var { ty, is_mut }, } } @@ -62,7 +66,7 @@ impl Symbol { pub fn get_ty(&self) -> Option<&Ty> { match &self.kind { - SymbolKind::Var(ty) => Some(ty), + SymbolKind::Var { ty, .. } => Some(ty), SymbolKind::Function { ty } => Some(ty), SymbolKind::Type { ty } => Some(ty), SymbolKind::Const { ty } => Some(ty), @@ -253,6 +257,11 @@ impl TypeEnv { self.insert(name, symbol); } + pub fn insert_var_with_mutability(&mut self, name: String, ty: Ty, is_mut: bool) { + let symbol = Symbol::var_with_mutability(name.clone(), ty, is_mut); + self.insert(name, symbol); + } + /// 插入函数 pub fn insert_fn(&mut self, name: String, ty: Ty) { let symbol = Symbol::function(name.clone(), ty); diff --git a/compiler/src/typeck/ty.rs b/compiler/src/typeck/ty.rs index daffc21..4c55023 100644 --- a/compiler/src/typeck/ty.rs +++ b/compiler/src/typeck/ty.rs @@ -384,6 +384,12 @@ pub enum TypeckError { span_lo: u32, span_hi: u32, }, + Diagnostic { + code: &'static str, + message: String, + span_lo: u32, + span_hi: u32, + }, /// 其他错误 Other(String), } @@ -403,6 +409,20 @@ impl TypeckError { } } + pub fn diagnostic( + code: &'static str, + message: impl Into, + span_lo: u32, + span_hi: u32, + ) -> Self { + Self::Diagnostic { + code, + message: message.into(), + span_lo, + span_hi, + } + } + pub fn stable_code(&self) -> Option<&'static str> { match self { Self::NonExhaustiveMatch { .. } => Some("non-exhaustive-match"), @@ -411,6 +431,7 @@ impl TypeckError { Self::OrPatternBindingMismatch { .. } => Some("or-pattern-binding-mismatch"), Self::InvalidQuestionMark { .. } => Some("invalid-question-mark"), Self::FfiSignature { code, .. } => Some(code), + Self::Diagnostic { code, .. } => Some(code), _ => None, } } @@ -428,6 +449,9 @@ impl TypeckError { } | Self::FfiSignature { span_lo, span_hi, .. + } + | Self::Diagnostic { + span_lo, span_hi, .. } => Some((*span_lo, *span_hi)), _ => None, } @@ -501,6 +525,9 @@ impl fmt::Display for TypeckError { TypeckError::FfiSignature { code, message, .. } => { write!(f, "[{}] {}", code, message) } + TypeckError::Diagnostic { code, message, .. } => { + write!(f, "[{}] {}", code, message) + } TypeckError::Other(msg) => { write!(f, "{}", msg) } diff --git a/docs/language-features.md b/docs/language-features.md index 48c3cd6..ff74475 100644 --- a/docs/language-features.md +++ b/docs/language-features.md @@ -4,7 +4,7 @@ Sengoo is a compiled language focused on practical engineering workflows: - Hybrid Python interoperability for gradual migration - Fast compile feedback with incremental pipeline reuse -- LLVM-native code generation and executable output +- Textual LLVM IR compiled and linked by `clang`, plus a Cranelift fast path - Optional non-invasive reflection with sidecar metadata ## 1. Current Capability Snapshot @@ -12,6 +12,8 @@ Sengoo is a compiled language focused on practical engineering workflows: | Capability | Status | Notes | |---|---|---| | Core syntax (`def`, `if`, `for`, `while`, `struct`, `impl`) | Available | Use `examples/*.sg` as validated learning surface. | +| Immutable-by-default locals (`let mut` for reassignment) | Available | `immutable-assignment` is shared by `sgc` JSON and `sglsp`. | +| Enum variants as values | Available | Fieldless and payload constructors lower to the representation consumed by `match`. | | Static type-check pipeline | Available | Entry command: `sgc check `. | | API documentation generation | Available | `sgc doc --output target/doc`. | | Incremental compile pipeline | Available | Fingerprint + workset-based invalidation/rebuild strategy. | @@ -39,7 +41,7 @@ def add(a: i64, b: i64) -> i64 { ```sg def sum(arr: [i64; 4]) -> i64 { - let total = 0; + let mut total = 0; for v in arr { total = total + v; } @@ -216,7 +218,7 @@ Sengoo 是一门面向工程落地的编译型语言,当前重点: - Python 互操作与渐进迁移 - 快速编译反馈(增量链路) -- LLVM 原生代码生成 +- 由 `clang` 编译和链接文本 LLVM IR,并提供 Cranelift 快路径 - 按需开启的非侵入式反射 ## 1. 能力快照 @@ -224,6 +226,8 @@ Sengoo 是一门面向工程落地的编译型语言,当前重点: | 能力 | 状态 | 说明 | |---|---|---| | 核心语法(`def`/`if`/`for`/`while`/`struct`/`impl`) | 可用 | 建议以 `examples/*.sg` 为已验证学习面。 | +| 默认不可变局部变量(重赋值使用 `let mut`) | 可用 | `sgc` JSON 与 `sglsp` 共用 `immutable-assignment`。 | +| 枚举变体作为值 | 可用 | 无 payload 与带 payload 构造均降级为 `match` 使用的表示。 | | 静态类型检查流水线 | 可用 | 命令:`sgc check `。 | | 增量编译链路 | 可用 | 基于指纹 + workset 感知失效与重编译。 | | Daemon 编译服务 | 可用 | `sgc daemon --addr 127.0.0.1:48765`。 | @@ -250,7 +254,7 @@ def add(a: i64, b: i64) -> i64 { ```sg def sum(arr: [i64; 4]) -> i64 { - let total = 0; + let mut total = 0; for v in arr { total = total + v; } diff --git a/docs/let-mut-migration.md b/docs/let-mut-migration.md new file mode 100644 index 0000000..9db90e4 --- /dev/null +++ b/docs/let-mut-migration.md @@ -0,0 +1,24 @@ +# `let mut` Migration + +Sengoo local bindings are immutable by default. Code that reassigns a local +must declare that binding with `let mut`. + +```sg +let mut count = 0; +count = count + 1; +``` + +Assignments through an indexed or field place also require the root local to be +mutable: + +```sg +let mut values = [1, 2, 3]; +values[0] = 4; +``` + +The compiler reports immutable reassignment with the stable diagnostic code +`immutable-assignment` and highlights the assignment target. Function +parameters already accept the same `mut` modifier. + +This change is source-incompatible only for programs that previously reassigned +plain `let` bindings. Read-only declarations require no changes. diff --git a/examples/05_loop.sg b/examples/05_loop.sg index 9be4c7e..1b1350f 100644 --- a/examples/05_loop.sg +++ b/examples/05_loop.sg @@ -2,7 +2,7 @@ def main() -> i64 { let arr = [1, 2, 3, 4, 5]; - let sum = 0; + let mut sum = 0; for x in arr { sum = sum + x } diff --git a/examples/conformance/01_scalars_control.sg b/examples/conformance/01_scalars_control.sg new file mode 100644 index 0000000..11a1675 --- /dev/null +++ b/examples/conformance/01_scalars_control.sg @@ -0,0 +1,19 @@ +// Core conformance: scalars, print, if, while, and range for. +// Run: sgc run examples/conformance/01_scalars_control.sg +// Expected output: core + +def main() -> i64 { + print("core"); + let ratio = 1.5 + 2.5; + let enabled = true; + let mut total = 0; + let mut index = 0; + while index < 3 { + total = total + 1; + index = index + 1; + } + for value in 0..4 { + total = total + value; + } + if enabled and ratio > 3.0 { total } else { 1 } +} diff --git a/examples/conformance/02_recursion.sg b/examples/conformance/02_recursion.sg new file mode 100644 index 0000000..7f556a3 --- /dev/null +++ b/examples/conformance/02_recursion.sg @@ -0,0 +1,15 @@ +// Core conformance: recursive function calls. +// Run: sgc run examples/conformance/02_recursion.sg +// Expected exit code: 13 + +def fibonacci(value: i64) -> i64 { + if value < 2 { + value + } else { + fibonacci(value - 1) + fibonacci(value - 2) + } +} + +def main() -> i64 { + fibonacci(7) +} diff --git a/examples/conformance/03_array_write.sg b/examples/conformance/03_array_write.sg new file mode 100644 index 0000000..c9b6f87 --- /dev/null +++ b/examples/conformance/03_array_write.sg @@ -0,0 +1,9 @@ +// Core conformance: fixed-size array index write and read. +// Run: sgc run examples/conformance/03_array_write.sg +// Expected exit code: 42 + +def main() -> i64 { + let mut values = [1, 2, 3]; + values[1] = 42; + values[1] +} diff --git a/examples/conformance/04_closure_multi_capture.sg b/examples/conformance/04_closure_multi_capture.sg new file mode 100644 index 0000000..a145535 --- /dev/null +++ b/examples/conformance/04_closure_multi_capture.sg @@ -0,0 +1,10 @@ +// Core conformance: closure capture of multiple enclosing locals. +// Run: sgc run examples/conformance/04_closure_multi_capture.sg +// Expected exit code: 18 + +def main() -> i64 { + let left = 7; + let right = 8; + let add = |value| left + right + value; + add(3) +} diff --git a/examples/conformance/05_enum_payload.sg b/examples/conformance/05_enum_payload.sg new file mode 100644 index 0000000..275a1ec --- /dev/null +++ b/examples/conformance/05_enum_payload.sg @@ -0,0 +1,16 @@ +// Core conformance: payload enum construction and match extraction. +// Run: sgc run examples/conformance/05_enum_payload.sg +// Expected exit code: 42 + +enum Maybe { Vacant, Value(i64) } + +def unwrap(value: Maybe) -> i64 { + match value { + Maybe::Vacant => 0, + Maybe::Value(inner) => inner, + } +} + +def main() -> i64 { + unwrap(Maybe::Value(42)) +} diff --git a/examples/conformance/06_enum_multi_payload.sg b/examples/conformance/06_enum_multi_payload.sg new file mode 100644 index 0000000..2b2e3a4 --- /dev/null +++ b/examples/conformance/06_enum_multi_payload.sg @@ -0,0 +1,16 @@ +// Core conformance: multi-field enum payload construction and extraction. +// Run: sgc run examples/conformance/06_enum_multi_payload.sg +// Expected exit code: 42 + +enum Outcome { Empty, Value(i64, bool) } + +def resolve(value: Outcome) -> i64 { + match value { + Outcome::Empty => 0, + Outcome::Value(number, enabled) => if enabled { number } else { 0 }, + } +} + +def main() -> i64 { + resolve(Outcome::Value(42, true)) +} diff --git a/examples/ergonomics/03_enum_match.sg b/examples/ergonomics/03_enum_match.sg index c9fe0f7..dd647f4 100644 --- a/examples/ergonomics/03_enum_match.sg +++ b/examples/ergonomics/03_enum_match.sg @@ -15,5 +15,5 @@ def label(c: Color) -> i64 { } def main() -> i64 { - 0 + label(Color::Green) } diff --git a/openspec/changes/core-language-correctness/VERIFICATION.md b/openspec/changes/core-language-correctness/VERIFICATION.md new file mode 100644 index 0000000..275a8e2 --- /dev/null +++ b/openspec/changes/core-language-correctness/VERIFICATION.md @@ -0,0 +1,81 @@ +# Verification Record + +## Implementation Base + +- Base revision: `b026b8a79a13dcee0fed71294ecebee4113ce960` +- Rust: `rustc 1.94.0` +- Cargo: `cargo 1.94.0` +- Clang: `19.1.7` +- OpenSpec CLI: `1.3.1` + +## Baseline Reconciliation + +The implementation base already contained the array and closure pointer-lowering +repairs described by the original reproduction. Its compiler library suite was +green at 681/681 tests. The historical Linux 11-failure report could not be +reproduced from this base, so no snapshot was rebaselined and no test was +ignored. + +The conformance harness records arrays and closures as required passing cases. +The mutability and enum-value tests were added before their implementations and +served as the failing implementation baseline. + +## Conformance Matrix + +| Form | Example | Expected | +| --- | --- | --- | +| Scalars, print, `if`, `while`, range `for`, `let mut` | `examples/conformance/01_scalars_control.sg` | stdout `core`, exit 9 | +| Recursion | `examples/conformance/02_recursion.sg` | exit 13 | +| Struct construction and field access | `examples/08_struct.sg` | exit 30 | +| `impl` method call | `examples/09_method_call.sg` | exit 43 | +| Fixed array read | `examples/04_array.sg` | exit 20 | +| Fixed array iteration | `examples/05_loop.sg` | exit 15 | +| Fixed array write | `examples/conformance/03_array_write.sg` | exit 42 | +| Single-capture closure | `examples/06_lambda.sg` | exit 15 | +| Multi-capture closure | `examples/conformance/04_closure_multi_capture.sg` | exit 18 | +| Fieldless enum value and match | `examples/ergonomics/03_enum_match.sg` | exit 2 | +| Payload enum value and match | `examples/conformance/05_enum_payload.sg` | exit 42 | +| Multi-field enum payload and match | `examples/conformance/06_enum_multi_payload.sg` | exit 42 | + +## Focused Evidence + +- `cargo test -p sengoo-compiler core_language_correctness_tests -- --nocapture` + passes 16 tests, including exhaustive enum-match CFG and JIT enum lowering. +- `cargo test -p sgc core_conformance_examples_compile_link_and_run -- --nocapture` + compiles, links, runs, and checks all conformance matrix rows. +- `cargo test -p sgc async_native_runtime_preserves_payloadless_enum_across_resume + -- --nocapture` proves the enum byte-payload ABI remains valid across async + frame spill/reload. +- `cargo test -p sgc jit_enum_match_ir_is_accepted_by_clang -- --nocapture` + sends JIT-generated payload-enum match IR through `clang -c`, covering + construction, discriminant extraction, payload extraction, switch, phi, and + unreachable-default lowering. +- `cargo test -p sgfmt preserves_mutable_local_bindings -- --nocapture` proves + formatting preserves `let mut`. +- `sgc` JSON and `sglsp` tests assert stable codes and exact source ranges for + immutable assignment, enum-value errors, array misuse, and duplicate closure + parameters. + +## Local Full-Workspace Gate + +- `cargo clippy --workspace --all-targets --locked -- -D warnings` passes. +- `cargo test --workspace --locked` passes after the async/JIT enum review + fixes; the compiler library reports 699 passing tests in the final run. + +An independent read-only review found and drove regression coverage for two +adjacent enum paths: payloadless enum values crossing `await`, and the public +JIT enum-match path. A follow-up review additionally identified an invalid +exhaustive-match default edge into a phi join. All three findings now have +executable regression tests. + +The final independent follow-up review reported no remaining P0, P1, or P2 +findings. + +## Dependency Decisions + +- `inkwell` and `llvm-sys` were removed because the workspace emits textual LLVM + IR and invokes `clang`. +- `pyo3` remains a workspace dependency because `sengoo-runtime` consumes it + through the optional `python` feature. + +The final full-workspace and Linux CI results are recorded by the PR checks. diff --git a/openspec/changes/core-language-correctness/design.md b/openspec/changes/core-language-correctness/design.md index 37ef088..e0ca2ce 100644 --- a/openspec/changes/core-language-correctness/design.md +++ b/openspec/changes/core-language-correctness/design.md @@ -42,6 +42,21 @@ All reproduced from a clean source build on Linux (`rustc` 1.96, `clang` + | Conformance/CI | `cargo test` on Linux | 670/681 pass; 11 fail (basic-example `insta` snapshots + one Linux `#[cfg]` attribute test) | | Reproducibility | fresh `cargo build` | no committed `Cargo.lock`; resolves newer deps that require a newer Rust than the README toolchain | +## Implementation Baseline Reconciliation + +The implementation branch starts from merged PR #12 +(`b026b8a79a13dcee0fed71294ecebee4113ce960`). On that revision, the array and +closure lowering fixes had already landed through other work, and the Windows +compiler library suite passed 681/681 tests. The earlier Linux report of 11 +failures therefore remains historical reproduction evidence, not a baseline +that can still be reproduced from this implementation base. + +The conformance gate treats the already-green array and closure forms as +required passing cases. New tests for `let mut`, immutable assignment, enum +construction, and their diagnostics were introduced before their +implementations and used as the red/green implementation baseline. No snapshot +was rebaselined and no failing test was ignored. + ## Approach 1. **Conformance contract first.** Define the pinned core-conformance form list diff --git a/openspec/changes/core-language-correctness/proposal.md b/openspec/changes/core-language-correctness/proposal.md index bc66b5c..cf72f68 100644 --- a/openspec/changes/core-language-correctness/proposal.md +++ b/openspec/changes/core-language-correctness/proposal.md @@ -69,9 +69,7 @@ stay green in CI**, and aligns those semantics with mainstream expectations. ## Impact -- OpenSpec planning artifact only in this change-creation task; no compiler or - runtime code is modified here. -- Future implementation will touch `compiler/src/mir/lowering.rs`, +- Implementation touches `compiler/src/mir/lowering/`, `compiler/src/codegen/` (LLVM IR emission), `compiler/src/parser/`, `compiler/src/typeck/`, `compiler/src/hir/`, `tools/sgc/` (example/conformance harness), `tools/sglsp/` (diagnostic parity), `.github/` (CI), `Cargo.lock`, diff --git a/openspec/changes/core-language-correctness/specs/core-language-correctness/spec.md b/openspec/changes/core-language-correctness/specs/core-language-correctness/spec.md index fb72182..bd03837 100644 --- a/openspec/changes/core-language-correctness/specs/core-language-correctness/spec.md +++ b/openspec/changes/core-language-correctness/specs/core-language-correctness/spec.md @@ -44,7 +44,7 @@ before pointer indexing. - **WHEN** a program runs `for v in arr` over a fixed-size array - **THEN** the loop binds each element in order - **AND** `examples/04_array.sg`, `examples/05_loop.sg`, and the - `docs/language-features.md` §2.2 snippet compile and produce their documented + `docs/language-features.md` section 2.2 snippet compile and produce their documented results #### Scenario: Growable arrays remain out of scope diff --git a/openspec/changes/core-language-correctness/tasks.md b/openspec/changes/core-language-correctness/tasks.md index 6f20c1e..5833bf1 100644 --- a/openspec/changes/core-language-correctness/tasks.md +++ b/openspec/changes/core-language-correctness/tasks.md @@ -1,72 +1,74 @@ ## 1. Baseline And Conformance Harness -- [ ] 1.1 Run `openspec validate core-language-correctness --strict`. -- [ ] 1.2 Pin the v1 core-conformance form list (see `design.md`) and record the +- [x] 1.1 Run `openspec validate core-language-correctness --strict`. +- [x] 1.2 Pin the v1 core-conformance form list (see `design.md`) and record the expected result for each form. -- [ ] 1.3 Add a conformance harness (extend the `tools/sgc` example smoke path or +- [x] 1.3 Add a conformance harness (extend the `tools/sgc` example smoke path or add a dedicated runner) that compiles each form's example and asserts its result or process exit code. -- [ ] 1.4 Enter the currently-failing forms (array index, array `for`, closure - capture, `let mut`, enum value) as expected-failing baseline entries so the - gate tracks progress and blocks silent regressions. -- [ ] 1.5 Record the 11 failing `cargo test` cases on Linux and classify each as - to-fix or to-rebaseline-with-reason. +- [x] 1.4 Record the baseline status of array index, array `for`, closure + capture, `let mut`, and enum values. Array/closure were already green on the + merged implementation base; mutability/enum tests supplied the red/green + baseline. See `VERIFICATION.md`. +- [x] 1.5 Reconcile the historical 11 failing Linux cases. They were not + reproducible from the merged implementation base, no snapshot was + rebaselined, and Linux CI remains the final source of truth. ## 2. Native Array Correctness -- [ ] 2.1 Fix fixed-size array value/address lowering so an array place decays to +- [x] 2.1 Fix fixed-size array value/address lowering so an array place decays to an element pointer (`T*`) before `getelementptr`, eliminating the `'[N x T]*' ... but expected 'i64*'` IR verification error. -- [ ] 2.2 Cover `arr[i]` read and write, and `for v in arr` iteration, with +- [x] 2.2 Cover `arr[i]` read and write, and `for v in arr` iteration, with runnable examples and executable result assertions. -- [ ] 2.3 Restore `examples/04_array.sg`, `examples/05_loop.sg`, and the +- [x] 2.3 Restore `examples/04_array.sg`, `examples/05_loop.sg`, and the `docs/language-features.md` §2.2 snippet to compiling-and-correct status. -- [ ] 2.4 Add negative tests for out-of-form array misuse that must still be +- [x] 2.4 Add negative tests for out-of-form array misuse that must still be rejected, with stable diagnostics. ## 3. Closure Capture Correctness -- [ ] 3.1 Fix environment-capturing closure lowering so captured-variable slots +- [x] 3.1 Fix environment-capturing closure lowering so captured-variable slots load/store at the correct pointer type. -- [ ] 3.2 Restore `examples/06_lambda.sg` and add closure examples covering +- [x] 3.2 Restore `examples/06_lambda.sg` and add closure examples covering capture-by-value of one and multiple locals with result assertions. -- [ ] 3.3 Add negative tests for unsupported closure shapes that remain rejected, +- [x] 3.3 Add negative tests for unsupported closure shapes that remain rejected, with stable diagnostics. ## 4. Mutability: `let mut` And Immutable Assignment -- [ ] 4.1 Parse `let mut ` (and the `mut` binding pattern) and add parser +- [x] 4.1 Parse `let mut ` (and the `mut` binding pattern) and add parser tests for accepted and rejected forms. -- [ ] 4.2 Thread a mutability flag through HIR/typeck and reject assignment to an +- [x] 4.2 Thread a mutability flag through HIR/typeck and reject assignment to an immutable binding with a stable diagnostic code. -- [ ] 4.3 Prove `sgc` JSON and `sglsp` parity (same range, severity, code/message +- [x] 4.3 Prove `sgc` JSON and `sglsp` parity (same range, severity, code/message family) for the immutable-assignment diagnostic. -- [ ] 4.4 If enforcement is source-incompatible with committed sources/examples, +- [x] 4.4 If enforcement is source-incompatible with committed sources/examples, add a migration note first and update affected examples in the same change. ## 5. Enum Variants As Values -- [ ] 5.1 Resolve `Enum::Variant` in value position; add typeck tests for known +- [x] 5.1 Resolve `Enum::Variant` in value position; add typeck tests for known and unknown variants. -- [ ] 5.2 Support payload-carrying variant construction for variants with fields +- [x] 5.2 Support payload-carrying variant construction for variants with fields and lower to the discriminant/payload representation consumed by `match`. -- [ ] 5.3 Add runnable examples constructing enum values and matching on them, +- [x] 5.3 Add runnable examples constructing enum values and matching on them, with result assertions; extend `examples/ergonomics/03_enum_match.sg` so its `main` actually constructs and consumes a variant. -- [ ] 5.4 Add negative tests for unknown variants and arity/type mismatches with +- [x] 5.4 Add negative tests for unknown variants and arity/type mismatches with stable diagnostics. ## 6. Reproducibility And Doc Reconciliation -- [ ] 6.1 Commit `Cargo.lock` and add a `rust-toolchain.toml` pinning the Rust +- [x] 6.1 Commit `Cargo.lock` and add a `rust-toolchain.toml` pinning the Rust version used to build the workspace. -- [ ] 6.2 Wire the conformance harness and `cargo test` into CI on Linux. -- [ ] 6.3 Correct the backend description (textual LLVM IR + `clang`, plus the +- [x] 6.2 Wire the conformance harness and `cargo test` into CI on Linux. +- [x] 6.3 Correct the backend description (textual LLVM IR + `clang`, plus the Cranelift fast path) in `README.md`, `README.zh-CN.md`, and `docs/language-features.md`. -- [ ] 6.4 Remove the unused `inkwell` / `llvm-sys` / `pyo3` workspace +- [x] 6.4 Remove the unused `inkwell` / `llvm-sys` / `pyo3` workspace dependencies from the root `Cargo.toml` (or document why they are retained). -- [ ] 6.5 Update `PROGRESS.md` status flags so completed items reflect verified, +- [x] 6.5 Update `PROGRESS.md` status flags so completed items reflect verified, compiling-and-running behavior. ## 7. Verification @@ -75,9 +77,9 @@ expected result on Linux. - [ ] 7.2 `cargo test` is green on Linux (failing cases fixed or rebaselined with a recorded reason). -- [ ] 7.3 `sgc check` / JSON diagnostic snapshots for representative accepted and +- [x] 7.3 `sgc check` / JSON diagnostic snapshots for representative accepted and rejected core forms. -- [ ] 7.4 `sglsp` diagnostic parity proven for every new diagnostic code. +- [x] 7.4 `sglsp` diagnostic parity proven for every new diagnostic code. ## Archive Gate diff --git a/packages/sggame/examples/snake.sg b/packages/sggame/examples/snake.sg index d2acb6e..727d035 100644 --- a/packages/sggame/examples/snake.sg +++ b/packages/sggame/examples/snake.sg @@ -38,8 +38,8 @@ def direction_blocks_reverse(current: Direction, requested: Direction) -> bool { def draw_grid(surface: Surface, cell: i64, color: Color) -> bool { let x = 0; let x_limit = surface.width; - let mut_x = 0; - let mut_x_run = true; + let mut mut_x = 0; + let mut mut_x_run = true; while mut_x_run { if mut_x > x_limit { mut_x_run = false; @@ -48,8 +48,8 @@ def draw_grid(surface: Surface, cell: i64, color: Color) -> bool { mut_x = mut_x + cell; } } - let mut_y = 0; - let mut_y_run = true; + let mut mut_y = 0; + let mut mut_y_run = true; while mut_y_run { if mut_y > surface.height { mut_y_run = false; @@ -96,15 +96,15 @@ def main() -> i64 { let clock_start = clock_new(); let running_start = true; - let head_loop = head_x_start; - let head_y_loop = head_y_start; - let dir_loop = dir_start; - let length_loop = length_start; - let food_x_loop = food_x_start; + let mut head_loop = head_x_start; + let mut head_y_loop = head_y_start; + let mut dir_loop = dir_start; + let mut length_loop = length_start; + let mut food_x_loop = food_x_start; let food_y_loop = food_y_start; - let score_loop = score_start; - let clock_loop = clock_start; - let running_loop = running_start; + let mut score_loop = score_start; + let mut clock_loop = clock_start; + let mut running_loop = running_start; while running_loop { pump(); @@ -177,8 +177,8 @@ def main() -> i64 { draw_grid(surface, cell, grid_color); let segment = 0; - let mut_segment = 0; - let mut_segment_run = true; + let mut mut_segment = 0; + let mut mut_segment_run = true; while mut_segment_run { if mut_segment >= length_after { mut_segment_run = false; diff --git a/packages/sggui/examples/counter.sg b/packages/sggui/examples/counter.sg index 1f11cd5..57b00d7 100644 --- a/packages/sggui/examples/counter.sg +++ b/packages/sggui/examples/counter.sg @@ -23,9 +23,9 @@ def main() -> i64 { let button_fill = color_new(59, 130, 246, 255); let button_border = color_new(147, 197, 253, 255); - let running = app.running(); - let app_loop = app; - let label_loop = count_label; + let mut running = app.running(); + let mut app_loop = app; + let mut label_loop = count_label; let start_ms = app.platform.ticks_ms(); while running { let polled = app_loop.poll(); diff --git a/packages/sggui/tests/hit_test.sg b/packages/sggui/tests/hit_test.sg index a562b9c..554b05e 100644 --- a/packages/sggui/tests/hit_test.sg +++ b/packages/sggui/tests/hit_test.sg @@ -23,14 +23,14 @@ def main() -> i64 { assert_true(button.handle_event(inside)); assert_true(not button.handle_event(outside)); assert_true(not button.handle_event(key_only)); - let count = 0; + let mut count = 0; count = counter_apply_click(count, button, outside); assert_eq_i64(count, 0); count = counter_apply_click(count, button, inside); assert_eq_i64(count, 1); count = counter_apply_click(count, button, inside); assert_eq_i64(count, 2); - let label = label_set_value(label_new("Count", count, rect_new(0, 0, 200, 32)), count); + let mut label = label_set_value(label_new("Count", count, rect_new(0, 0, 200, 32)), count); assert_eq_i64(label.value, 2); label = label_increment(label); assert_eq_i64(label.value, 3); @@ -41,4 +41,4 @@ def main() -> i64 { assert_eq_i64(row_a.bounds.y, 0); assert_eq_i64(row_b.bounds.y, 40); 0; -} \ No newline at end of file +} diff --git a/packages/sgplatform/examples/blank_window.sg b/packages/sgplatform/examples/blank_window.sg index 4a7c069..e9fc9f6 100644 --- a/packages/sgplatform/examples/blank_window.sg +++ b/packages/sgplatform/examples/blank_window.sg @@ -29,7 +29,7 @@ def main() -> i64 { let presented = renderer.present().unwrap_or(0); let start = platform.ticks_ms(); - let running = true; + let mut running = true; while running { let event = platform.poll_event(); if event_is_quit(event) { diff --git a/runtime/src/net/tls.rs b/runtime/src/net/tls.rs index 59c0b65..52dd24f 100644 --- a/runtime/src/net/tls.rs +++ b/runtime/src/net/tls.rs @@ -281,7 +281,7 @@ mod tests { TestCertBundle { #[cfg(not(windows))] ca_der: CertificateDer::from(ca_cert.der().clone()), - server_der: CertificateDer::from(server_cert.der().clone()), + server_der: server_cert.der().clone(), server_key: PrivateKeyDer::Pkcs8(server_key.serialize_der().into()), #[cfg(not(windows))] wrong_host_der, diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..8823dbe --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.94.0" +profile = "minimal" +components = ["clippy", "rustfmt"] diff --git a/tools/sgc/src/interface/generic_instances/collector.rs b/tools/sgc/src/interface/generic_instances/collector.rs index 413d34c..0a0b830 100644 --- a/tools/sgc/src/interface/generic_instances/collector.rs +++ b/tools/sgc/src/interface/generic_instances/collector.rs @@ -440,7 +440,9 @@ pub(super) fn collect_generic_instances_in_stmt( callable_meta: &HashMap, ) { match &stmt.kind { - StmtKind::Let { name, ty, value } => { + StmtKind::Let { + name, ty, value, .. + } => { if let Some(value) = value.as_deref() { collect_generic_instances_in_expr( out, diff --git a/tools/sgc/src/pipeline/ast_pruning.rs b/tools/sgc/src/pipeline/ast_pruning.rs index f198b14..87f6042 100644 --- a/tools/sgc/src/pipeline/ast_pruning.rs +++ b/tools/sgc/src/pipeline/ast_pruning.rs @@ -53,7 +53,18 @@ pub(super) fn reachable_ast_function_names(program: &AstProgram) -> Option> = vec![Vec::new(); functions.len()]; for (idx, fn_decl) in functions.iter().enumerate() { - edges[idx] = collect_ast_call_targets_from_block(&fn_decl.body, &index_by_name); + let mut targets = collect_ast_call_targets_from_block(&fn_decl.body, &index_by_name); + let mut seen = targets.iter().copied().collect(); + for contract in [ + fn_decl.precondition.as_deref(), + fn_decl.postcondition.as_deref(), + ] + .into_iter() + .flatten() + { + collect_ast_call_targets_from_expr(contract, &index_by_name, &mut targets, &mut seen); + } + edges[idx] = targets; } let mut reachable = vec![false; functions.len()]; diff --git a/tools/sgc/src/pipeline/hir_pruning.rs b/tools/sgc/src/pipeline/hir_pruning.rs index 87eb08e..4dfc87f 100644 --- a/tools/sgc/src/pipeline/hir_pruning.rs +++ b/tools/sgc/src/pipeline/hir_pruning.rs @@ -24,7 +24,18 @@ pub(super) fn prune_unreachable_hir_functions(items: &mut Vec) -> usize let mut edges: Vec> = vec![Vec::new(); functions.len()]; for (idx, fn_item) in functions.iter().enumerate() { - edges[idx] = collect_hir_call_targets_from_body(&fn_item.body, &index_by_name); + let mut targets = collect_hir_call_targets_from_body(&fn_item.body, &index_by_name); + let mut seen = targets.iter().copied().collect(); + for contract in [ + fn_item.precondition.as_ref(), + fn_item.postcondition.as_ref(), + ] + .into_iter() + .flatten() + { + collect_hir_call_targets_from_expr(contract, &index_by_name, &mut targets, &mut seen); + } + edges[idx] = targets; } let mut reachable = vec![false; functions.len()]; @@ -227,6 +238,11 @@ fn collect_hir_call_targets_from_expr( } } } + HIRExpr::EnumConstruct { args, .. } => { + for arg in args { + collect_hir_call_targets_from_expr(arg, index_by_name, targets, seen); + } + } HIRExpr::MethodCall { receiver, args, .. } => { collect_hir_call_targets_from_expr(receiver, index_by_name, targets, seen); for arg in args { diff --git a/tools/sgc/src/tests.rs b/tools/sgc/src/tests.rs index 4b5d01d..1e793ff 100644 --- a/tools/sgc/src/tests.rs +++ b/tools/sgc/src/tests.rs @@ -34,7 +34,9 @@ use super::{ use crate::cli::Cli; use crate::cross_compile::NativeBuildTarget; use clap::Parser as _; -use sengoo_compiler::{compile_to_ir as compile_compiler_ir, CompileWarning}; +use sengoo_compiler::{ + compile_to_ir as compile_compiler_ir, compile_to_mir, CompileWarning, JITCodegen, +}; use serde_json::Value; use std::collections::{BTreeMap, HashSet}; use std::fs; @@ -3280,6 +3282,54 @@ async def main() -> i64 { let _ = fs::remove_file(&exe_path); } +#[test] +fn async_native_runtime_preserves_payloadless_enum_across_resume() { + let Some(clang) = find_clang() else { + return; + }; + + let Some(runtime_c) = find_runtime_c() else { + return; + }; + + if !stdlib_runtime_c_is_compilable(&clang, Path::new(&runtime_c)) { + return; + } + + let source = r#" +enum State { Cold, Hot } + +async def step() -> i64 { 1 } + +async def main() -> i64 { + let state = State::Hot; + let waited = await step(); + match state { + State::Cold => 0, + State::Hot => waited + 41, + } +} +"#; + + let llvm_ir = compile_source(source, 1).expect("async enum source should compile to LLVM IR"); + let ll_path = temp_artifact("async-payloadless-enum", "ll"); + fs::write(&ll_path, llvm_ir).unwrap(); + + let exe_path = temp_artifact( + "async-payloadless-enum", + if cfg!(windows) { "exe" } else { "" }, + ); + compile_native_binary(&clang, &ll_path, &exe_path, Some(&runtime_c), 1, None, None).unwrap(); + + let output = Command::new(&exe_path) + .output() + .expect("async enum executable should run"); + assert_eq!(output.status.code(), Some(42)); + + let _ = fs::remove_file(&ll_path); + let _ = fs::remove_file(&exe_path); +} + #[test] fn async_native_runtime_executes_if_structured_multi_await_body() { let Some(clang) = find_clang() else { @@ -3345,7 +3395,7 @@ fn async_native_runtime_executes_loop_with_await_body() { async def step() -> i64 { 1 } async def main() -> i64 { - let x = 0; + let mut x = 0; while x < 3 { let y = await step(); x = x + y; @@ -3386,7 +3436,7 @@ fn async_native_runtime_executes_sleep_inside_loop_body() { let source = r#" async def main() -> i64 { - let ticks = 0; + let mut ticks = 0; while ticks < 3 { await sleep(1); ticks = ticks + 1; @@ -4224,6 +4274,7 @@ fn compile_and_run_example_with_args( relative_path: &str, extra_c_inputs: &[&str], args: &[&str], + strict_native: bool, ) -> Option { let source = super::expand_stdlib_imports_for_source(&read_example_source(relative_path)) .unwrap_or_else(|err| { @@ -4232,9 +4283,28 @@ fn compile_and_run_example_with_args( let llvm_ir = compile_source(&source, 1) .unwrap_or_else(|err| panic!("example {relative_path} should compile: {err}")); - let clang = find_clang()?; - let runtime_c = find_runtime_c()?; + let clang = find_clang().unwrap_or_else(|| { + if strict_native { + panic!("core conformance requires clang"); + } + String::new() + }); + if clang.is_empty() { + return None; + } + let runtime_c = find_runtime_c().unwrap_or_else(|| { + if strict_native { + panic!("core conformance requires the stdlib runtime sources"); + } + String::new() + }); + if runtime_c.is_empty() { + return None; + } if !stdlib_runtime_c_is_compilable(&clang, Path::new(&runtime_c)) { + if strict_native { + panic!("core conformance stdlib runtime sources must compile"); + } return None; } @@ -4243,17 +4313,23 @@ fn compile_and_run_example_with_args( let obj_ext = if cfg!(windows) { "obj" } else { "o" }; let main_obj = temp_artifact(&format!("examples-smoke-{tag}-main"), obj_ext); - if compile_ir_to_object(&clang, &ll_path, &main_obj, 1, None).is_err() { + if let Err(error) = compile_ir_to_object(&clang, &ll_path, &main_obj, 1, None) { let _ = fs::remove_file(&ll_path); let _ = fs::remove_file(&main_obj); + if strict_native { + panic!("example {relative_path} LLVM IR should compile: {error}"); + } return None; } let runtime_objects = match ensure_runtime_objects(&clang, &runtime_c, 1, None) { Ok(objects) => objects, - Err(_) => { + Err(error) => { let _ = fs::remove_file(&ll_path); let _ = fs::remove_file(&main_obj); + if strict_native { + panic!("example {relative_path} runtime objects should compile: {error}"); + } return None; } }; @@ -4270,20 +4346,21 @@ fn compile_and_run_example_with_args( ), obj_ext, ); - if compile_ir_to_object( + if let Err(error) = compile_ir_to_object( &clang, &workspace_root.join(extra_input), &extra_obj, 1, None, - ) - .is_err() - { + ) { let _ = fs::remove_file(&ll_path); let _ = fs::remove_file(&main_obj); for object in &extra_objects { let _ = fs::remove_file(object); } + if strict_native { + panic!("example {relative_path} extra input {extra_input} should compile: {error}"); + } return None; } object_paths.push(extra_obj.clone()); @@ -4294,13 +4371,18 @@ fn compile_and_run_example_with_args( &format!("examples-smoke-{tag}"), if cfg!(windows) { "exe" } else { "" }, ); - if link_native_binary_from_objects(&clang, &object_paths, &exe_path, None, None).is_err() { + if let Err(error) = + link_native_binary_from_objects(&clang, &object_paths, &exe_path, None, None) + { let _ = fs::remove_file(&ll_path); let _ = fs::remove_file(&main_obj); for object in &extra_objects { let _ = fs::remove_file(object); } let _ = fs::remove_file(&exe_path); + if strict_native { + panic!("example {relative_path} should link: {error}"); + } return None; } @@ -4353,7 +4435,8 @@ fn assert_example_output_with_c_inputs_and_args( args: &[&str], expected_stdout: &str, ) { - let Some(output) = compile_and_run_example_with_args(tag, relative_path, extra_c_inputs, args) + let Some(output) = + compile_and_run_example_with_args(tag, relative_path, extra_c_inputs, args, false) else { return; }; @@ -4370,6 +4453,156 @@ fn assert_example_output_with_c_inputs_and_args( ); } +#[test] +fn immutable_assignment_json_reports_stable_code_and_target_span() { + let source = r#" +def main() -> i64 { + let value = 1; + value = value + 1; + value +} +"#; + let error = sengoo_compiler::compile_to_ir(source).expect_err("assignment should fail"); + let location = super::location_from_compile_error(source, &error); + let json = super::render_compile_error_json_with_location( + Some("tests/immutable.sg"), + &error.to_string(), + location, + ); + let value: Value = serde_json::from_str(&json).expect("json payload should be valid"); + let target_lo = source.find("value = value").expect("assignment target") as u64; + + assert_eq!(value["stage"], "typecheck"); + assert_eq!(value["code"], "immutable-assignment"); + assert_eq!(value["location"]["span"]["lo"], target_lo); + assert_eq!( + value["location"]["span"]["hi"], + target_lo + "value".len() as u64 + ); +} + +fn assert_example_result( + tag: &str, + relative_path: &str, + expected_exit_code: i32, + expected_stdout: &str, +) { + let Some(output) = compile_and_run_example_with_args(tag, relative_path, &[], &[], true) else { + return; + }; + assert_eq!( + output.status.code(), + Some(expected_exit_code), + "{relative_path} exit mismatch: stdout={} stderr={}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&output.stdout).trim(), + expected_stdout, + "{relative_path} stdout mismatch" + ); +} + +#[test] +fn core_conformance_examples_compile_link_and_run() { + let cases = [ + ( + "core-scalars-control", + "examples/conformance/01_scalars_control.sg", + 9, + "core", + ), + ( + "core-recursion", + "examples/conformance/02_recursion.sg", + 13, + "", + ), + ("core-struct", "examples/08_struct.sg", 30, ""), + ("core-method", "examples/09_method_call.sg", 43, ""), + ("core-array-read", "examples/04_array.sg", 20, ""), + ("core-array-for", "examples/05_loop.sg", 15, ""), + ( + "core-array-write", + "examples/conformance/03_array_write.sg", + 42, + "", + ), + ("core-closure", "examples/06_lambda.sg", 15, ""), + ( + "core-closure-multi", + "examples/conformance/04_closure_multi_capture.sg", + 18, + "", + ), + ( + "core-enum-value", + "examples/ergonomics/03_enum_match.sg", + 2, + "", + ), + ( + "core-enum-payload", + "examples/conformance/05_enum_payload.sg", + 42, + "", + ), + ( + "core-enum-multi-payload", + "examples/conformance/06_enum_multi_payload.sg", + 42, + "", + ), + ]; + + for (tag, path, exit_code, stdout) in cases { + assert_example_result(tag, path, exit_code, stdout); + } +} + +#[test] +fn jit_enum_match_ir_is_accepted_by_clang() { + let Some(clang) = find_clang() else { + return; + }; + + let source = r#" +enum Maybe { Empty, Value(i64) } + +def main() -> i64 { + let value = Maybe::Value(42); + match value { + Maybe::Empty => 0, + Maybe::Value(inner) => inner, + } +} +"#; + let mir = compile_to_mir(source).expect("enum match should compile to MIR"); + let ir = JITCodegen::new() + .generate(&mir) + .expect("JIT enum match should generate LLVM IR"); + let ll_path = temp_artifact("jit-enum-match", "ll"); + let obj_path = temp_artifact("jit-enum-match", if cfg!(windows) { "obj" } else { "o" }); + fs::write(&ll_path, ir).expect("JIT IR should be writable"); + + let output = Command::new(&clang) + .arg("-c") + .arg(&ll_path) + .arg("-o") + .arg(&obj_path) + .output() + .expect("clang should run"); + + let _ = fs::remove_file(&ll_path); + let _ = fs::remove_file(&obj_path); + assert!( + output.status.success(), + "clang rejected JIT enum-match IR: {}", + String::from_utf8_lossy(&output.stderr) + ); +} + #[test] fn examples_catalog_lists_expanded_categories() { let examples = [ @@ -6808,8 +7041,8 @@ def main() -> i64 { map.insert(17, 30); let iter = map.iter(); - let item = iter.next(); - let total = 0; + let mut item = iter.next(); + let mut total = 0; while item.is_some { total = total + item.value; item = iter.next(); diff --git a/tools/sgfmt/src/expressions.rs b/tools/sgfmt/src/expressions.rs index e6bdc3d..a496726 100644 --- a/tools/sgfmt/src/expressions.rs +++ b/tools/sgfmt/src/expressions.rs @@ -29,8 +29,18 @@ impl Formatter { fn format_stmt(&self, stmt: &Stmt, indent: usize) -> String { match &stmt.kind { - StmtKind::Let { name, ty, value } => { - let mut s = format!("{}let {}", self.pad(indent), name.name); + StmtKind::Let { + name, + ty, + value, + is_mut, + } => { + let mut s = format!( + "{}let {}{}", + self.pad(indent), + if *is_mut { "mut " } else { "" }, + name.name + ); if let Some(ty) = ty { s.push_str(": "); s.push_str(&self.format_type(ty)); @@ -56,8 +66,13 @@ impl Formatter { fn format_stmt_inline(&self, stmt: &Stmt) -> String { match &stmt.kind { - StmtKind::Let { name, ty, value } => { - let mut s = format!("let {}", name.name); + StmtKind::Let { + name, + ty, + value, + is_mut, + } => { + let mut s = format!("let {}{}", if *is_mut { "mut " } else { "" }, name.name); if let Some(ty) = ty { s.push_str(": "); s.push_str(&self.format_type(ty)); diff --git a/tools/sgfmt/src/lib.rs b/tools/sgfmt/src/lib.rs index 3dee24d..d30c347 100644 --- a/tools/sgfmt/src/lib.rs +++ b/tools/sgfmt/src/lib.rs @@ -747,6 +747,13 @@ mod tests { assert_eq!(first, second); } + #[test] + fn preserves_mutable_local_bindings() { + let src = "def main()->i64{let mut value=1;value=value+1;value}"; + let formatted = format_test_source(src, FormatOptions::default()); + assert!(formatted.contains("let mut value = 1;")); + } + #[test] fn formats_keyword_logical_operators_idempotently() { let src = "def main() -> i64 {\nlet a = true;\nlet b = false;\nlet ok = a && !b || b;\nif ok { 0 } else { 1 }\n}"; diff --git a/tools/sglsp/src/diagnostics.rs b/tools/sglsp/src/diagnostics.rs index ea3681a..877f356 100644 --- a/tools/sglsp/src/diagnostics.rs +++ b/tools/sglsp/src/diagnostics.rs @@ -322,6 +322,9 @@ fn diagnostic_range_from_compile_error(content: &str, error: &CompileError) -> O match error { CompileError::ParseError(error) => diagnostic_range_from_parse_error(content, error), CompileError::TypeError(error) => diagnostic_range_from_type_error(content, error), + CompileError::TypeckError(error) => error + .span() + .map(|(lo, hi)| range_from_byte_span(content, lo, hi)), _ => None, } } @@ -1054,6 +1057,98 @@ async def main() -> i64 { assert_eq!(range.start.character, 8); } + #[test] + fn immutable_assignment_uses_typeck_code_and_exact_target_range() { + let src = r#" +def main() -> i64 { + let value = 1; + value = value + 1; + value +} +"#; + let diagnostics = embedded_compiler_diagnostics(src); + let target_lo = src.find("value = value").expect("assignment target") as u32; + let expected = range_from_byte_span(src, target_lo, target_lo + "value".len() as u32); + + assert_eq!(diagnostics.len(), 1); + assert_eq!(diagnostics[0].severity, Some(DiagnosticSeverity::ERROR)); + assert_eq!( + diagnostics[0].code, + Some(NumberOrString::String("immutable-assignment".to_string())) + ); + assert_eq!(diagnostics[0].range, expected); + assert!(diagnostics[0].message.contains("let mut")); + } + + #[test] + fn enum_value_errors_keep_stable_codes_and_precise_ranges() { + let cases = [ + ( + "enum Color { Red }\ndef main() -> Color { Color::Blue }\n", + "unknown-enum-variant", + "Color::Blue", + ), + ( + "enum Maybe { Value(i64) }\ndef main() -> Maybe { Maybe::Value() }\n", + "enum-variant-arity", + "Maybe::Value", + ), + ( + "enum Maybe { Value(i64) }\ndef main() -> Maybe { Maybe::Value(true) }\n", + "enum-variant-type", + "true", + ), + ]; + + for (src, code, target) in cases { + let diagnostics = embedded_compiler_diagnostics(src); + let lo = src.find(target).expect("diagnostic target") as u32; + let expected = range_from_byte_span(src, lo, lo + target.len() as u32); + + assert_eq!(diagnostics.len(), 1, "{code}"); + assert_eq!( + diagnostics[0].code, + Some(NumberOrString::String(code.to_string())) + ); + assert_eq!(diagnostics[0].range, expected, "{code}"); + } + } + + #[test] + fn array_and_closure_errors_keep_stable_codes_and_precise_ranges() { + let cases = [ + ( + "def main() -> i64 {\n let values = [1, 2, 3];\n values[3]\n}\n", + "array-index-out-of-bounds", + "3", + ), + ( + "def main() -> i64 {\n let values = [1, 2, 3];\n values[true]\n}\n", + "invalid-array-index", + "true", + ), + ( + "def main() -> i64 {\n let invalid = |value, value| value;\n invalid(1, 2)\n}\n", + "duplicate-closure-parameter", + "value|", + ), + ]; + + for (src, code, target) in cases { + let diagnostics = embedded_compiler_diagnostics(src); + let target_lo = src.rfind(target).expect("diagnostic target") as u32; + let target_len = target.trim_end_matches('|').len() as u32; + let expected = range_from_byte_span(src, target_lo, target_lo + target_len); + + assert_eq!(diagnostics.len(), 1, "{code}"); + assert_eq!( + diagnostics[0].code, + Some(NumberOrString::String(code.to_string())) + ); + assert_eq!(diagnostics[0].range, expected, "{code}"); + } + } + #[test] fn compiler_diagnostics_fall_back_to_embedded_compiler_when_sgc_is_missing() { let src = "def main() -> i64 {\n let = 1;\n}\n"; diff --git a/tools/stdlib/collections.sg b/tools/stdlib/collections.sg index aaebbe2..98b777b 100644 --- a/tools/stdlib/collections.sg +++ b/tools/stdlib/collections.sg @@ -354,8 +354,8 @@ impl VecIter { } def filter_with(self, pred: fn(bool) -> bool) -> Option { - let found = 0; - let matched = false; + let mut found = 0; + let mut matched = false; while sengoo_vec_iter_done_i64(self.handle) == 0 { if found != 0 { break; @@ -392,8 +392,8 @@ impl Iterator for VecIter { } def filter_even(self) -> Option { - let found = 0; - let matched = 0; + let mut found = 0; + let mut matched = 0; while sengoo_vec_iter_done_i64(self.handle) == 0 { if found != 0 { break; @@ -421,8 +421,8 @@ impl Iterator for VecIter { } def filter_with(self, pred: fn(i64) -> i64) -> Option { - let found = 0; - let matched = 0; + let mut found = 0; + let mut matched = 0; while sengoo_vec_iter_done_i64(self.handle) == 0 { if found != 0 { break; @@ -592,8 +592,8 @@ impl HashMapIter { } def filter_with(self, pred: fn(bool) -> bool) -> Option { - let found = 0; - let matched = false; + let mut found = 0; + let mut matched = false; while sengoo_hashmap_iter_done_i64(self.handle) == 0 { if found != 0 { break; @@ -631,8 +631,8 @@ impl Iterator for HashMapIter { } def filter_with(self, pred: fn(i64) -> i64) -> Option { - let found = 0; - let matched = 0; + let mut found = 0; + let mut matched = 0; while sengoo_hashmap_iter_done_i64(self.handle) == 0 { if found != 0 { break; diff --git a/tools/stdlib/string.sg b/tools/stdlib/string.sg index 3824f6d..09d8be4 100644 --- a/tools/stdlib/string.sg +++ b/tools/stdlib/string.sg @@ -66,8 +66,8 @@ def str_append(lhs: &str, rhs: &str) -> &str { } def str_repeat(value: &str, count: i64) -> &str { - let out = ""; - let remaining = count; + let mut out = ""; + let mut remaining = count; while remaining > 0 { out = str_concat(out, value); remaining = remaining - 1; From 039b33bb222ad3d708c8470110c72a8a2c0ee666 Mon Sep 17 00:00:00 2001 From: Hyper66666 <2247081184@qq.com> Date: Wed, 10 Jun 2026 18:05:02 +0800 Subject: [PATCH 2/7] Use the published OpenSpec CLI in GitHub Actions The unscoped openspec package is a placeholder without an executable, so both core conformance and realworld validation now invoke the versioned official CLI package directly. Constraint: GitHub-hosted runners do not have the repository developer's global OpenSpec installation. Rejected: Install the unscoped openspec package | npm publishes it as 0.0.0 without a CLI binary. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Pin OpenSpec workflow invocations to a verified @fission-ai/openspec release. Tested: npx --yes @fission-ai/openspec@1.4.1 validate core-language-correctness --strict; npx --yes @fission-ai/openspec@1.4.1 validate package-release-defaults --strict; git diff --check. --- .github/workflows/core-conformance.yml | 2 +- .github/workflows/realworld-e2e.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/core-conformance.yml b/.github/workflows/core-conformance.yml index 793e8e4..52eabb8 100644 --- a/.github/workflows/core-conformance.yml +++ b/.github/workflows/core-conformance.yml @@ -23,7 +23,7 @@ jobs: run: sudo apt-get update && sudo apt-get install -y clang - name: Validate OpenSpec - run: npx --yes openspec validate core-language-correctness --strict + run: npx --yes @fission-ai/openspec@1.4.1 validate core-language-correctness --strict - name: Run core conformance examples run: cargo test --locked -p sgc core_conformance_examples_compile_link_and_run -- --nocapture diff --git a/.github/workflows/realworld-e2e.yml b/.github/workflows/realworld-e2e.yml index cd6d85a..eecb038 100644 --- a/.github/workflows/realworld-e2e.yml +++ b/.github/workflows/realworld-e2e.yml @@ -47,7 +47,7 @@ jobs: run: cargo test -p sengoo-runtime net::tls -- --nocapture - name: Validate OpenSpec child change - run: npx --yes openspec validate package-release-defaults --strict + run: npx --yes @fission-ai/openspec@1.4.1 validate package-release-defaults --strict continue-on-error: true graphics-package-smoke: From 7258e615bd54f5568b4b65df74d36f1d1871a5dc Mon Sep 17 00:00:00 2001 From: Hyper66666 <2247081184@qq.com> Date: Wed, 10 Jun 2026 18:56:02 +0800 Subject: [PATCH 3/7] Make conformance gates portable and deterministic Linux exposed host-specific snapshot and cfg assumptions, while the production benchmark still generated mutable Sengoo locals without `mut`. Snapshot output now normalizes only the host triple, cfg coverage uses a platform-independent false predicate, and generated benchmark programs follow the explicit mutability contract. The workspace CI gate is serialized because the native network error ABI is intentionally process-global. Constraint: Preserve process-visible network error semantics across threads. Rejected: Make LAST_NET_ERROR thread-local | contradicts the existing public runtime contract and its cross-thread test. Rejected: Rebaseline snapshots separately per host | duplicates snapshots for one non-semantic target-triple line. Confidence: high Scope-risk: moderate Reversibility: clean Directive: Generated Sengoo fixtures that assign locals must declare them with `let mut`; snapshot tests must not record host triples verbatim. Tested: cargo test --workspace --locked -- --test-threads=1; cargo clippy --workspace --all-targets --locked -- -D warnings; cargo fmt --all -- --check; focused snapshot/cfg tests; generated benchmark fixture validation; OpenSpec strict validation. Not-tested: Full local advanced benchmark exceeded 30 minutes after progressing beyond the previous failure into reachability measurement; GitHub production gate pending. --- .github/workflows/core-conformance.yml | 3 +-- bench/advanced_pipeline_bench.py | 20 ++++++++-------- compiler/src/parser/attribute_expander.rs | 6 ++--- compiler/src/tests/snapshot_tests.rs | 24 ++++++++++++++++++- ...ts__snapshot_tests__snapshot_01_hello.snap | 2 +- ...napshot_tests__snapshot_02_arithmetic.snap | 2 +- ...snapshot_tests__snapshot_03_variables.snap | 2 +- ...ts__snapshot_tests__snapshot_04_array.snap | 2 +- ...sts__snapshot_tests__snapshot_05_loop.snap | 2 +- ...s__snapshot_tests__snapshot_06_lambda.snap | 2 +- ...tests__snapshot_tests__snapshot_07_if.snap | 2 +- ...s__snapshot_tests__snapshot_08_struct.snap | 2 +- ...apshot_tests__snapshot_09_method_call.snap | 2 +- ...s__snapshot_tests__snapshot_09_simple.snap | 2 +- .../core-language-correctness/VERIFICATION.md | 7 ++++-- 15 files changed, 52 insertions(+), 28 deletions(-) diff --git a/.github/workflows/core-conformance.yml b/.github/workflows/core-conformance.yml index 52eabb8..041b680 100644 --- a/.github/workflows/core-conformance.yml +++ b/.github/workflows/core-conformance.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - codex/** jobs: core-language: @@ -29,4 +28,4 @@ jobs: run: cargo test --locked -p sgc core_conformance_examples_compile_link_and_run -- --nocapture - name: Run workspace tests - run: cargo test --workspace --locked + run: cargo test --workspace --locked -- --test-threads=1 diff --git a/bench/advanced_pipeline_bench.py b/bench/advanced_pipeline_bench.py index 5351274..9c71bb8 100644 --- a/bench/advanced_pipeline_bench.py +++ b/bench/advanced_pipeline_bench.py @@ -643,8 +643,8 @@ def mix(x: i64) -> i64 {{ }} def calc(n: i64) -> i64 {{ - let i = 0 - let acc = 0 + let mut i = 0 + let mut acc = 0 while i < n {{ {body} i = i + 1 @@ -669,8 +669,8 @@ def mix(x: i64) -> i64 { } def calc(n: i64) -> i64 { - let i = 0 - let acc = 0 + let mut i = 0 + let mut acc = 0 while i < n { acc = acc + mix(i) i = i + 1 @@ -693,8 +693,8 @@ def mix(x: i64, scale: i64) -> i64 { } def calc(n: i64) -> i64 { - let i = 0 - let acc = 0 + let mut i = 0 + let mut acc = 0 while i < n { acc = acc + mix(i, 2) i = i + 1 @@ -728,8 +728,8 @@ def mix(x: i64) -> i64 {{ }} {extra} def calc(n: i64) -> i64 {{ - let i = 0 - let acc = 0 + let mut i = 0 + let mut acc = 0 while i < n {{ {use_extra} i = i + 1 @@ -1303,8 +1303,8 @@ def make_scale_source_sengoo(target_loc: int) -> str: lines.extend( [ "def main() -> i64 {", - " let acc = 0", - " let i = 0", + " let mut acc = 0", + " let mut i = 0", " while i < 1000 {", f" acc = acc + f{fn_count - 1}(i)", " i = i + 1", diff --git a/compiler/src/parser/attribute_expander.rs b/compiler/src/parser/attribute_expander.rs index cc32f9a..71e08c8 100644 --- a/compiler/src/parser/attribute_expander.rs +++ b/compiler/src/parser/attribute_expander.rs @@ -1002,13 +1002,13 @@ def main() -> i64 {{ #[test] fn cfg_false_removes_declaration() { let source = r#" -#[cfg(target_os = "linux")] -struct LinuxOnly {} +#[cfg(all(target_os = "windows", target_os = "linux"))] +struct ImpossibleTarget {} struct Always {} "#; let processed = process_surface_attributes(source).expect("cfg filter should succeed"); - assert!(!processed.contains("LinuxOnly")); + assert!(!processed.contains("ImpossibleTarget")); assert!(processed.contains("Always")); } diff --git a/compiler/src/tests/snapshot_tests.rs b/compiler/src/tests/snapshot_tests.rs index aad564c..b69e409 100644 --- a/compiler/src/tests/snapshot_tests.rs +++ b/compiler/src/tests/snapshot_tests.rs @@ -12,11 +12,33 @@ use crate::compile_to_ir; /// On success returns the LLVM IR; on error returns the error description. fn compile_snapshot(source: &str) -> String { match compile_to_ir(source) { - Ok(ir) => ir, + Ok(ir) => normalize_host_target_triple(ir), Err(e) => format!("COMPILE ERROR: {}", e), } } +fn normalize_host_target_triple(mut ir: String) -> String { + const PREFIX: &str = "target triple = \""; + let Some(start) = ir.find(PREFIX) else { + return ir; + }; + let value_start = start + PREFIX.len(); + let Some(value_len) = ir[value_start..].find('"') else { + return ir; + }; + ir.replace_range(value_start..value_start + value_len, ""); + ir +} + +#[test] +fn snapshot_normalization_hides_only_the_host_triple() { + let ir = "target triple = \"x86_64-unknown-linux-gnu\"\ndefine i64 @main()"; + assert_eq!( + normalize_host_target_triple(ir.to_string()), + "target triple = \"\"\ndefine i64 @main()" + ); +} + #[test] fn snapshot_01_hello() { let source = include_str!("../../../examples/01_hello.sg"); diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_01_hello.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_01_hello.snap index fbeba54..5cd7af4 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_01_hello.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_01_hello.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_02_arithmetic.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_02_arithmetic.snap index 6970b27..63a2eab 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_02_arithmetic.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_02_arithmetic.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_03_variables.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_03_variables.snap index 33bd42f..265c458 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_03_variables.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_03_variables.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_04_array.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_04_array.snap index 8cc669f..298d757 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_04_array.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_04_array.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_05_loop.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_05_loop.snap index 7f4933f..d0104ff 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_05_loop.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_05_loop.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_06_lambda.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_06_lambda.snap index a9a7329..562918d 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_06_lambda.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_06_lambda.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_07_if.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_07_if.snap index 19fb82e..3a274e1 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_07_if.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_07_if.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_08_struct.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_08_struct.snap index 2f62558..eae76f7 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_08_struct.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_08_struct.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_method_call.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_method_call.snap index 1981461..0ea5517 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_method_call.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_method_call.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_simple.snap b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_simple.snap index fbeba54..5cd7af4 100644 --- a/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_simple.snap +++ b/compiler/src/tests/snapshots/sengoo_compiler__tests__snapshot_tests__snapshot_09_simple.snap @@ -5,7 +5,7 @@ expression: output ; Sengoo LLVM IR ; Generated by Sengoo Compiler -target triple = "x86_64-pc-windows-msvc" +target triple = "" ; External C library functions declare i32 @puts(i8*) diff --git a/openspec/changes/core-language-correctness/VERIFICATION.md b/openspec/changes/core-language-correctness/VERIFICATION.md index 275a8e2..ad3a90d 100644 --- a/openspec/changes/core-language-correctness/VERIFICATION.md +++ b/openspec/changes/core-language-correctness/VERIFICATION.md @@ -59,8 +59,11 @@ served as the failing implementation baseline. ## Local Full-Workspace Gate - `cargo clippy --workspace --all-targets --locked -- -D warnings` passes. -- `cargo test --workspace --locked` passes after the async/JIT enum review - fixes; the compiler library reports 699 passing tests in the final run. +- `cargo test --workspace --locked -- --test-threads=1` passes after the + async/JIT enum review fixes. The single-thread setting is required because + the native networking ABI intentionally exposes one process-wide + `LAST_NET_ERROR`; parallel network tests can otherwise overwrite each + other's asserted error code. An independent read-only review found and drove regression coverage for two adjacent enum paths: payloadless enum values crossing `await`, and the public From 894063c061e9db4635b68610d2be2383b748f582 Mon Sep 17 00:00:00 2001 From: Hyper66666 <2247081184@qq.com> Date: Wed, 10 Jun 2026 18:58:17 +0800 Subject: [PATCH 4/7] Avoid duplicate feature-branch CI executions Pull requests already validate every feature-branch update, so direct push triggers are now reserved for main. This keeps the expensive compile-scale and cross-platform realworld gates from running twice for the same commit. Constraint: Preserve both pre-merge PR validation and post-merge main validation. Rejected: Keep codex branch push triggers | every synchronized PR update schedules duplicate jobs. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Add feature-branch push filters only for workflows that do not also listen to pull_request. Tested: git diff --check; workflow trigger structure reviewed against core-conformance.yml. Not-tested: actionlint is not installed locally; GitHub Actions parser pending. --- .github/workflows/perf-smoke.yml | 1 - .github/workflows/realworld-e2e.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/perf-smoke.yml b/.github/workflows/perf-smoke.yml index 3d77162..a15ee2a 100644 --- a/.github/workflows/perf-smoke.yml +++ b/.github/workflows/perf-smoke.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - codex/** jobs: compile-scale-production-gate: diff --git a/.github/workflows/realworld-e2e.yml b/.github/workflows/realworld-e2e.yml index eecb038..d610151 100644 --- a/.github/workflows/realworld-e2e.yml +++ b/.github/workflows/realworld-e2e.yml @@ -2,7 +2,7 @@ name: realworld-e2e on: push: - branches: [main, codex/**] + branches: [main] pull_request: jobs: From dc20b4a70b9f431bc33964f8141e6b4752848534 Mon Sep 17 00:00:00 2001 From: Hyper66666 <2247081184@qq.com> Date: Wed, 10 Jun 2026 19:44:38 +0800 Subject: [PATCH 5/7] Satisfy the native async dispatch link contract on Unix Rust's native runtime staticlib groups scalar select entry points into one archive member, so Unix linkers require every generated result-dispatch symbol once any async runtime path pulls that member in. Async lowering now emits the complete scalar dispatch surface for every async program, with a regression test covering the symbol contract. Constraint: The runtime staticlib is linked with compiler-generated dispatch functions. Rejected: Make runtime dispatch symbols optional | the native select ABI directly references the generated extern symbols. Rejected: Depend on Windows linker behavior | GNU and LLD correctly expose the incomplete symbol surface. Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep the generated async dispatch symbol family aligned with runtime/src/async_runtime/select.rs. Tested: red/green compiler dispatch-surface regression; cargo test --workspace --locked -- --test-threads=1; cargo clippy --workspace --all-targets --locked -- -D warnings; Windows native async sleep execution. Not-tested: Linux native link path pending GitHub Actions. --- compiler/src/mir/async_lowering.rs | 5 ++++- compiler/src/tests/async_tests.rs | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/compiler/src/mir/async_lowering.rs b/compiler/src/mir/async_lowering.rs index 1265521..e1fa719 100644 --- a/compiler/src/mir/async_lowering.rs +++ b/compiler/src/mir/async_lowering.rs @@ -191,7 +191,10 @@ pub fn expand_async_functions( _ => false, }) }); - if needs_select_runtime { + // The native Rust staticlib groups all scalar select entry points into one + // archive member. Unix linkers therefore require every dispatch symbol once + // any async runtime entry point pulls that member into the executable. + if needs_select_runtime || !spawn_dispatch_entries.is_empty() { for (suffix, return_ty) in [ ("bool", MIR_BOOL), ("i8", MIRType::Int(8)), diff --git a/compiler/src/tests/async_tests.rs b/compiler/src/tests/async_tests.rs index 2506d99..9ab88b9 100644 --- a/compiler/src/tests/async_tests.rs +++ b/compiler/src/tests/async_tests.rs @@ -91,6 +91,30 @@ async def main() -> i64 { ); } +#[test] +fn async_program_synthesizes_complete_native_result_dispatch_surface() { + let source = r#" +async def main() -> i64 { + await sleep(1); + 42 +} +"#; + + let mir_fns = compile_to_mir(source).expect("async source should lower to MIR"); + let names = mir_fns + .iter() + .map(|function| function.name.as_str()) + .collect::>(); + + for suffix in ["bool", "i8", "i16", "i32", "i64", "f32", "f64"] { + let expected = format!("sengoo_async_result_dispatch_{suffix}"); + assert!( + names.contains(expected.as_str()), + "native async runtime link surface is missing `{expected}`" + ); + } +} + #[test] fn async_frame_rejects_payload_enum_local_crossing_await_before_codegen() { let source = r#" From f4c24c65fb4f71b1828a5d9fa38b83729ce6eba3 Mon Sep 17 00:00:00 2001 From: Hyper66666 <2247081184@qq.com> Date: Wed, 10 Jun 2026 20:06:43 +0800 Subject: [PATCH 6/7] Honor the SysV ABI for async aggregate results Linux exposed a native channel receive failure after the async dispatch link surface was repaired. The Rust runtime returns 24-byte C-layout outcomes through a hidden result pointer under SysV, while generated LLVM IR incorrectly treated them as direct register returns. Codegen now applies the supported target ABI consistently to both declarations and calls. Constraint: Compiler-generated LLVM declarations must match the Rust extern C ABI used by the native runtime. Rejected: Relax the channel result assertion | the observed value was an ABI decode failure, not nondeterministic channel behavior. Rejected: Force every non-Windows aggregate through sret | the 16-byte send outcome is returned directly under SysV. Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep async result declaration and call ABI decisions centralized in async_result_uses_sret. Tested: clang x86_64 SysV ABI probe; Linux-target LLVM declaration/call regression; ABI rule unit test; cargo test --workspace --locked -- --test-threads=1; cargo clippy --workspace --all-targets --locked -- -D warnings; Windows channel and sleep native runtime tests. Not-tested: End-to-end Linux native runtime execution pending GitHub Actions. --- compiler/src/codegen/declaration_helpers.rs | 57 ++++++++++++++++-- compiler/src/codegen/instruction_helpers.rs | 15 ++--- compiler/src/tests/async_tests.rs | 67 +++++++++++++++++++++ 3 files changed, 124 insertions(+), 15 deletions(-) diff --git a/compiler/src/codegen/declaration_helpers.rs b/compiler/src/codegen/declaration_helpers.rs index 9eb4c19..526edba 100644 --- a/compiler/src/codegen/declaration_helpers.rs +++ b/compiler/src/codegen/declaration_helpers.rs @@ -330,7 +330,10 @@ impl Codegen { self.declarations .push_str("declare i64 @sengoo_async_timeout_cancel_i64__poll(i64)\n"); self.declarations.push_str(Self::sret_or_direct_decl( - self.targets_windows_msvc(), + Self::async_result_uses_sret( + self.targets_windows_msvc(), + "sengoo_async_timeout_cancel_i64__result", + ), "sengoo_async_timeout_cancel_i64__result", "{ i1, i64, i64 }", )); @@ -422,7 +425,10 @@ impl Codegen { ( "result", Self::sret_or_direct_decl( - targets_windows_msvc, + Self::async_result_uses_sret( + targets_windows_msvc, + "sengoo_async_channel_send_i64__result", + ), "sengoo_async_channel_send_i64__result", "{ i1, i64 }", ), @@ -449,7 +455,10 @@ impl Codegen { ( "result", Self::sret_or_direct_decl( - targets_windows_msvc, + Self::async_result_uses_sret( + targets_windows_msvc, + "sengoo_async_channel_recv_i64__result", + ), "sengoo_async_channel_recv_i64__result", "{ i1, i64, i64 }", ), @@ -476,7 +485,10 @@ impl Codegen { ( "result", Self::sret_or_direct_decl( - targets_windows_msvc, + Self::async_result_uses_sret( + targets_windows_msvc, + "sengoo_async_mutex_lock_i64__result", + ), "sengoo_async_mutex_lock_i64__result", "{ i1, i64, i64 }", ), @@ -511,6 +523,27 @@ impl Codegen { } } + pub(super) fn async_result_uses_sret(targets_windows_msvc: bool, func: &str) -> bool { + if targets_windows_msvc { + return matches!( + func, + "sengoo_async_timeout_cancel_i64__result" + | "sengoo_async_channel_send_i64__result" + | "sengoo_async_channel_recv_i64__result" + | "sengoo_async_mutex_lock_i64__result" + ); + } + + // SysV returns aggregates larger than two eightbytes through a hidden + // result pointer. The send outcome is only 16 bytes and remains direct. + matches!( + func, + "sengoo_async_timeout_cancel_i64__result" + | "sengoo_async_channel_recv_i64__result" + | "sengoo_async_mutex_lock_i64__result" + ) + } + fn sret_or_direct_decl(use_sret: bool, func: &str, ret_ty: &str) -> &'static str { if !use_sret { return match func { @@ -621,4 +654,20 @@ mod tests { let needle = "declare i64 @sengoo_async_select_winner(i64, i64, i64, i64)\n"; assert_eq!(cg.declarations.matches(needle).count(), 1); } + + #[test] + fn async_result_sret_rules_match_supported_native_abis() { + assert!(Codegen::async_result_uses_sret( + false, + "sengoo_async_channel_recv_i64__result" + )); + assert!(!Codegen::async_result_uses_sret( + false, + "sengoo_async_channel_send_i64__result" + )); + assert!(Codegen::async_result_uses_sret( + true, + "sengoo_async_channel_send_i64__result" + )); + } } diff --git a/compiler/src/codegen/instruction_helpers.rs b/compiler/src/codegen/instruction_helpers.rs index 37d4e60..22bedb3 100644 --- a/compiler/src/codegen/instruction_helpers.rs +++ b/compiler/src/codegen/instruction_helpers.rs @@ -566,7 +566,7 @@ impl Codegen { } else if ret_ty == "void" { self.ir .push_str(&format!("call void {}({})\n", callee, arg_strs.join(", "))); - } else if self.uses_windows_sret_async_result(func, dest_ty) { + } else if self.uses_sret_async_result(func, dest_ty) { let sret_slot = format!("{dest}.sret"); self.ir .push_str(&format!("{sret_slot} = alloca {ret_ty}\n")); @@ -1093,15 +1093,8 @@ impl Codegen { }) } - fn uses_windows_sret_async_result(&self, func: &str, dest_ty: &MIRType) -> bool { - self.targets_windows_msvc() - && matches!(dest_ty, MIRType::Struct { .. }) - && matches!( - func, - "sengoo_async_timeout_cancel_i64__result" - | "sengoo_async_channel_send_i64__result" - | "sengoo_async_channel_recv_i64__result" - | "sengoo_async_mutex_lock_i64__result" - ) + fn uses_sret_async_result(&self, func: &str, dest_ty: &MIRType) -> bool { + matches!(dest_ty, MIRType::Struct { .. }) + && Self::async_result_uses_sret(self.targets_windows_msvc(), func) } } diff --git a/compiler/src/tests/async_tests.rs b/compiler/src/tests/async_tests.rs index 9ab88b9..c0321f5 100644 --- a/compiler/src/tests/async_tests.rs +++ b/compiler/src/tests/async_tests.rs @@ -1,4 +1,5 @@ use crate::ast::DeclKind; +use crate::codegen::{Codegen, FfiCodegenConfig}; use crate::mir::{Instruction, LocalKind}; use crate::CompileError; use crate::{compile_to_ir, compile_to_mir, Parser}; @@ -115,6 +116,72 @@ async def main() -> i64 { } } +#[test] +fn linux_sysv_async_three_field_results_use_sret_for_declaration_and_call() { + let result_ty = crate::mir::MIRType::Struct { + name: "ChannelRecvOutcome".to_string(), + fields: vec![ + ("is_ok".to_string(), crate::mir::MIR_BOOL), + ("value".to_string(), crate::mir::MIR_I64), + ("error".to_string(), crate::mir::MIR_I64), + ], + }; + let mut function = + crate::mir::MirFunction::new("probe".to_string(), vec![], crate::mir::MIR_I64); + let handle = function.add_local(LocalKind::Temp, crate::mir::MIR_I64); + let outcome = function.add_local(LocalKind::Temp, result_ty); + let return_value = function.add_local(LocalKind::Temp, crate::mir::MIR_I64); + let entry = function.start_block; + function.push_inst_to_block( + entry, + Instruction::Assign { + destination: handle, + value: crate::mir::MirConstant::Int(1), + }, + ); + function.push_inst_to_block( + entry, + Instruction::Call { + destination: outcome, + func: "sengoo_async_channel_recv_i64__result".to_string(), + args: vec![handle], + }, + ); + function.push_inst_to_block( + entry, + Instruction::Assign { + destination: return_value, + value: crate::mir::MirConstant::Int(0), + }, + ); + function + .block_mut(entry) + .expect("entry block should exist") + .set_terminator(crate::mir::Terminator::Return(Some(return_value))); + + let mut codegen = Codegen::with_ffi_and_target( + FfiCodegenConfig::default(), + Some("x86_64-unknown-linux-gnu".to_string()), + ); + let ir = codegen + .codegen(&[function]) + .expect("ABI probe should lower to LLVM IR"); + let result_llvm_ty = "{ i1, i64, i64 }"; + + assert!( + ir.contains(&format!( + "declare void @sengoo_async_channel_recv_i64__result({result_llvm_ty}* sret({result_llvm_ty}) align 8, i64)" + )), + "Linux SysV declaration must use an sret pointer for the 24-byte result:\n{ir}" + ); + assert!( + ir.contains( + "call void @sengoo_async_channel_recv_i64__result(%ChannelRecvOutcome* sret(%ChannelRecvOutcome) align 8" + ), + "Linux SysV call must use the same sret ABI as the runtime:\n{ir}" + ); +} + #[test] fn async_frame_rejects_payload_enum_local_crossing_await_before_codegen() { let source = r#" From f9257cf677947bf1f6f774cd76f157f6ebade534 Mon Sep 17 00:00:00 2001 From: Hyper66666 <2247081184@qq.com> Date: Wed, 10 Jun 2026 20:15:36 +0800 Subject: [PATCH 7/7] Close the core correctness acceptance record with Linux evidence The conformance and deterministic workspace gates now pass on the final Linux implementation, including the native async dispatch and SysV aggregate-result repairs discovered by CI. The change checklist and verification record now point to the exact successful runs and distinguish the repository's separate performance-baseline calibration issue. Constraint: Archive claims require reproducible Linux evidence, not only local Windows results. Rejected: Mark the performance baseline workflow as part of this change | the proposal explicitly excludes performance targets and its absolute budgets are already satisfied. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Preserve the linked GitHub Actions runs when archiving this change. Tested: openspec validate core-language-correctness --strict; openspec validate --all --strict (25/25); @fission-ai/openspec 1.4.1 strict validation; GitHub core-conformance run 27275073812; GitHub realworld-e2e run 27275073863. Not-tested: Repository-wide hosted-runner performance baseline calibration remains outside this correctness change. --- .../core-language-correctness/VERIFICATION.md | 25 +++++++++++++++++-- .../core-language-correctness/tasks.md | 18 ++++++------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/openspec/changes/core-language-correctness/VERIFICATION.md b/openspec/changes/core-language-correctness/VERIFICATION.md index ad3a90d..b4010ce 100644 --- a/openspec/changes/core-language-correctness/VERIFICATION.md +++ b/openspec/changes/core-language-correctness/VERIFICATION.md @@ -6,7 +6,7 @@ - Rust: `rustc 1.94.0` - Cargo: `cargo 1.94.0` - Clang: `19.1.7` -- OpenSpec CLI: `1.3.1` +- OpenSpec CLI: local `1.3.1`; CI `@fission-ai/openspec 1.4.1` ## Baseline Reconciliation @@ -74,6 +74,27 @@ executable regression tests. The final independent follow-up review reported no remaining P0, P1, or P2 findings. +## Linux CI Evidence + +- GitHub Actions core-conformance run + [`27275073812`](https://github.com/Hyper66666/Sengoo/actions/runs/27275073812) + passed on Linux. Its OpenSpec validation, core conformance examples, and + deterministic full-workspace test steps all completed successfully. +- GitHub Actions realworld-e2e run + [`27275073863`](https://github.com/Hyper66666/Sengoo/actions/runs/27275073863) + passed the realworld and graphics package jobs on both Ubuntu and Windows. +- Linux CI exposed two adjacent native async ABI defects while exercising the + full workspace. The compiler now emits the complete scalar result-dispatch + symbol family for async programs and uses the SysV hidden-result-pointer ABI + for 24-byte channel, mutex, and timeout outcomes. Both defects have compiler + regression tests, and the repaired channel round trip passed in the final + Linux run. +- The repository-wide `perf-smoke` workflow is not an acceptance criterion for + this correctness-only change. Its hosted Windows runner satisfies the + absolute time and RSS budgets but is still compared against a frozen, + machine-specific 10% regression baseline; that existing performance-gate + calibration remains separate work. + ## Dependency Decisions - `inkwell` and `llvm-sys` were removed because the workspace emits textual LLVM @@ -81,4 +102,4 @@ findings. - `pyo3` remains a workspace dependency because `sengoo-runtime` consumes it through the optional `python` feature. -The final full-workspace and Linux CI results are recorded by the PR checks. +The final full-workspace and Linux CI results are recorded above and by PR #13. diff --git a/openspec/changes/core-language-correctness/tasks.md b/openspec/changes/core-language-correctness/tasks.md index 5833bf1..0d5694e 100644 --- a/openspec/changes/core-language-correctness/tasks.md +++ b/openspec/changes/core-language-correctness/tasks.md @@ -73,9 +73,9 @@ ## 7. Verification -- [ ] 7.1 Conformance harness: every v1 core form compiles, runs, and asserts the +- [x] 7.1 Conformance harness: every v1 core form compiles, runs, and asserts the expected result on Linux. -- [ ] 7.2 `cargo test` is green on Linux (failing cases fixed or rebaselined with +- [x] 7.2 `cargo test` is green on Linux (failing cases fixed or rebaselined with a recorded reason). - [x] 7.3 `sgc check` / JSON diagnostic snapshots for representative accepted and rejected core forms. @@ -83,15 +83,15 @@ ## Archive Gate -- [ ] `openspec validate core-language-correctness --strict` passes. -- [ ] Every v1 core-conformance form has a committed example and an executable +- [x] `openspec validate core-language-correctness --strict` passes. +- [x] Every v1 core-conformance form has a committed example and an executable test that asserts its result. -- [ ] Array indexing/iteration, closure capture, `let mut`, immutable-assignment +- [x] Array indexing/iteration, closure capture, `let mut`, immutable-assignment rejection, and enum-value construction all compile to valid IR and run correctly. -- [ ] `cargo test` is green on Linux, with any rebaselined snapshots reviewed. -- [ ] `Cargo.lock` and `rust-toolchain.toml` are committed and the conformance +- [x] `cargo test` is green on Linux, with any rebaselined snapshots reviewed. +- [x] `Cargo.lock` and `rust-toolchain.toml` are committed and the conformance gate runs in CI. -- [ ] Docs and `PROGRESS.md` match verified behavior; unused backend deps are +- [x] Docs and `PROGRESS.md` match verified behavior; unused backend deps are removed or justified. -- [ ] Any source-incompatible mutability cleanup has an accepted migration note. +- [x] Any source-incompatible mutability cleanup has an accepted migration note.