From ec6c7edf8d6fccef0af75d71ff8cbe679bce06b1 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sun, 31 May 2026 21:42:23 +0200 Subject: [PATCH 01/14] unpin Zig version --- .github/workflows/artifacts.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/main.yml | 2 +- README.md | 2 +- build.zig | 13 ------------- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index 3c93d190de..af2773a082 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -39,7 +39,7 @@ jobs: - uses: mlugg/setup-zig@v2 if: env.SKIP_DEPLOY != 'true' with: - version: "0.17.0-dev.387+31f157d80" # https://github.com/zigtools/zls/issues/3208 + version: master - name: Install APT packages if: env.SKIP_DEPLOY != 'true' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 69bb16a5a4..433d25f3c3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -16,7 +16,7 @@ jobs: - uses: mlugg/setup-zig@v2 with: - version: "0.17.0-dev.387+31f157d80" # https://github.com/zigtools/zls/issues/3208 + version: master - name: Install kcov run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2d1a556e96..6407ff3647 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,7 +20,7 @@ jobs: - uses: mlugg/setup-zig@v2 with: - version: "0.17.0-dev.387+31f157d80" # https://github.com/zigtools/zls/issues/3208 + version: master - name: Run zig fmt if: matrix.os == 'ubuntu-22.04' diff --git a/README.md b/README.md index 03a90dbb9f..9dd2c5a86f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The complete installation guide is available on the [Zigtools website](https://z ### Build From Source -The default branch of ZLS targets `0.17.0-dev.387+31f157d80` until [#3208](https://github.com/zigtools/zls/issues/3208) has been resolved. +The default branch of ZLS targets [Zig master](https://ziglang.org/download/). ```bash git clone https://github.com/zigtools/zls diff --git a/build.zig b/build.zig index 7b2dac45f7..278448caeb 100644 --- a/build.zig +++ b/build.zig @@ -601,19 +601,6 @@ fn release(b: *Build, release_artifacts: []const *Build.Step.Compile, released_z const Build = blk: { @setEvalBranchQuota(10_000); - { - const version = std.SemanticVersion.parse("0.17.0-dev.601+0ff175b69") catch unreachable; - if (builtin.zig_version.order(version) != .lt) { - const message = std.fmt.comptimePrint( - \\The used Zig version ({s}) is not yet supported by ZLS. - \\ - \\For more information please visit the following issue: - \\> https://github.com/zigtools/zls/issues/3208 - , .{builtin.zig_version_string}); - @compileError(message); - } - } - const min_build_zig = std.SemanticVersion.parse(minimum_build_zig_version) catch unreachable; const min_runtime_zig = std.SemanticVersion.parse(minimum_runtime_zig_version) catch unreachable; From 3df5a93f7fdd744a2b742d1c56ec1558916c4a90 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 3 Sep 2026 22:52:02 +0200 Subject: [PATCH 02/14] set minimum zig version to `0.17.0-dev.1936+5a625d5f3` --- build.zig | 7 ++----- build.zig.zon | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build.zig b/build.zig index 278448caeb..b3f0a6f38f 100644 --- a/build.zig +++ b/build.zig @@ -5,11 +5,8 @@ const zls_version = std.SemanticVersion.parse(@import("build.zig.zon").version) const minimum_build_zig_version = @import("build.zig.zon").minimum_zig_version; -/// Specify the minimum Zig version that is usable with ZLS: -/// `std.builtin` -> `std.lang` migration progress -/// -/// A breaking change to the Zig Build System should be handled by updating ZLS's build runner (see src\build_runner) -const minimum_runtime_zig_version = "0.17.0-dev.274+7eb79daff"; +/// Specifies the minimum Zig version that is runtime compatible with ZLS +const minimum_runtime_zig_version = "0.17.0-dev.1936+5a625d5f3"; const release_targets = [_]std.Target.Query{ .{ .cpu_arch = .aarch64, .os_tag = .linux }, diff --git a/build.zig.zon b/build.zig.zon index 16c470d2e1..e1f292a0d3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -10,7 +10,7 @@ // nix flake update --commit-lock-file // ``` // If you do not use Nix, a ZLS maintainer can take care of this. - .minimum_zig_version = "0.17.0-dev.292+fc1c83a36", + .minimum_zig_version = "0.17.0-dev.1936+5a625d5f3", // Must be kept in sync with the `deps.nix` for the Nix flake. // If you do not use Nix, a ZLS maintainer can take care of this. .dependencies = .{ From 1f3839fc2624ccc724e7b1854af5e7d36795ba1e Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 3 Sep 2026 22:52:41 +0200 Subject: [PATCH 03/14] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/8fd9daa3db09ced9700431c5b7ad0e8ba199b575?narHash=sha256-Ti%2BZBvW6yrWWAg2szExVTwCd4qOJ3KlVr1tFHfyfi8Q%3D' (2026-05-10) → 'github:NixOS/nixpkgs/b6018f87da91d19d0ab4cf979885689b469cdd41?narHash=sha256-twXPFqFsrrY5r28Zh7Homgcp2gUMBgQ6WDS98Q/3xFI%3D' (2026-06-30) • Updated input 'zig-flake': 'github:silversquirl/zig-flake/824cb996143ba0f42653b5312a1d3cb8822c3947?narHash=sha256-zm8q9v7KmBjtMTP1x6kk2Fh3XrAySDnZl3dMZl0Jqkc%3D' (2026-05-11) → 'github:silversquirl/zig-flake/61642c0d25af42802ba19ea9716dfc161778564e?narHash=sha256-QJHb%2BckgPtKE8Up3imHRJKdnM2YJiDLcDkmJhTwFmsI%3D' (2026-09-03) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index d6c3dc5111..8df5d630fe 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1778430510, - "narHash": "sha256-Ti+ZBvW6yrWWAg2szExVTwCd4qOJ3KlVr1tFHfyfi8Q=", + "lastModified": 1782847189, + "narHash": "sha256-twXPFqFsrrY5r28Zh7Homgcp2gUMBgQ6WDS98Q/3xFI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8fd9daa3db09ced9700431c5b7ad0e8ba199b575", + "rev": "b6018f87da91d19d0ab4cf979885689b469cdd41", "type": "github" }, "original": { @@ -29,11 +29,11 @@ ] }, "locked": { - "lastModified": 1778524253, - "narHash": "sha256-zm8q9v7KmBjtMTP1x6kk2Fh3XrAySDnZl3dMZl0Jqkc=", + "lastModified": 1788458932, + "narHash": "sha256-QJHb+ckgPtKE8Up3imHRJKdnM2YJiDLcDkmJhTwFmsI=", "owner": "silversquirl", "repo": "zig-flake", - "rev": "824cb996143ba0f42653b5312a1d3cb8822c3947", + "rev": "61642c0d25af42802ba19ea9716dfc161778564e", "type": "github" }, "original": { From bc7774bb8ee6d018331b8ef856add431912469fe Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 8 Aug 2026 00:29:44 +0200 Subject: [PATCH 04/14] update dependencies --- build.zig.zon | 12 ++++++------ deps.nix | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index e1f292a0d3..123567fe74 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -15,16 +15,16 @@ // If you do not use Nix, a ZLS maintainer can take care of this. .dependencies = .{ .known_folders = .{ - .url = "https://github.com/ziglibs/known-folders/archive/207c34a16e4365edc20d92c7892f962b3bed46e8.tar.gz", - .hash = "known_folders-0.0.0-Fy-PJsbKAACbDh9bBxR0MMThxZSS6A9RH4apWphNHY70", + .url = "https://github.com/ziglibs/known-folders/archive/6da0d0c41b78b9ed2d34fa364fcb81b5ebece6c4.tar.gz", + .hash = "known_folders-0.0.0-Fy-PJiDLAAB98m3uYUzatrTb2mO2fpvwx2zpSroEtfbO", }, .diffz = .{ - .url = "https://github.com/ziglibs/diffz/archive/d080c1eb782fff15068cabb3b82da85ce6054b74.tar.gz", - .hash = "diffz-0.0.1-G2tlIfLNAQCc06RFk0tFGj2M-X-id4WHFkMVw2JoMILR", + .url = "https://github.com/ziglibs/diffz/archive/aac8aa99c436ab8277b0711922aad062c0167b12.tar.gz", + .hash = "diffz-0.0.1-G2tlISvOAQDORzPTSxDgiKwlHuADKeJMdJrw4kRfLufj", }, .lsp_kit = .{ - .url = "https://github.com/zigtools/lsp-kit/archive/b886a2b0d5cee85ecbcc3089b863f7517cc9ff7f.tar.gz", - .hash = "lsp_kit-0.1.0-bi_PL3IyDACfp1xdTnkiOHEok2YpPCCCJHuuOcNzjl1D", + .url = "https://github.com/zigtools/lsp-kit/archive/d148676ace1eaecf3c703ce7d350d0e94e502ed7.tar.gz", + .hash = "lsp_kit-0.1.0-bi_PLwc2DACoR7VTz_qKFXUvtx7veHvWcTQvP9pDFLpk", }, .tracy = .{ .url = "https://github.com/wolfpld/tracy/archive/refs/tags/v0.13.1.tar.gz", diff --git a/deps.nix b/deps.nix index b9215ded0a..acdd63f23a 100644 --- a/deps.nix +++ b/deps.nix @@ -5,24 +5,24 @@ }: linkFarm "zig-packages" [ { - name = "known_folders-0.0.0-Fy-PJsbKAACbDh9bBxR0MMThxZSS6A9RH4apWphNHY70"; + name = "known_folders-0.0.0-Fy-PJiDLAAB98m3uYUzatrTb2mO2fpvwx2zpSroEtfbO"; path = fetchzip { - url = "https://github.com/ziglibs/known-folders/archive/207c34a16e4365edc20d92c7892f962b3bed46e8.tar.gz"; - hash = "sha256-9hMnEc3ktnFTZT28hjaOkafjSUXEI+SNIO9GMcJrBfA="; + url = "https://github.com/ziglibs/known-folders/archive/6da0d0c41b78b9ed2d34fa364fcb81b5ebece6c4.tar.gz"; + hash = "sha256-F17Ivvsyyla2CBIh+kImc1O3AtD2yTDAGamzMVbVzuw="; }; } { - name = "diffz-0.0.1-G2tlIfLNAQCc06RFk0tFGj2M-X-id4WHFkMVw2JoMILR"; + name = "diffz-0.0.1-G2tlISvOAQDORzPTSxDgiKwlHuADKeJMdJrw4kRfLufj"; path = fetchzip { - url = "https://github.com/ziglibs/diffz/archive/d080c1eb782fff15068cabb3b82da85ce6054b74.tar.gz"; - hash = "sha256-a9O9Wt8QkdMnSznWiowRoMlQm1JCFuxLwZR3SDHsGHs="; + url = "https://github.com/ziglibs/diffz/archive/aac8aa99c436ab8277b0711922aad062c0167b12.tar.gz"; + hash = "sha256-GQ4iCZSSpVvdDQteoi02keLSqSoDOSQPH8eihum++Z0="; }; } { - name = "lsp_kit-0.1.0-bi_PL3IyDACfp1xdTnkiOHEok2YpPCCCJHuuOcNzjl1D"; + name = "lsp_kit-0.1.0-bi_PLwc2DACoR7VTz_qKFXUvtx7veHvWcTQvP9pDFLpk"; path = fetchzip { - url = "https://github.com/zigtools/lsp-kit/archive/b886a2b0d5cee85ecbcc3089b863f7517cc9ff7f.tar.gz"; - hash = "sha256-367wPydvnpl9RYlTrXwk4bZ/ui9DbYjeY/VDYs7ZJRs="; + url = "https://github.com/zigtools/lsp-kit/archive/d148676ace1eaecf3c703ce7d350d0e94e502ed7.tar.gz"; + hash = "sha256-O4LSGT/0/2gFbXcOt0Panj4a26J5gA1NkmO0+7PfkJg="; }; } ] From 9d5250bee812ccab4ed2189395d7565f9c03c31d Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 3 Sep 2026 22:15:52 +0200 Subject: [PATCH 05/14] update langref.html.in --- src/tools/langref.html.in | 892 +++++++++----------------------------- 1 file changed, 214 insertions(+), 678 deletions(-) diff --git a/src/tools/langref.html.in b/src/tools/langref.html.in index 17f3be04a0..37a7ac4c3b 100644 --- a/src/tools/langref.html.in +++ b/src/tools/langref.html.in @@ -315,7 +315,7 @@ 0.9.1 | 0.10.1 | 0.11.0 | - 0.12.0 | + 0.12.1 | 0.13.0 | 0.14.1 | 0.15.2 | @@ -814,7 +814,7 @@

A destructuring expression may only appear within a block (i.e. not at {#link|Namespace#} scope). The left hand side of the assignment must consist of a comma separated list, - each element of which may be either an lvalue (for instance, an existing `var`) or a variable declaration: + each element of which may be either an lvalue (for instance, an existing {#syntax#}var{#endsyntax#}) or a variable declaration:

{#code|destructuring_mixed.zig#} @@ -1119,6 +1119,10 @@ otherwise the optimizer figures out all the values at compile-time, which operates in strict mode.

{#code|float_mode_exe.zig#} + {#shell_samp#}$ zig build-exe float_mode_exe.zig float_mode_obj.o -O fast +$ ./float_mode_exe +optimized = 0.001 +strict = 0.0009765625{#end_shell_samp#} {#see_also|@setFloatMode|Division by Zero#} {#header_close#} @@ -1370,7 +1374,8 @@ a /= b{#endsyntax#}
  • Can cause {#link|Division by Zero#} for floats in {#link|FloatMode.optimized Mode|Floating Point Operations#}.
  • Signed integer operands must be comptime-known and positive. In other cases, use {#link|@divTrunc#}, - {#link|@divFloor#}, or + {#link|@divFloor#}, + {#link|@divCeil#}, or {#link|@divExact#} instead.
  • Invokes {#link|Peer Type Resolution#} for the operands.
  • @@ -1917,7 +1922,7 @@ or

    Vectors generally support the same builtin operators as their underlying base types. - The only exception to this is the keywords `and` and `or` on vectors of bools, since + The only exception to this is the keywords {#syntax#}and{#endsyntax#} and {#syntax#}or{#endsyntax#} on vectors of bools, since these operators affect control flow, which is not allowed for vectors. All other operations are performed element-wise, and return a vector of the same length as the input vectors. This includes: @@ -1955,7 +1960,7 @@ or TODO talk about C ABI interop
    TODO consider suggesting std.MultiArrayList

    - {#see_also|@splat|@shuffle|@select|@reduce#} + {#see_also|@splat|@shuffle|@select|@reduce|Slicing by Length#} {#header_open|Relationship with Arrays#}

    Vectors and {#link|Arrays#} each have a well-defined bit layout @@ -2150,6 +2155,24 @@ or {#see_also|Pointers|for|Arrays#} + {#header_open|Slicing by Length#} +

    Even though Zig only has syntax for slicing based on start and end indices, by slicing twice, + one can express a slice by length operation.

    +

    The pattern {#syntax#}[a .. a + b]{#endsyntax#} is always better expressed + {#syntax#}[a..][0..b]{#endsyntax#} because:

    +
      +
    • Slices are represented in memory as a pointer and length. Despite + syntactically appearing as twice the work, it is actually one less + subtraction in machine code.
    • +
    • If {#syntax#}a{#endsyntax#} is known at runtime and + {#syntax#}b{#endsyntax#} is known at {#link|comptime#}, the former + results in a slice but the latter results in a single-item + {#link|pointer|Pointers#} to an {#link|array|Arrays#}, a generally more + safe type because the length is compile-time known.
    • +
    + {#code|slicing_by_length.zig#} + {#header_close#} + {#header_open|Sentinel-Terminated Slices#}

    The syntax {#syntax#}[:x]T{#endsyntax#} is a slice which has a runtime-known length @@ -2252,6 +2275,7 @@ or This even works at {#link|comptime#}:

    {#code|test_packed_structs.zig#} +

    The backing integer can be inferred or explicitly provided. When inferred, it will be unsigned. When explicitly provided, its bit width @@ -2260,6 +2284,12 @@ or

    {#code|test_missized_packed_struct.zig#} +

    + A {#syntax#}packed struct{#endsyntax#} can be converted to and from its backing + integer using {#link|@backingInt#} and {#link|@fromBackingInt#}: +

    + {#code|test_packed_struct_backing_int.zig#} +

    Zig allows the address to be taken of a non-byte-aligned field:

    @@ -2382,7 +2412,7 @@ or {#header_open|enum#} {#code|test_enums.zig#} - {#see_also|@typeInfo|@tagName|@sizeOf#} + {#see_also|@backingInt|@fromBackingInt|@typeInfo|@tagName|@sizeOf|noreturn#} {#header_open|extern enum#}

    @@ -2412,9 +2442,7 @@ or The enum must specify a tag type and cannot consume every enumeration value.

    - {#link|@enumFromInt#} on a non-exhaustive enum involves the safety semantics - of {#link|@intCast#} to the integer tag type, but beyond that always results in - a well-defined enum value. + {#link|@fromBackingInt#} on a non-exhaustive enum always results in a valid enum value.

    A switch on a non-exhaustive enum can include a {#syntax#}_{#endsyntax#} prong as an alternative to an {#syntax#}else{#endsyntax#} prong. @@ -2442,7 +2470,8 @@ or {#code|test_simple_union.zig#}

    - In order to use {#link|switch#} with a union, it must be a {#link|Tagged union#}. + In order to use {#link|switch#} with a union, it must be a {#link|tagged union|Tagged union#} + or a {#link|packed union#}.

    To initialize a union when the tag is a {#link|comptime#}-known name, see {#link|@unionInit#}. @@ -2471,7 +2500,7 @@ or

    Unions with inferred enum tag types can also assign ordinal values to their inferred tag. This requires the tag to specify an explicit integer type. - {#link|@intFromEnum#} can be used to access the ordinal value corresponding to the active field. + {#link|@backingInt#} can be used to access the ordinal value corresponding to the active field.

    {#code|test_tagged_union_with_tag_values.zig#} @@ -2502,6 +2531,7 @@ or

    {#code|test_packed_union_equality.zig#} + {#see_also|@backingInt|@fromBackingInt#} {#header_close#} {#header_open|Anonymous Union Literals#} @@ -2904,8 +2934,8 @@ or {#header_open|inline fn#}

    Adding the {#syntax#}inline{#endsyntax#} keyword to a function definition makes that - function become semantically inlined at the callsite. This is - not a hint to be possibly observed by optimization passes, but has + function become semantically inlined at the callsite. {#link|Inline Is Not A Hint#} + to be possibly observed by optimization passes, but has implications on the types and values involved in the function call.

    @@ -2917,16 +2947,22 @@ or

    If {#syntax#}inline{#endsyntax#} is removed, the test fails with the compile error instead of passing.

    -

    It is generally better to let the compiler decide when to inline a - function, except for these scenarios:

    + + {#header_open|Inline Is Not A Hint#} +

    Use of {#syntax#}inline{#endsyntax#} restricts what the + compiler is allowed to do. This can harm binary size, compilation speed, + and even runtime performance. In exchange, these use cases are addressed:

      -
    • To change how many stack frames are in the call stack, for debugging purposes.
    • -
    • To force comptime-ness of the arguments to propagate to the return value of the function, as in the above example.
    • -
    • Real world performance measurements demand it.
    • +
    • Ability to ensure certain stack frames are in the call stack, for + debugging purposes, or for integrating with {#link|Assembly#}.
    • +
    • Forcing arguments from the callsite that happen to be + {#link|comptime#} to propagate to the return value of the function, as + in the above example.
    • +
    • Working around limitations of compiler backend implementations.
    -

    Note that {#syntax#}inline{#endsyntax#} actually restricts - what the compiler is allowed to do. This can harm binary size, - compilation speed, and even runtime performance.

    +

    For an alternative to {#syntax#}inline{#endsyntax#} that merely hints to the compiler + that inlining a function may be valuable, use {#link|@branchHint#}.

    + {#header_close#} {#header_close#} {#header_open|Function Reflection#} @@ -3567,14 +3603,14 @@ void do_a_thing(struct Foo *foo) {
    • {#link|@bitCast#} - change type but maintain bit representation
    • {#link|@alignCast#} - make a pointer have more alignment
    • -
    • {#link|@enumFromInt#} - obtain an enum value based on its integer tag value
    • +
    • {#link|@fromBackingInt#} - obtain an enum or a packed struct/union value based on its backing integer
    • {#link|@errorFromInt#} - obtain an error code based on its integer value
    • {#link|@errorCast#} - convert to a smaller error set
    • {#link|@floatCast#} - convert a larger float to a smaller float
    • {#link|@floatFromInt#} - convert an integer to a float value
    • {#link|@intCast#} - convert between integer types
    • {#link|@intFromBool#} - convert true to 1 and false to 0
    • -
    • {#link|@intFromEnum#} - obtain the integer tag value of an enum or tagged union
    • +
    • {#link|@backingInt#} - obtain the backing integer value of an enum or a packed struct/union
    • {#link|@intFromError#} - obtain the integer value of an error code
    • {#link|@round#}, {#link|@floor#}, {#link|@ceil#}, {#link|@trunc#} - float to integer conversion
    • {#link|@intFromPtr#} - obtain the address of a pointer
    • @@ -4413,6 +4449,18 @@ comptime { {#see_also|@atomicLoad|@atomicRmw|@cmpxchgWeak|@cmpxchgStrong#} {#header_close#} + {#header_open|@backingInt#} +
      {#syntax#}@backingInt(enum_or_bitpack: T) BackingInt(T){#endsyntax#}
      +

      + Converts an {#link|enum#}, a {#link|packed struct#} or a {#link|packed union#} value + to its backing integer. +

      +

      + Also works with {#link|tagged unions|Tagged union#}, acting on the active enum tag value. +

      + {#see_also|@fromBackingInt|@bitCast#} + {#header_close#} + {#header_open|@bitCast#}
      {#syntax#}@bitCast(value: anytype) anytype{#endsyntax#}

      @@ -4433,8 +4481,18 @@ comptime {

    • Convert {#syntax#}i32{#endsyntax#} to {#syntax#}u32{#endsyntax#} preserving twos complement

    - Works at compile-time if {#syntax#}value{#endsyntax#} is known at compile time. It's a compile error to bitcast a value of undefined layout; this means that, besides the restriction from types which possess dedicated casting builtins (enums, pointers, error sets), bare structs, error unions, slices, optionals, and any other type without a well-defined memory layout, also cannot be used in this operation. + Works at compile-time if {#syntax#}value{#endsyntax#} is known at compile time. + It's a compile error to bitcast a value of undefined layout; this means that, + besides the restriction from types which possess dedicated casting builtins + (pointers, error sets), bare structs, error unions, slices, optionals, and any + other type without a well-defined memory layout, also cannot be used in this + operation. +

    +

    + Attempting to convert an integer with no corresponding tag value to an + {#syntax#}enum{#endsyntax#} invokes safety-checked {#link|Illegal Behavior#}.

    + {#see_also|@ptrCast|@intFromPtr|@ptrFromInt|@errorCast|@intFromError|@errorFromInt|@backingInt|@fromBackingInt#} {#header_close#} {#header_open|@bitOffsetOf#} @@ -4717,7 +4775,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
  • {#syntax#}@divExact(a, b) * b == a{#endsyntax#}
  • For a function that returns a possible error code, use {#syntax#}@import("std").math.divExact{#endsyntax#}.

    - {#see_also|@divTrunc|@divFloor#} + {#see_also|@divTrunc|@divFloor|@divCeil#} {#header_close#} {#header_open|@divFloor#}
    {#syntax#}@divFloor(numerator: T, denominator: T) T{#endsyntax#}
    @@ -4731,7 +4789,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
  • {#syntax#}(@divFloor(a, b) * b) + @mod(a, b) == a{#endsyntax#}
  • For a function that returns a possible error code, use {#syntax#}@import("std").math.divFloor{#endsyntax#}.

    - {#see_also|@divTrunc|@divExact#} + {#see_also|@divTrunc|@divCeil|@divExact#} {#header_close#} {#header_open|@divTrunc#}
    {#syntax#}@divTrunc(numerator: T, denominator: T) T{#endsyntax#}
    @@ -4745,7 +4803,20 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
  • {#syntax#}(@divTrunc(a, b) * b) + @rem(a, b) == a{#endsyntax#}
  • For a function that returns a possible error code, use {#syntax#}@import("std").math.divTrunc{#endsyntax#}.

    - {#see_also|@divFloor|@divExact#} + {#see_also|@divFloor|@divCeil|@divExact#} + {#header_close#} + {#header_open|@divCeil#} +
    {#syntax#}@divCeil(numerator: T, denominator: T) T{#endsyntax#}
    +

    + Ceiled division. Rounds toward positive infinity. Caller guarantees {#syntax#}denominator != 0{#endsyntax#} and + {#syntax#}!(@typeInfo(T) == .int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1){#endsyntax#}. +

    +
      +
    • {#syntax#}@divCeil(5, 3) == 2{#endsyntax#}
    • +
    • {#syntax#}@divCeil(-5, 3) == -1{#endsyntax#}
    • +
    +

    For a function that returns a possible error code, use {#syntax#}@import("std").math.divCeil{#endsyntax#}.

    + {#see_also|@divFloor|@divTrunc|@divExact#} {#header_close#} {#header_open|@embedFile#} @@ -4766,6 +4837,9 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#header_open|@enumFromInt#}
    {#syntax#}@enumFromInt(integer: anytype) anytype{#endsyntax#}

    + Deprecated. Use {#link|@fromBackingInt#} or {#link|@bitCast#} instead. +

    +

    Converts an integer into an {#link|enum#} value. The return type is the inferred result type.

    @@ -4921,11 +4995,41 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

    {#header_close#} + {#header_open|@fromBackingInt#} +
    {#syntax#}@fromBackingInt(backing_int: BackingInt(T)) T{#endsyntax#}
    +

    + Converts an integer into a {#link|enum#}, a {#link|packed struct#} or a + {#link|packed union#} value. The return type is the inferred result type. +

    +

    + Attempting to convert an integer with no corresponding tag value to an + {#syntax#}enum{#endsyntax#} invokes safety-checked {#link|Illegal Behavior#}. + Note that a {#link|non-exhaustive enum|Non-exhaustive enum#} has corresponding values for + all integers in the enum's integer tag type: the {#syntax#}_{#endsyntax#} value represents + all the remaining unnamed integers in the enum's tag type. +

    + {#see_also|@backingInt|@bitCast#} + {#header_close#} + {#header_open|@hasDecl#}
    {#syntax#}@hasDecl(comptime Namespace: type, comptime name: []const u8) bool{#endsyntax#}
    -

    Returns whether or not a {#link|Namespace#} has a declaration matching {#syntax#}name{#endsyntax#}.

    +

    Returns whether or not a {#link|Namespace#} has a public declaration matching {#syntax#}name{#endsyntax#}.

    {#code|test_hasDecl_builtin.zig#} +

    Caution: using {#syntax#}@hasDecl{#endsyntax#} to implement + conditional compilation based on the presence or absence of declarations + in an API runs the risk of introducing dead code into the codebase, + unnoticed. For example, if the field is ever renamed, code which uses + {#syntax#}@hasDecl{#endsyntax#} will compile successfully while failing + to enable or disable an intended block of code. Even a simple typo in + the field name passed to {#syntax#}@hasDecl{#endsyntax#} would go + unnoticed by the compiler. Thus, it is safer to use alternative methods + to check for the presence or absence of a particular declaration where + possible. For example, if the target operating system would indicate whether a particular + function is available, it is more maintainable to check + {#syntax#}@import("builtin").target.os.tag{#endsyntax#} rather than + {#syntax#}@hasDecl{#endsyntax#}.

    + {#see_also|@hasField#} {#header_close#} @@ -5003,12 +5107,11 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#header_open|@intFromEnum#}
    {#syntax#}@intFromEnum(enum_or_tagged_union: anytype) anytype{#endsyntax#}

    - Converts an enumeration value into its integer tag type. When a tagged union is passed, - the tag value is used as the enumeration value. + Deprecated. Use {#link|@backingInt#} or {#link|@bitCast#} instead.

    - If there is only one possible enum value, the result is a {#syntax#}comptime_int{#endsyntax#} - known at {#link|comptime#}. + Converts an enumeration value into its integer tag type. When a tagged union is passed, + the tag value is used as the enumeration value.

    {#see_also|@enumFromInt#} {#header_close#} @@ -5045,7 +5148,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val Converts {#syntax#}value{#endsyntax#} to a {#syntax#}usize{#endsyntax#} which is the address of the pointer. {#syntax#}value{#endsyntax#} can be {#syntax#}*T{#endsyntax#} or {#syntax#}?*T{#endsyntax#}.

    -

    To convert the other way, use {#link|@ptrFromInt#}

    + {#see_also|@ptrFromInt#} {#header_close#} {#header_open|@max#} @@ -5249,6 +5352,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val If the destination pointer type does not allow address zero and {#syntax#}address{#endsyntax#} is zero, this invokes safety-checked {#link|Illegal Behavior#}.

    + {#see_also|@intFromPtr#} {#header_close#} {#header_open|@rem#} @@ -5456,19 +5560,14 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#header_close#} {#header_open|@splat#} -
    {#syntax#}@splat(scalar: anytype) anytype{#endsyntax#}
    +
    {#syntax#}@splat(element: anytype) anytype{#endsyntax#}

    Produces an array or vector where each element is the value - {#syntax#}scalar{#endsyntax#}. The return type and thus the length of the - vector is inferred. + {#syntax#}element{#endsyntax#}. The return type, including the number of + elements, is inferred.

    {#code|test_splat_builtin.zig#} - -

    - {#syntax#}scalar{#endsyntax#} must be an {#link|integer|Integers#}, {#link|bool|Primitive Types#}, - {#link|float|Floats#}, or {#link|pointer|Pointers#}. -

    - {#see_also|Vectors|@shuffle#} + {#see_also|Vectors|Arrays|@shuffle#} {#header_close#} {#header_open|@reduce#} @@ -5752,7 +5851,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#header_open|@Fn#}
    {#syntax#}@Fn(
         comptime param_types: []const type,
    -    comptime param_attrs: *const [param_types.len]std.lang.Type.Fn.Param.Attributes,
    +    comptime param_attrs: *const [param_types.len]std.lang.Type.Fn.ParamAttributes,
         comptime ReturnType: type,
         comptime attrs: std.lang.Type.Fn.Attributes,
     ) type{#endsyntax#}
    @@ -5765,7 +5864,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val comptime BackingInt: ?type, comptime field_names: []const []const u8, comptime field_types: *const [field_names.len]type, - comptime field_attrs: *const [field_names.len]std.lang.Type.StructField.Attributes, + comptime field_attrs: *const [field_names.len]std.lang.Type.Struct.FieldAttributes, ) type{#endsyntax#}

    Returns a {#link|struct#} type with the properties specified by the arguments.

    {#header_close#} @@ -5773,11 +5872,11 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#header_open|@Union#}
    {#syntax#}@Union(
         comptime layout: std.lang.Type.ContainerLayout,
    -    /// Either the integer tag type, or the integer backing type, depending on `layout`.
    +    /// Either the enum tag type, or the integer backing type, depending on `layout`.
         comptime ArgType: ?type,
         comptime field_names: []const []const u8,
         comptime field_types: *const [field_names.len]type,
    -    comptime field_attrs: *const [field_names.len]std.lang.Type.UnionField.Attributes,
    +    comptime field_attrs: *const [field_names.len]std.lang.Type.Union.FieldAttributes,
     ) type{#endsyntax#}

    Returns a {#link|union#} type with the properties specified by the arguments.

    {#header_close#} @@ -5792,6 +5891,50 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

    Returns an {#link|enum#} type with the properties specified by the arguments.

    {#header_close#} + {#header_open|@SpirvType#} +
    {#syntax#}@SpirvType(comptime options: std.lang.Type.Spirv) type{#endsyntax#}
    +

    + Returns a SPIR-V type with the properties specified by the arguments. +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TagSPIR-V EquivalentDescription
    .samplerOpTypeSamplerAn opaque sampler
    .imageOpTypeImageAn opaque image
    .sampled_imageOpTypeSampledImageAn opaque image combined with a sampler
    .runtime_arrayOpTypeRuntimeArray + An array whose length is determined at runtime. + The resulting type supports indexing and exposes a {#syntax#}.len{#endsyntax#} field. + It may only appear as the last field of an {#link|extern struct#}. +
    +
    + {#header_close#} + {#header_open|@typeInfo#}
    {#syntax#}@typeInfo(comptime T: type) std.lang.Type{#endsyntax#}

    @@ -6038,6 +6181,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

  • {#syntax#}/{#endsyntax#} (division)
  • {#link|@divTrunc#} (division)
  • {#link|@divFloor#} (division)
  • +
  • {#link|@divCeil#} (division)
  • {#link|@divExact#} (division)
  • Example with addition at compile-time:

    @@ -6055,6 +6199,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
  • {#syntax#}@import("std").math.mul{#endsyntax#}
  • {#syntax#}@import("std").math.divTrunc{#endsyntax#}
  • {#syntax#}@import("std").math.divFloor{#endsyntax#}
  • +
  • {#syntax#}@import("std").math.divCeil{#endsyntax#}
  • {#syntax#}@import("std").math.divExact{#endsyntax#}
  • {#syntax#}@import("std").math.shl{#endsyntax#}
  • @@ -7135,46 +7280,27 @@ fn readU32Be() u32 {} {#header_close#} {#header_close#} + {#header_open|Source Encoding#} -

    Zig source code is encoded in UTF-8. An invalid UTF-8 byte sequence results in a compile error.

    -

    Throughout all zig source code (including in comments), some code points are never allowed:

    +

    Zig source code is UTF-8 encoded. Invalid UTF-8 byte sequences are not allowed anywhere.

    +

    Some code points are never allowed, even in {#link|Comments#}:

      -
    • Ascii control characters, except for U+000a (LF), U+000d (CR), and U+0009 (HT): U+0000 - U+0008, U+000b - U+000c, U+000e - U+0001f, U+007f.
    • -
    • Non-Ascii Unicode line endings: U+0085 (NEL), U+2028 (LS), U+2029 (PS).
    • +
    • ASCII control characters, except for U+000a (LF): U+0000...U+0009, U+000b...U+0001f, U+007f.
    • +
    • Non-ASCII Unicode line endings: U+0085 (NEL), U+2028 (LS), U+2029 (PS).
    • +
    • Byte order marks: U+FEFF (BOM).

    - LF (byte value 0x0a, code point U+000a, {#syntax#}'\n'{#endsyntax#}) is the line terminator in Zig source code. - This byte value terminates every line of zig source code except the last line of the file. - It is recommended that non-empty source files end with an empty line, which means the last byte would be 0x0a (LF). -

    -

    - Each LF may be immediately preceded by a single CR (byte value 0x0d, code point U+000d, {#syntax#}'\r'{#endsyntax#}) - to form a Windows style line ending, but this is discouraged. Note that in multiline strings, CRLF sequences will - be encoded as LF when compiled into a zig program. - A CR in any other context is not allowed. -

    -

    - HT hard tabs (byte value 0x09, code point U+0009, {#syntax#}'\t'{#endsyntax#}) are interchangeable with - SP spaces (byte value 0x20, code point U+0020, {#syntax#}' '{#endsyntax#}) as a token separator, - but use of hard tabs is discouraged. See {#link|Grammar#}. -

    -

    - For compatibility with other tools, the compiler ignores a UTF-8-encoded byte order mark (U+FEFF) - if it is the first Unicode code point in the source text. A byte order mark is not allowed anywhere else in the source. -

    -

    - Note that running zig fmt on a source file will implement all recommendations mentioned here. -

    -

    - Note that a tool reading Zig source code can make assumptions if the source code is assumed to be correct Zig code. - For example, when identifying the ends of lines, a tool can use a naive search such as /\n/, - or an advanced - search such as /\r\n?|[\n\u0085\u2028\u2029]/, and in either case line endings will be correctly identified. - For another example, when identifying the whitespace before the first token on a line, - a tool can either use a naive search such as /[ \t]/, - or an advanced search such as /\s/, - and in either case whitespace will be correctly identified. + LF (byte value 0x0a, code point U+000a, {#syntax#}'\n'{#endsyntax#}) is + the line terminator in Zig source code. This byte value terminates every + line of Zig source code, including last line of the file.

    +

    These conservative rules mean that third party tools reading + already-validated Zig source code may make simplifying assumptions, such + as naively separating lines based on {#syntax#}'\n'{#endsyntax#}. + However, tooling such as zig fmt provides convenience + functionality to convert invalid source encodings to valid source + encodings, for instance by stripping byte order marks and carriage + returns.

    {#header_close#} {#header_open|Keyword Reference#} @@ -7378,7 +7504,7 @@ fn readU32Be() u32 {}
    {#syntax#}errdefer{#endsyntax#}
    - {#syntax#}errdefer{#endsyntax#} will execute an expression when control flow leaves the current block if the function returns an error, the errdefer expression can capture the unwrapped value. + {#syntax#}errdefer{#endsyntax#} will execute an expression when control flow leaves the current block if the function returns an error.
    • See also {#link|errdefer#}
    @@ -7725,614 +7851,24 @@ fn readU32Be() u32 {} {#header_open|Appendix#} {#header_open|Grammar#} - {#syntax_block|peg|grammar.peg#} -Root <- skip ContainerMembers eof - -# *** Top level *** -ContainerMembers <- container_doc_comment? ContainerDeclaration* (ContainerField COMMA)* (ContainerField / ContainerDeclaration*) - -ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl - -TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block - -ComptimeDecl <- KEYWORD_comptime Block - -Decl - <- (KEYWORD_export / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block) - / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON - / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl - -FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr !ExprSuffix - -VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? - -GlobalVarDecl <- VarDeclProto (EQUAL Expr)? SEMICOLON - -ContainerField <- doc_comment? (KEYWORD_comptime / !KEYWORD_comptime) !KEYWORD_fn (IDENTIFIER COLON / !(IDENTIFIER COLON))? TypeExpr ByteAlign? (EQUAL Expr)? - -# *** Block Level *** -BlockStatement - <- Statement - / KEYWORD_defer BlockExprStatement - / KEYWORD_errdefer BlockExprStatement - / !ExprStatement (KEYWORD_comptime !BlockExpr)? VarAssignStatement - -Statement - <- ExprStatement - / KEYWORD_suspend BlockExprStatement - / !ExprStatement (KEYWORD_comptime !BlockExpr)? AssignExpr SEMICOLON - -ExprStatement - <- IfStatement - / LabeledStatement - / KEYWORD_nosuspend BlockExprStatement - / KEYWORD_comptime BlockExpr - -IfStatement - <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )? - / IfPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement ) - -LabeledStatement <- BlockLabel? (Block / LoopStatement / SwitchExpr) - -LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement) - -ForStatement - <- ForPrefix BlockExpr ( KEYWORD_else Statement / !KEYWORD_else ) - / ForPrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Statement ) - -WhileStatement - <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )? - / WhilePrefix !BlockExpr AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement ) - -BlockExprStatement - <- BlockExpr - / !BlockExpr AssignExpr SEMICOLON - -BlockExpr <- BlockLabel? Block - -# An assignment or a destructure whose LHS are all lvalue expressions or variable declarations. -VarAssignStatement <- (VarDeclProto / Expr) (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON - -# *** Expression Level *** - -# An assignment or a destructure whose LHS are all lvalue expressions. -AssignExpr <- Expr (AssignOp Expr / (COMMA Expr)+ EQUAL Expr)? - -SingleAssignExpr <- Expr (AssignOp Expr)? - -Expr <- BoolOrExpr - -BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)* - -BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)* - -CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)? - -BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)* - -BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)* - -AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)* - -MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)* - -PrefixExpr <- PrefixOp* PrimaryExpr - -PrimaryExpr - <- AsmExpr - / IfExpr - / KEYWORD_break (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart) - / KEYWORD_comptime Expr !ExprSuffix - / KEYWORD_nosuspend Expr !ExprSuffix - / KEYWORD_continue (BreakLabel / !BreakLabel) (Expr !ExprSuffix / !SinglePtrTypeStart) - / KEYWORD_resume Expr !ExprSuffix - / KEYWORD_return (Expr !ExprSuffix / !SinglePtrTypeStart) - / BlockLabel? LoopExpr - / Block - / CurlySuffixExpr - -IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix - -Block <- LBRACE BlockStatement* RBRACE - -LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr) - -ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) !ExprSuffix - -WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix - -CurlySuffixExpr <- TypeExpr InitList? - -InitList - <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE - / LBRACE Expr (COMMA Expr)* COMMA? RBRACE - / LBRACE RBRACE - -TypeExpr <- PrefixTypeOp* ErrorUnionExpr - -ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)? - -SuffixExpr - <- PrimaryTypeExpr (SuffixOp / FnCallArguments)* - -PrimaryTypeExpr - <- BUILTINIDENTIFIER FnCallArguments - / CHAR_LITERAL - / ContainerDecl - / DOT IDENTIFIER - / DOT InitList - / ErrorSetDecl - / FLOAT - / FnProto - / GroupedExpr - / LabeledTypeExpr - / IDENTIFIER !(COLON LabelableExpr) - / IfTypeExpr - / INTEGER - / KEYWORD_comptime TypeExpr !ExprSuffix - / KEYWORD_error DOT IDENTIFIER - / KEYWORD_anyframe - / KEYWORD_unreachable - / STRINGLITERAL - -ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto - -ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE - -GroupedExpr <- LPAREN Expr RPAREN - -IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix - -LabeledTypeExpr - <- BlockLabel Block - / BlockLabel? LoopTypeExpr - / BlockLabel? SwitchExpr - -LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr) - -ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) !ExprSuffix - -WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix - -SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE - -# *** Assembly *** -AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN - -AsmOutput <- COLON AsmOutputList AsmInput? - -AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN - -AsmInput <- COLON AsmInputList AsmClobbers? - -AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERALSINGLE LPAREN Expr RPAREN - -AsmClobbers <- COLON Expr - -# *** Helper grammar *** -BreakLabel <- COLON IDENTIFIER - -BlockLabel <- IDENTIFIER COLON - -FieldInit <- DOT IDENTIFIER EQUAL Expr - -WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN - -LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN - -AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN - -# Fn specific -CallConv <- KEYWORD_callconv LPAREN Expr RPAREN - -ParamDecl <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime / !KEYWORD_comptime) (IDENTIFIER COLON / !(IDENTIFIER_COLON)) ParamType - -ParamType - <- KEYWORD_anytype - / TypeExpr - -# Control flow prefixes -IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload? - -WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr? - -ForPrefix <- KEYWORD_for LPAREN ForArgumentsList RPAREN PtrListPayload - -# Payloads -Payload <- PIPE IDENTIFIER PIPE - -PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE - -PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE - -PtrListPayload <- PIPE ASTERISK? IDENTIFIER (COMMA ASTERISK? IDENTIFIER)* COMMA? PIPE - -# Switch specific -SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? SingleAssignExpr - -SwitchCase - <- SwitchItem (COMMA SwitchItem)* COMMA? - / KEYWORD_else - -SwitchItem <- Expr (DOT3 Expr)? - -# For specific -ForArgumentsList <- ForItem (COMMA ForItem)* COMMA? - -ForItem <- Expr (DOT2 Expr?)? - -# Operators -AssignOp - <- ASTERISKEQUAL - / ASTERISKPIPEEQUAL - / SLASHEQUAL - / PERCENTEQUAL - / PLUSEQUAL - / PLUSPIPEEQUAL - / MINUSEQUAL - / MINUSPIPEEQUAL - / LARROW2EQUAL - / LARROW2PIPEEQUAL - / RARROW2EQUAL - / AMPERSANDEQUAL - / CARETEQUAL - / PIPEEQUAL - / ASTERISKPERCENTEQUAL - / PLUSPERCENTEQUAL - / MINUSPERCENTEQUAL - / EQUAL - -CompareOp - <- EQUALEQUAL - / EXCLAMATIONMARKEQUAL - / LARROW - / RARROW - / LARROWEQUAL - / RARROWEQUAL - -BitwiseOp - <- AMPERSAND - / CARET - / PIPE - / KEYWORD_orelse - / KEYWORD_catch Payload? - -BitShiftOp - <- LARROW2 - / RARROW2 - / LARROW2PIPE - -AdditionOp - <- PLUS - / MINUS - / PLUS2 - / PLUSPERCENT - / MINUSPERCENT - / PLUSPIPE - / MINUSPIPE - -MultiplyOp - <- PIPE2 - / ASTERISK - / SLASH - / PERCENT - / ASTERISKPERCENT - / ASTERISKPIPE - -PrefixOp - <- EXCLAMATIONMARK - / MINUS - / TILDE - / MINUSPERCENT - / AMPERSAND - / KEYWORD_try - -PrefixTypeOp - <- QUESTIONMARK - / KEYWORD_anyframe MINUSRARROW - / (ManyPtrTypeStart / SliceTypeStart) KEYWORD_allowzero? ByteAlign? AddrSpace? KEYWORD_const? KEYWORD_volatile? - / SinglePtrTypeStart KEYWORD_allowzero? BitAlign? AddrSpace? KEYWORD_const? KEYWORD_volatile? - / ArrayTypeStart - -SuffixOp - <- LBRACKET Expr (DOT2 (Expr? (COLON Expr)?)?)? RBRACKET - / DOT IDENTIFIER - / DOTASTERISK - / DOTQUESTIONMARK - -FnCallArguments <- LPAREN ExprList RPAREN - -ExprSuffix - <- KEYWORD_or - / KEYWORD_and - / CompareOp - / BitwiseOp - / BitShiftOp - / AdditionOp - / MultiplyOp - / EXCLAMATIONMARK - / SuffixOp - / FnCallArguments - -LabelableExpr - <- Block - / SwitchExpr - / LoopExpr - -# Ptr specific -SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET - -SinglePtrTypeStart <- ASTERISK - -ManyPtrTypeStart <- LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET - -ArrayTypeStart <- LBRACKET Expr !ASTERISK (COLON Expr)? RBRACKET - -# ContainerDecl specific -ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE - -ContainerDeclType - <- KEYWORD_struct (LPAREN Expr RPAREN)? - / KEYWORD_opaque - / KEYWORD_enum (LPAREN Expr RPAREN)? - / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / !KEYWORD_enum Expr) RPAREN)? - -# Alignment -ByteAlign <- KEYWORD_align LPAREN Expr RPAREN - -BitAlign <- KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN - -# Lists -IdentifierList <- (doc_comment? IDENTIFIER COMMA)* (doc_comment? IDENTIFIER)? - -SwitchProngList <- (SwitchProng COMMA)* SwitchProng? - -AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem? - -AsmInputList <- (AsmInputItem COMMA)* AsmInputItem? - -ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)? - -ExprList <- (Expr COMMA)* Expr? - -# *** Tokens *** -eof <- !. -bin <- [01] -bin_ <- '_'? bin -oct <- [0-7] -oct_ <- '_'? oct -hex <- [0-9a-fA-F] -hex_ <- '_'? hex -dec <- [0-9] -dec_ <- '_'? dec - -bin_int <- bin bin_* -oct_int <- oct oct_* -dec_int <- dec dec_* -hex_int <- hex hex_* - -ox80_oxBF <- [\200-\277] -oxF4 <- '\364' -ox80_ox8F <- [\200-\217] -oxF1_oxF3 <- [\361-\363] -oxF0 <- '\360' -ox90_0xBF <- [\220-\277] -oxEE_oxEF <- [\356-\357] -oxED <- '\355' -ox80_ox9F <- [\200-\237] -oxE1_oxEC <- [\341-\354] -oxE0 <- '\340' -oxA0_oxBF <- [\240-\277] -oxC2_oxDF <- [\302-\337] - -# From https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/ -# First Byte Second Byte Third Byte Fourth Byte -# [0x00,0x7F] -# [0xC2,0xDF] [0x80,0xBF] -# 0xE0 [0xA0,0xBF] [0x80,0xBF] -# [0xE1,0xEC] [0x80,0xBF] [0x80,0xBF] -# 0xED [0x80,0x9F] [0x80,0xBF] -# [0xEE,0xEF] [0x80,0xBF] [0x80,0xBF] -# 0xF0 [0x90,0xBF] [0x80,0xBF] [0x80,0xBF] -# [0xF1,0xF3] [0x80,0xBF] [0x80,0xBF] [0x80,0xBF] -# 0xF4 [0x80,0x8F] [0x80,0xBF] [0x80,0xBF] - -multibyte_utf8 <- - oxF4 ox80_ox8F ox80_oxBF ox80_oxBF - / oxF1_oxF3 ox80_oxBF ox80_oxBF ox80_oxBF - / oxF0 ox90_0xBF ox80_oxBF ox80_oxBF - / oxEE_oxEF ox80_oxBF ox80_oxBF - / oxED ox80_ox9F ox80_oxBF - / oxE1_oxEC ox80_oxBF ox80_oxBF - / oxE0 oxA0_oxBF ox80_oxBF - / oxC2_oxDF ox80_oxBF - -non_control_ascii <- [\040-\176] -non_control_utf8 <- [\040-\377] - -char_escape - <- "\\x" hex hex - / "\\u{" hex+ "}" - / "\\" [nr\\t'"] -char_char - <- multibyte_utf8 - / char_escape - / ![\\'\n] non_control_ascii - -string_char - <- multibyte_utf8 - / char_escape - / ![\\"\n] non_control_ascii - -container_doc_comment <- ('//!' non_control_utf8* [ \n]* skip)+ -doc_comment <- ('///' non_control_utf8* [ \n]* skip)+ -line_comment <- '//' ![!/] non_control_utf8* / '////' non_control_utf8* -line_string <- '\\\\' non_control_utf8* [ \n]* -skip <- ([ \n] / line_comment)* - -CHAR_LITERAL <- ['] char_char ['] skip -FLOAT - <- '0x' hex_int '.' hex_int ([pP] [-+]? dec_int)? skip - / dec_int '.' dec_int ([eE] [-+]? dec_int)? skip - / '0x' hex_int [pP] [-+]? dec_int skip - / dec_int [eE] [-+]? dec_int skip -INTEGER - <- '0b' bin_int skip - / '0o' oct_int skip - / '0x' hex_int skip - / dec_int skip -STRINGLITERALSINGLE <- ["] string_char* ["] skip -STRINGLITERAL - <- STRINGLITERALSINGLE - / (line_string skip)+ -IDENTIFIER - <- !keyword [A-Za-z_] [A-Za-z0-9_]* skip - / '@' STRINGLITERALSINGLE -BUILTINIDENTIFIER <- '@'[A-Za-z_][A-Za-z0-9_]* skip - - -AMPERSAND <- '&' ![=] skip -AMPERSANDEQUAL <- '&=' skip -ASTERISK <- '*' ![*%=|] skip -ASTERISKEQUAL <- '*=' skip -ASTERISKPERCENT <- '*%' ![=] skip -ASTERISKPERCENTEQUAL <- '*%=' skip -ASTERISKPIPE <- '*|' ![=] skip -ASTERISKPIPEEQUAL <- '*|=' skip -CARET <- '^' ![=] skip -CARETEQUAL <- '^=' skip -COLON <- ':' skip -COMMA <- ',' skip -DOT <- '.' ![*.?] skip -DOT2 <- '..' ![.] skip -DOT3 <- '...' skip -DOTASTERISK <- '.*' skip -DOTQUESTIONMARK <- '.?' skip -EQUAL <- '=' ![>=] skip -EQUALEQUAL <- '==' skip -EQUALRARROW <- '=>' skip -EXCLAMATIONMARK <- '!' ![=] skip -EXCLAMATIONMARKEQUAL <- '!=' skip -LARROW <- '<' ![<=] skip -LARROW2 <- '<<' ![=|] skip -LARROW2EQUAL <- '<<=' skip -LARROW2PIPE <- '<<|' ![=] skip -LARROW2PIPEEQUAL <- '<<|=' skip -LARROWEQUAL <- '<=' skip -LBRACE <- '{' skip -LBRACKET <- '[' skip -LPAREN <- '(' skip -MINUS <- '-' ![%=>|] skip -MINUSEQUAL <- '-=' skip -MINUSPERCENT <- '-%' ![=] skip -MINUSPERCENTEQUAL <- '-%=' skip -MINUSPIPE <- '-|' ![=] skip -MINUSPIPEEQUAL <- '-|=' skip -MINUSRARROW <- '->' skip -PERCENT <- '%' ![=] skip -PERCENTEQUAL <- '%=' skip -PIPE <- '|' ![|=] skip -PIPE2 <- '||' skip -PIPEEQUAL <- '|=' skip -PLUS <- '+' ![%+=|] skip -PLUS2 <- '++' skip -PLUSEQUAL <- '+=' skip -PLUSPERCENT <- '+%' ![=] skip -PLUSPERCENTEQUAL <- '+%=' skip -PLUSPIPE <- '+|' ![=] skip -PLUSPIPEEQUAL <- '+|=' skip -LETTERC <- 'c' skip -QUESTIONMARK <- '?' skip -RARROW <- '>' ![>=] skip -RARROW2 <- '>>' ![=] skip -RARROW2EQUAL <- '>>=' skip -RARROWEQUAL <- '>=' skip -RBRACE <- '}' skip -RBRACKET <- ']' skip -RPAREN <- ')' skip -SEMICOLON <- ';' skip -SLASH <- '/' ![=] skip -SLASHEQUAL <- '/=' skip -TILDE <- '~' skip - -end_of_word <- ![a-zA-Z0-9_] skip -KEYWORD_addrspace <- 'addrspace' end_of_word -KEYWORD_align <- 'align' end_of_word -KEYWORD_allowzero <- 'allowzero' end_of_word -KEYWORD_and <- 'and' end_of_word -KEYWORD_anyframe <- 'anyframe' end_of_word -KEYWORD_anytype <- 'anytype' end_of_word -KEYWORD_asm <- 'asm' end_of_word -KEYWORD_break <- 'break' end_of_word -KEYWORD_callconv <- 'callconv' end_of_word -KEYWORD_catch <- 'catch' end_of_word -KEYWORD_comptime <- 'comptime' end_of_word -KEYWORD_const <- 'const' end_of_word -KEYWORD_continue <- 'continue' end_of_word -KEYWORD_defer <- 'defer' end_of_word -KEYWORD_else <- 'else' end_of_word -KEYWORD_enum <- 'enum' end_of_word -KEYWORD_errdefer <- 'errdefer' end_of_word -KEYWORD_error <- 'error' end_of_word -KEYWORD_export <- 'export' end_of_word -KEYWORD_extern <- 'extern' end_of_word -KEYWORD_fn <- 'fn' end_of_word -KEYWORD_for <- 'for' end_of_word -KEYWORD_if <- 'if' end_of_word -KEYWORD_inline <- 'inline' end_of_word -KEYWORD_noalias <- 'noalias' end_of_word -KEYWORD_nosuspend <- 'nosuspend' end_of_word -KEYWORD_noinline <- 'noinline' end_of_word -KEYWORD_opaque <- 'opaque' end_of_word -KEYWORD_or <- 'or' end_of_word -KEYWORD_orelse <- 'orelse' end_of_word -KEYWORD_packed <- 'packed' end_of_word -KEYWORD_pub <- 'pub' end_of_word -KEYWORD_resume <- 'resume' end_of_word -KEYWORD_return <- 'return' end_of_word -KEYWORD_linksection <- 'linksection' end_of_word -KEYWORD_struct <- 'struct' end_of_word -KEYWORD_suspend <- 'suspend' end_of_word -KEYWORD_switch <- 'switch' end_of_word -KEYWORD_test <- 'test' end_of_word -KEYWORD_threadlocal <- 'threadlocal' end_of_word -KEYWORD_try <- 'try' end_of_word -KEYWORD_union <- 'union' end_of_word -KEYWORD_unreachable <- 'unreachable' end_of_word -KEYWORD_var <- 'var' end_of_word -KEYWORD_volatile <- 'volatile' end_of_word -KEYWORD_while <- 'while' end_of_word - -keyword <- KEYWORD_addrspace / KEYWORD_align / KEYWORD_allowzero / KEYWORD_and - / KEYWORD_anyframe / KEYWORD_anytype / KEYWORD_asm - / KEYWORD_break / KEYWORD_callconv / KEYWORD_catch - / KEYWORD_comptime / KEYWORD_const / KEYWORD_continue / KEYWORD_defer - / KEYWORD_else / KEYWORD_enum / KEYWORD_errdefer / KEYWORD_error / KEYWORD_export - / KEYWORD_extern / KEYWORD_fn / KEYWORD_for / KEYWORD_if - / KEYWORD_inline / KEYWORD_noalias / KEYWORD_nosuspend / KEYWORD_noinline - / KEYWORD_opaque / KEYWORD_or / KEYWORD_orelse / KEYWORD_packed - / KEYWORD_pub / KEYWORD_resume / KEYWORD_return / KEYWORD_linksection - / KEYWORD_struct / KEYWORD_suspend / KEYWORD_switch / KEYWORD_test - / KEYWORD_threadlocal / KEYWORD_try / KEYWORD_union / KEYWORD_unreachable - / KEYWORD_var / KEYWORD_volatile / KEYWORD_while - {#end_syntax_block#} + {#grammar#} {#header_close#} {#header_open|Zen#}
    • Communicate intent precisely.
    • Edge cases matter.
    • Favor reading code over writing code.
    • -
    • Only one obvious way to do things.
    • +
    • There is an idiomatic way to do it.
    • Runtime crashes are better than bugs.
    • Compile errors are better than runtime crashes.
    • Incremental improvements.
    • Avoid local maximums.
    • Reduce the amount one must remember.
    • -
    • Focus on code rather than style.
    • -
    • Resource allocation may fail; resource deallocation must succeed.
    • -
    • Memory is a resource.
    • -
    • Together we serve the users.
    • +
    • Focus on logic, not style.
    • +
    • Resource allocation may fail.
    • +
    • Resource deallocation must succeed.
    +

    Together, we serve the users!

    {#header_close#} {#header_close#} From d97d8b3944cd34d64f9886a444248a8e8d379089 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 3 Sep 2026 23:09:22 +0200 Subject: [PATCH 06/14] update build system code --- build.zig | 164 +++++++++++++++++++------------ tests/add_analysis_cases.zig | 31 +++--- tests/add_build_runner_cases.zig | 33 ++++--- 3 files changed, 130 insertions(+), 98 deletions(-) diff --git a/build.zig b/build.zig index b3f0a6f38f..2ded4914b5 100644 --- a/build.zig +++ b/build.zig @@ -65,9 +65,9 @@ pub fn build(b: *Build) !void { const test_options = b.addOptions(); test_options.step.name = "ZLS test options"; - test_options.addOptionPath("zig_exe_path", .{ .cwd_relative = b.graph.zig_exe }); - test_options.addOptionPath("zig_lib_path", .{ .cwd_relative = b.fmt("{f}", .{b.graph.zig_lib_directory}) }); - test_options.addOptionPath("global_cache_path", .{ .cwd_relative = b.cache_root.join(b.allocator, &.{"zls"}) catch @panic("OOM") }); + test_options.addOptionPath("zig_exe_path", .zig_exe); + test_options.addOptionPath("zig_lib_path", .zig_lib); + test_options.addOptionPath("global_cache_path", std.Build.LazyPath.cache_root.join(b.allocator, "zls") catch @panic("OOM")); break :blk test_options.createModule(); }; @@ -115,18 +115,22 @@ pub fn build(b: *Build) !void { { // zig build gen const gen_step = b.step("gen", "Regenerate config files"); + const update_source = b.addUpdateSourceFiles(); + gen_step.dependOn(&update_source.step); + const gen_cmd = b.addRunArtifact(gen_exe); - if (b.args) |args| { - gen_cmd.addArgs(args); - gen_step.dependOn(&gen_cmd.step); - } else { - const update_source = b.addUpdateSourceFiles(); - gen_cmd.addArg("--generate-config"); - update_source.addCopyFileToSource(gen_cmd.addOutputFileArg("Config.zig"), "src/Config.zig"); - gen_cmd.addArg("--generate-schema"); - update_source.addCopyFileToSource(gen_cmd.addOutputFileArg("schema.json"), "schema.json"); - gen_step.dependOn(&update_source.step); - } + gen_cmd.addArg("--generate-config"); + update_source.addCopyFileToSource(gen_cmd.addOutputFileArg("Config.zig"), "src/Config.zig"); + gen_cmd.addArg("--generate-schema"); + update_source.addCopyFileToSource(gen_cmd.addOutputFileArg("schema.json"), "schema.json"); + gen_cmd.addPassthruArgs(); + } + + { // zig build config-gen + const gen_step = b.step("config-gen", "Run src/tools/config_gen.zig"); + const gen_cmd = b.addRunArtifact(gen_exe); + gen_cmd.addPassthruArgs(); + gen_step.dependOn(&gen_cmd.step); } { // zig build release @@ -168,7 +172,7 @@ pub fn build(b: *Build) !void { artifact.* = b.addExecutable(.{ .name = "zls", .root_module = exe_module, - .max_rss = if (optimize == .Debug and target_query.os_tag == .wasi) 2_600_000_000 else 2_000_000_000, + .max_rss = if (optimize == .debug and target_query.os_tag == .wasi) 2_600_000_000 else 2_000_000_000, .use_llvm = use_llvm, .use_lld = use_llvm, }); @@ -252,20 +256,6 @@ pub fn build(b: *Build) !void { .use_lld = use_llvm, }); - if (target.result.cpu.arch.isWasm() and b.enable_wasmtime) { - // Zig's build system integration with wasmtime does not support adding custom preopen directories so it is done manually. - const args: []const ?[]const u8 = &.{ - "wasmtime", - "--dir=.", - b.fmt("--dir={f}::/lib", .{b.graph.zig_lib_directory}), - b.fmt("--dir={s}::/cache", .{b.cache_root.join(b.allocator, &.{"zls"}) catch @panic("OOM")}), - "--", - null, - }; - tests.setExecCmd(args); - src_tests.setExecCmd(args); - } - blk: { // zig build test, zig build test-build-runner, zig build test-analysis const test_step = b.step("test", "Run all the tests"); const test_build_runner_step = b.step("test-build-runner", "Run all the build runner tests"); @@ -278,8 +268,10 @@ pub fn build(b: *Build) !void { const run_tests = b.addRunArtifact(tests); const run_src_tests = b.addRunArtifact(src_tests); - run_tests.skip_foreign_checks = target.result.cpu.arch.isWasm() and b.enable_wasmtime; - run_src_tests.skip_foreign_checks = target.result.cpu.arch.isWasm() and b.enable_wasmtime; + for ([_]*Build.Step.Run{ run_tests, run_src_tests }) |run| { + run.setPreopen("/lib", .zig_lib); + run.setPreopen("/cache", Build.LazyPath.cache_root.join(b.allocator, "zls") catch @panic("OOM")); + } // Setup dependencies of `zig build test` test_step.dependOn(&run_tests.step); @@ -300,21 +292,23 @@ pub fn build(b: *Build) !void { run_test_steps.append(b.allocator, step.cast(std.Build.Step.Run).?) catch @panic("OOM"); } - const kcov_bin = b.findProgram(&.{"kcov"}, &.{}) catch "kcov"; + const kcov_bin = b.findProgramLazy(.{ .names = &.{"kcov"} }); const merge_step = std.Build.Step.Run.create(b, "merge coverage"); - merge_step.addArgs(&.{ kcov_bin, "--merge" }); + merge_step.addFileArg(kcov_bin); + merge_step.addArg("--merge"); merge_step.rename_step_with_output_arg = false; - const merged_coverage_output = merge_step.addOutputFileArg("."); + const merged_coverage_output = merge_step.addOutputFileArg2(".", .{}); for (run_test_steps.items) |run_step| { run_step.setName(b.fmt("{s} (collect coverage)", .{run_step.step.name})); // prepend the kcov exec args const argv = run_step.argv.toOwnedSlice(b.allocator) catch @panic("OOM"); - run_step.addArgs(&.{ kcov_bin, "--collect-only" }); - run_step.addPrefixedDirectoryArg("--include-pattern=", b.path("src")); - merge_step.addDirectoryArg(run_step.addOutputFileArg(run_step.producer.?.name)); + run_step.addFileArg(kcov_bin); + run_step.addArg("--collect-only"); + run_step.addDirectoryArg2(b.path("src"), .{ .prefix = "--include-pattern=" }); + merge_step.addDirectoryArg(run_step.addOutputFileArg2(run_step.producer.?.name, .{})); run_step.argv.appendSlice(b.allocator, argv) catch @panic("OOM"); } @@ -327,7 +321,6 @@ pub fn build(b: *Build) !void { } } -/// Returns `MAJOR.MINOR.PATCH-dev` when `git describe` failed. fn getVersion(b: *Build) std.SemanticVersion { const version_string = b.option([]const u8, "version-string", "Override the version of this build. Must be a semantic version."); if (version_string) |semver_string| { @@ -338,27 +331,68 @@ fn getVersion(b: *Build) std.SemanticVersion { if (zls_version.pre == null) return zls_version; + // Ensure git version changes get picked up. + git: { + const io = b.graph.io; + const git_file = b.root.openFile(io, ".git", .{ .allow_directory = false }) catch |err| switch (err) { + error.IsDir => { + b.dependOnFileContents(b.path(".git/logs/HEAD")); + break :git; + }, + error.FileNotFound => { + // Need a way to indicate that the build.zig logic depends on the absence of a file. + b.graph.poisonCache(); + break :git; + }, + else => |e| std.process.fatal("Failed to read '.git' directory: {t}", .{e}), + }; + defer git_file.close(io); + var line_buffer: ["gitdir: ".len + std.Io.Dir.max_path_bytes + 1]u8 = undefined; + var git_file_reader = git_file.reader(io, &line_buffer); + if (std.mem.cutPrefix(u8, std.mem.trimEnd(u8, git_file_reader.interface.allocRemaining( + b.graph.arena, + .limited("gitdir: ".len + std.Io.Dir.max_path_bytes + "\r\n".len), + ) catch |e| @panic(@errorName(e)), "\r\n"), "gitdir: ")) |git_dir| { + const head_file = b.pathJoin(&.{ git_dir, "logs", "HEAD" }); + b.dependOnFileContents(if (std.Io.Dir.path.isAbsolute(head_file)) + b.graph.cwdRelativePath(head_file) + else + b.path(head_file)); + } + } + + if (!std.process.can_spawn) { + std.process.fatal("version info cannot be retrieved from git. ZLS version must be provided using -Dversion-string", .{}); + } + + const root_path = b.fmt("{f}", .{b.root}); const argv: []const []const u8 = &.{ - "git", "-C", b.pathFromRoot("."), "--git-dir", ".git", "describe", "--match", "*.*.*", "--tags", + "git", "-C", root_path, "--git-dir", ".git", "describe", "--match", "*.*.*", "--tags", }; - var code: u8 = undefined; - const git_describe_untrimmed = b.runAllowFail(argv, &code, .ignore) catch |err| { - const argv_joined = std.mem.join(b.allocator, " ", argv) catch @panic("OOM"); + const run_result = b.runFallible(argv, .{ .stderr_behavior = .ignore }); + const git_describe = switch (run_result) { + .success => |stdout| std.mem.trim(u8, stdout, " \n\r"), + .spawn_failed => |err| err, + .bad_exit_code => error.BadExitCode, + .crashed => error.Crashed, + } catch |err| { + b.graph.poisonCache(); + const cmd: std.zig.SubprocessCommand = .{ .argv = argv, .cwd = root_path }; std.log.warn( - \\Failed to run git describe to resolve ZLS version: {} - \\command: {s} + \\Failed to run 'git describe' to resolve ZLS version: {t} + \\command: {f} \\ \\Consider passing the -Dversion-string flag to specify the ZLS version. - , .{ err, argv_joined }); + , .{ err, cmd }); return zls_version; }; - const git_describe = std.mem.trim(u8, git_describe_untrimmed, " \n\r"); - - switch (std.mem.count(u8, git_describe, "-")) { + switch (std.mem.countScalar(u8, git_describe, '-')) { 0 => { // Tagged release version (e.g. 0.10.0). - std.debug.assert(std.mem.eql(u8, git_describe, b.fmt("{f}", .{zls_version}))); // tagged release must match version string + if (!std.mem.eql(u8, git_describe, b.fmt("{f}", .{zls_version}))) { + std.process.fatal("ZLS version '{f}' does not match Git tag '{s}'", .{ zls_version, git_describe }); + } return zls_version; }, 2 => { @@ -368,9 +402,18 @@ fn getVersion(b: *Build) std.SemanticVersion { const commit_height = it.next().?; const commit_id = it.next().?; - const ancestor_ver = std.SemanticVersion.parse(tagged_ancestor) catch unreachable; - std.debug.assert(zls_version.order(ancestor_ver) == .gt); // ZLS version must be greater than its previous version - std.debug.assert(std.mem.startsWith(u8, commit_id, "g")); // commit hash is prefixed with a 'g' + if (std.SemanticVersion.parse(tagged_ancestor)) |ancestor_ver| { + if (zls_version.order(ancestor_ver) != .gt) { + std.process.fatal("ZLS version '{f}' must be greater than tagged ancestor '{f}'", .{ zls_version, ancestor_ver }); + } + } else |_| { + // Ignore tagged ancestor if it's not a semantic version. + } + + // Check that the commit hash is prefixed with a 'g' (a Git convention). + if (!std.mem.startsWith(u8, commit_id, "g")) { + std.process.fatal("Unexpected `git describe` output: {s}", .{git_describe}); + } return .{ .major = zls_version.major, @@ -380,10 +423,7 @@ fn getVersion(b: *Build) std.SemanticVersion { .build = commit_id[1..], }; }, - else => { - std.debug.print("Unexpected 'git describe' output: '{s}'\n", .{git_describe}); - std.process.exit(1); - }, + else => std.process.fatal("Unexpected 'git describe' output: '{s}'", .{git_describe}), } } @@ -485,7 +525,7 @@ fn release(b: *Build, release_artifacts: []const *Build.Step.Compile, released_z const release_minisign = b.option(bool, "release-minisign", "Sign release artifacts with Minisign") orelse false; if (released_zls_version.pre != null and released_zls_version.build == null) { - release_step.addError("Cannot build release because the ZLS version could not be resolved", .{}) catch @panic("OOM"); + release_step.dependOn(&b.addFail("Cannot build release because the ZLS version could not be resolved").step); return; } @@ -522,7 +562,7 @@ fn release(b: *Build, release_artifacts: []const *Build.Step.Compile, released_z switch (extension) { .zip => { compress_cmd.addArgs(&.{ "7z", "a", "-mx=9" }); - compressed_artifacts.putNoClobber(b.allocator, file_name, compress_cmd.addOutputFileArg(file_name)) catch @panic("OOM"); + compressed_artifacts.putNoClobber(b.allocator, file_name, compress_cmd.addOutputFileArg2(file_name, .{})) catch @panic("OOM"); compress_cmd.addArtifactArg(exe); compress_cmd.addFileArg(exe.getEmittedPdb()); compress_cmd.addFileArg(b.path("LICENSE")); @@ -534,10 +574,10 @@ fn release(b: *Build, release_artifacts: []const *Build.Step.Compile, released_z compress_cmd.setEnvironmentVariable("XZ_OPT", "-9"); compress_cmd.addArgs(&.{ "tar", "caf" }); compressed_artifacts.putNoClobber(b.allocator, file_name, compress_cmd.addOutputFileArg(file_name)) catch @panic("OOM"); - compress_cmd.addPrefixedDirectoryArg("-C", exe.getEmittedBinDirectory()); + compress_cmd.addDirectoryArg2(exe.getEmittedBinDirectory(), .{ .prefix = "-C" }); compress_cmd.addArg(exe_name); - compress_cmd.addPrefixedDirectoryArg("-C", b.path(".")); + compress_cmd.addDirectoryArg2(b.path("."), .{ .prefix = "-C", .make_absolute = true }); compress_cmd.addArg("LICENSE"); compress_cmd.addArg("README.md"); @@ -565,8 +605,8 @@ fn release(b: *Build, release_artifacts: []const *Build.Step.Compile, released_z const minising_cmd = b.addSystemCommand(&.{ "minisign", "-Sm" }); minising_cmd.clearEnvironment(); minising_cmd.addFileArg(file_path); - minising_cmd.addPrefixedFileArg("-s", .{ .cwd_relative = "minisign.key" }); - const minising_file_path = minising_cmd.addPrefixedOutputFileArg("-x", minisign_basename); + minising_cmd.addFileArg2(b.graph.cwdRelativePath("minisign.key"), .{ .prefix = "-s" }); + const minising_file_path = minising_cmd.addOutputFileArg2(minisign_basename, .{ .prefix = "-x" }); const install_minising = b.addInstallFileWithDir(minising_file_path, install_dir, minisign_basename); release_step.dependOn(&install_minising.step); diff --git a/tests/add_analysis_cases.zig b/tests/add_analysis_cases.zig index cd436f6c48..c01284ad11 100644 --- a/tests/add_analysis_cases.zig +++ b/tests/add_analysis_cases.zig @@ -10,8 +10,7 @@ pub fn addCases( test_step: *std.Build.Step, test_filters: []const []const u8, ) void { - const cases_dir = b.path("tests/analysis"); - const cases_path_from_root = b.pathFromRoot("tests/analysis"); + const cases_path = "tests/analysis"; const check_exe = b.addExecutable(.{ .name = "analysis_check", @@ -25,16 +24,17 @@ pub fn addCases( }), }); - // https://github.com/ziglang/zig/issues/20605 - var dir = std.Io.Dir.cwd().openDir(b.graph.io, cases_path_from_root, .{ .iterate = true }) catch |err| - std.debug.panic("failed to open '{s}': {}", .{ cases_path_from_root, err }); + b.dependOnDirectory(b.path(cases_path)); + + var dir = b.root.openDir(b.graph.io, cases_path, .{ .iterate = true }) catch |err| + std.debug.panic("failed to open '{f}': {}", .{ b.path(cases_path), err }); defer dir.close(b.graph.io); var it = dir.iterate(); while (true) { const entry = it.next(b.graph.io) catch |err| - std.debug.panic("failed to walk directory '{s}': {}", .{ cases_path_from_root, err }) orelse break; + std.debug.panic("failed to walk directory '{f}': {}", .{ b.path(cases_path), err }) orelse break; if (entry.kind != .file) continue; if (!std.mem.eql(u8, std.Io.Dir.path.extension(entry.name), ".zig")) continue; @@ -46,32 +46,23 @@ pub fn addCases( const run_check = std.Build.Step.Run.create(b, b.fmt("run analysis on {s}", .{entry.name})); run_check.producer = check_exe; - if (target.result.cpu.arch.isWasm() and b.enable_wasmtime) { - run_check.skip_foreign_checks = true; - run_check.addArgs(&.{ - "wasmtime", - "--dir=.", - b.fmt("--dir={f}::/lib", .{b.graph.zig_lib_directory}), - "--", - }); - } - run_check.addArtifactArg(check_exe); if (target.query.eql(b.graph.host.query)) { run_check.addArg("--zig-exe-path"); - run_check.addFileArg(.{ .cwd_relative = b.graph.zig_exe }); + run_check.addFileArg(.zig_exe); } + run_check.setPreopen("/lib", .zig_lib); if (!target.result.cpu.arch.isWasm()) { run_check.addArg("--zig-lib-path"); - run_check.addDirectoryArg(.{ .cwd_relative = b.fmt("{f}", .{b.graph.zig_lib_directory}) }); + run_check.addDirectoryArg(.zig_lib); } - const input_file = cases_dir.path(b, entry.name); + const input_file = b.path(cases_path).path(b, entry.name); if (!target.result.cpu.arch.isWasm()) { run_check.addFileArg(input_file); } else { // pass a relative file path when running with wasmtime - run_check.setCwd(cases_dir); + run_check.setCwd(b.path(cases_path)); run_check.addArg(entry.name); run_check.addFileInput(input_file); } diff --git a/tests/add_build_runner_cases.zig b/tests/add_build_runner_cases.zig index 94a661774d..7e19e92308 100644 --- a/tests/add_build_runner_cases.zig +++ b/tests/add_build_runner_cases.zig @@ -8,8 +8,9 @@ pub fn addCases( test_step: *std.Build.Step, test_filters: []const []const u8, ) void { - const cases_dir = b.path("tests/build_runner_cases"); - const cases_path_from_root = b.pathFromRoot("tests/build_runner_cases"); + const cases_path = "tests/build_runner_cases"; + + const global_cache: std.Build.LazyPath = .{ .relative = .{ .base = .global_cache } }; const check_exe = b.addExecutable(.{ .name = "build_runner_check", @@ -22,16 +23,17 @@ pub fn addCases( }), }); - // https://github.com/ziglang/zig/issues/20605 - var dir = std.Io.Dir.cwd().openDir(b.graph.io, cases_path_from_root, .{ .iterate = true }) catch |err| - std.debug.panic("failed to open '{s}': {}", .{ cases_path_from_root, err }); + b.dependOnDirectory(b.path(cases_path)); + + var dir = b.root.openDir(b.graph.io, cases_path, .{ .iterate = true }) catch |err| + std.debug.panic("failed to open '{f}': {}", .{ b.path(cases_path), err }); defer dir.close(b.graph.io); var it = dir.iterate(); while (true) { const entry = it.next(b.graph.io) catch |err| - std.debug.panic("failed to walk directory '{s}': {}", .{ cases_path_from_root, err }) orelse break; + std.debug.panic("failed to walk directory '{f}': {}", .{ b.path(cases_path), err }) orelse break; if (entry.kind != .file) continue; if (!std.mem.eql(u8, std.Io.Dir.path.extension(entry.name), ".zig")) continue; @@ -40,23 +42,22 @@ pub fn addCases( if (std.mem.find(u8, entry.name, test_filter) != null) break; } else if (test_filters.len > 0) continue; - const build_file = cases_dir.path(b, entry.name); - const build_config_json_path = b.fmt("{s}/{s}.json", .{ cases_path_from_root, std.Io.Dir.path.stem(entry.name) }); - const expected_build_config_json = cases_dir.path(b, build_config_json_path); + const build_file = b.path(cases_path).path(b, entry.name); + const expected_build_config_json = b.path(cases_path).path(b, std.Io.Dir.path.stem(entry.name)); const build_cmd = std.Build.Step.Run.create(b, b.fmt("run build runner ({s})", .{entry.name})); - build_cmd.addFileArg(.{ .cwd_relative = b.graph.zig_exe }); + build_cmd.addFileArg(.zig_exe); build_cmd.addArg("build"); build_cmd.addArg("--build-file"); build_cmd.addFileArg(build_file); build_cmd.addArg("--build-runner"); build_cmd.addFileArg(b.path("src/build_runner/build_runner.zig")); build_cmd.addArg("--cache-dir"); - build_cmd.addDirectoryArg(.{ .cwd_relative = b.fmt("{f}", .{b.cache_root}) }); + build_cmd.addDirectoryArg(.cache_root); build_cmd.addArg("--global-cache-dir"); - build_cmd.addDirectoryArg(.{ .cwd_relative = b.fmt("{f}", .{b.graph.global_cache_root}) }); + build_cmd.addDirectoryArg(global_cache); build_cmd.addArg("--zig-lib-dir"); - build_cmd.addDirectoryArg(.{ .cwd_relative = b.fmt("{f}", .{b.graph.zig_lib_directory}) }); + build_cmd.addDirectoryArg(.zig_lib); build_cmd.addFileInput(b.path("src/build_runner/shared.zig")); @@ -64,13 +65,13 @@ pub fn addCases( const run_diff = b.addRunArtifact(check_exe); run_diff.setName(b.fmt("run {s} ({s})", .{ check_exe.name, entry.name })); - run_diff.setCwd(cases_dir); + run_diff.setCwd(b.path(cases_path)); run_diff.addFileArg(expected_build_config_json); run_diff.addFileArg(actual_build_config_json); run_diff.addArg("--cache-dir"); - run_diff.addDirectoryArg(.{ .cwd_relative = b.fmt("{f}", .{b.cache_root}) }); + run_diff.addDirectoryArg(.cache_root); run_diff.addArg("--global-cache-dir"); - run_diff.addDirectoryArg(.{ .cwd_relative = b.fmt("{f}", .{b.graph.global_cache_root}) }); + run_diff.addDirectoryArg(global_cache); test_step.dependOn(&run_diff.step); } From 3501ad77d3989a1fd94f10b3dec49f3c2484ba92 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 3 Sep 2026 22:40:33 +0200 Subject: [PATCH 07/14] update to latest Zig version --- src/DiagnosticsCollection.zig | 14 +++++----- src/DocumentScope.zig | 6 ++--- src/DocumentStore.zig | 4 ++- src/Server.zig | 8 +++--- src/TrigramStore.zig | 4 +-- src/analyser/InternPool.zig | 40 ++++++++++++++--------------- src/analyser/error_msg.zig | 1 + src/analyser/segmented_list.zig | 2 +- src/ast.zig | 41 ++++++++++++++++++++++-------- src/configuration.zig | 36 +++++++++++++------------- src/features/code_actions.zig | 9 +++---- src/features/diagnostics.zig | 8 +++--- src/features/document_symbol.zig | 4 +-- src/features/inlay_hints.zig | 4 +++ src/features/semantic_tokens.zig | 6 ++--- src/main.zig | 2 +- src/offsets.zig | 8 +++--- src/print_ast.zig | 8 +++--- src/tools/config_gen.zig | 2 +- tests/ErrorBuilder.zig | 2 +- tests/analysis/builtins.zig | 12 ++++----- tests/lsp_features/completion.zig | 3 ++- tests/lsp_features/diagnostics.zig | 16 ++++++------ tests/utility/ast.zig | 2 +- tests/utility/position_context.zig | 2 +- 25 files changed, 136 insertions(+), 108 deletions(-) diff --git a/src/DiagnosticsCollection.zig b/src/DiagnosticsCollection.zig index a6184b0c5f..5e89cebbb0 100644 --- a/src/DiagnosticsCollection.zig +++ b/src/DiagnosticsCollection.zig @@ -404,7 +404,7 @@ fn convertErrorBundleToLSPDiangostics( .range = src_range, .severity = .Error, .source = "zls", - .message = message, + .message = .{ .string = message }, .tags = if (tags.items.len != 0) tags.items else null, .relatedInformation = relatedInformation, }); @@ -517,7 +517,7 @@ test DiagnosticsCollection { try std.testing.expectEqual(1, diagnostics.items.len); try std.testing.expectEqual(lsp.types.Diagnostic.Severity.Error, diagnostics.items[0].severity); - try std.testing.expectEqualStrings("Living For The City", diagnostics.items[0].message); + try std.testing.expectEqualStrings("Living For The City", diagnostics.items[0].message.string); try std.testing.expectEqual(null, diagnostics.items[0].relatedInformation); } @@ -528,7 +528,7 @@ test DiagnosticsCollection { try collection.collectLspDiagnosticsForDocument(uri, .@"utf-8", arena, &diagnostics); try std.testing.expectEqual(1, diagnostics.items.len); - try std.testing.expectEqualStrings("Living For The City", diagnostics.items[0].message); + try std.testing.expectEqualStrings("Living For The City", diagnostics.items[0].message.string); } { @@ -538,7 +538,7 @@ test DiagnosticsCollection { try collection.collectLspDiagnosticsForDocument(uri, .@"utf-8", arena, &diagnostics); try std.testing.expectEqual(1, diagnostics.items.len); - try std.testing.expectEqualStrings("You Haven't Done Nothin'", diagnostics.items[0].message); + try std.testing.expectEqualStrings("You Haven't Done Nothin'", diagnostics.items[0].message.string); } { @@ -558,8 +558,8 @@ test DiagnosticsCollection { try collection.collectLspDiagnosticsForDocument(uri, .@"utf-8", arena, &diagnostics); try std.testing.expectEqual(2, diagnostics.items.len); - try std.testing.expectEqualStrings("You Haven't Done Nothin'", diagnostics.items[0].message); - try std.testing.expectEqualStrings("As", diagnostics.items[1].message); + try std.testing.expectEqualStrings("You Haven't Done Nothin'", diagnostics.items[0].message.string); + try std.testing.expectEqualStrings("As", diagnostics.items[1].message.string); } } @@ -596,7 +596,7 @@ test "DiagnosticsCollection - compile_log_text" { \\Compile Log Output: \\@as(comptime_int, 7) \\@as(comptime_int, 13) - , diagnostics.items[0].message); + , diagnostics.items[0].message.string); try std.testing.expectEqual(null, diagnostics.items[0].relatedInformation); } diff --git a/src/DocumentScope.zig b/src/DocumentScope.zig index f23c8ba508..a6ca091e9e 100644 --- a/src/DocumentScope.zig +++ b/src/DocumentScope.zig @@ -98,8 +98,8 @@ pub const Declaration = union(enum) { error_token: Ast.TokenIndex, comptime { - for (std.meta.fields(Declaration)) |field| { - std.debug.assert(@sizeOf(field.type) <= 8); // a Declaration without the union tag must be less than 8 bytes + for (std.meta.fieldTypes(Declaration)) |T| { + std.debug.assert(@sizeOf(T) <= 8); // a Declaration without the union tag must be less than 8 bytes } } @@ -340,7 +340,7 @@ const ScopeContext = struct { if (std.mem.eql(u8, name, "_")) return; defer std.debug.assert(pushed.context.doc_scope.declarations.len == pushed.context.doc_scope.declaration_lookup_map.count()); - if (@import("builtin").mode == .Debug) { + if (@import("builtin").mode == .debug) { // Check that nameToken works std.debug.assert(identifier_token == declaration.nameToken(pushed.context.tree)); } diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index cc5fd07ed6..3e7f4f5bfc 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -449,7 +449,9 @@ pub const Handle = struct { const tracy_zone = tracy.traceNamed(@src(), "Ast.parse"); defer tracy_zone.end(); - var tree = try Ast.parse(allocator, new_text, mode); + var tree = try Ast.parse(allocator, new_text, .{ + .mode = mode, + }); errdefer tree.deinit(allocator); // remove unused capacity diff --git a/src/Server.zig b/src/Server.zig index 94e87e9de8..18b3c4d07c 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -983,11 +983,11 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void server.showMessage(.Error, "{s}", .{msg}); } - inline for (std.meta.fields(Config)) |field| { - if (@field(result.did_change, field.name)) { + inline for (comptime std.meta.fieldNames(Config)) |field_name| { + if (@field(result.did_change, field_name)) { var runtime_known_field_name: []const u8 = ""; // avoid unnecessary function instantiations of `std.Io.Writer.print` - runtime_known_field_name = field.name; - const new_value = @field(server.config_manager.config, field.name); + runtime_known_field_name = field_name; + const new_value = @field(server.config_manager.config, field_name); log.info("Set config option '{s}' to {f}", .{ runtime_known_field_name, std.json.fmt(new_value, .{}) }); } } diff --git a/src/TrigramStore.zig b/src/TrigramStore.zig index 87e7eecefb..57a230014b 100644 --- a/src/TrigramStore.zig +++ b/src/TrigramStore.zig @@ -96,7 +96,7 @@ pub fn init( .simple_var_decl, .aligned_var_decl, => { - const in_function = in_function_stack.getLast() orelse false; + const in_function = in_function_stack.last() orelse false; if (in_function) continue; const main_token = tree.nodeMainToken(node); @@ -297,7 +297,7 @@ fn appendOneTrigram( const gop = try store.trigram_to_declarations.getOrPutValue(allocator, trigram, .empty); - if (gop.value_ptr.getLast() != declaration_index) { + if (gop.value_ptr.last() != declaration_index) { try gop.value_ptr.append(allocator, declaration_index); } } diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 19e0817d31..44950930bb 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -1144,7 +1144,7 @@ pub fn init(io: std.Io, gpa: Allocator) Allocator.Error!InternPool { try ip.map.ensureTotalCapacity(gpa, items.len); try ip.items.ensureTotalCapacity(gpa, items.len); - if (builtin.is_test or builtin.mode == .Debug) { + if (builtin.is_test or builtin.mode == .debug) { // detect wrong value for extra_count try ip.extra.ensureTotalCapacityPrecise(gpa, extra_count); } else { @@ -1546,9 +1546,9 @@ fn addExtra(ip: *InternPool, comptime T: type, extra: T) Allocator.Error!u32 { const size = @divExact(@sizeOf(T), 4); try ip.extra.ensureUnusedCapacity(ip.gpa, size); - inline for (std.meta.fields(T)) |field| { - const item = @field(extra, field.name); - switch (field.type) { + inline for (comptime std.meta.fieldNames(T), comptime std.meta.fieldTypes(T)) |field_name, FieldType| { + const item = @field(extra, field_name); + switch (FieldType) { Index, Decl.Index, Decl.OptionalIndex, @@ -1574,7 +1574,7 @@ fn addExtra(ip: *InternPool, comptime T: type, extra: T) Allocator.Error!u32 { LimbSlice, => ip.extra.appendSliceAssumeCapacity(&.{ item.start, item.len }), - else => @compileError("unexpected: " ++ @typeName(field.type)), + else => @compileError("unexpected: " ++ @typeName(FieldType)), } } return result; @@ -1583,10 +1583,10 @@ fn addExtra(ip: *InternPool, comptime T: type, extra: T) Allocator.Error!u32 { fn extraData(ip: *const InternPool, comptime T: type, index: u32) T { var result: T = undefined; var i: u32 = 0; - inline for (std.meta.fields(T)) |field| { + inline for (comptime std.meta.fieldNames(T), comptime std.meta.fieldTypes(T)) |field_name, FieldType| { const item = ip.extra.items[index + i]; i += 1; - @field(result, field.name) = switch (field.type) { + @field(result, field_name) = switch (FieldType) { Index, StringPool.String, StringPool.OptionalString, @@ -1620,7 +1620,7 @@ fn extraData(ip: *const InternPool, comptime T: type, index: u32) T { break :blk .{ .start = item, .len = ip.extra.items[index + i] }; }, - else => @compileError("unexpected: " ++ @typeName(field.type)), + else => @compileError("unexpected: " ++ @typeName(FieldType)), }; } return result; @@ -1894,7 +1894,7 @@ fn coerceInt( } pub fn resolvePeerTypes(ip: *InternPool, types: []const Index, target: std.Target) Allocator.Error!Index { - if (builtin.mode == .Debug) { + if (builtin.mode == .debug) { for (types) |ty| { assert(ip.isType(ty)); } @@ -3325,16 +3325,16 @@ pub fn intInfo(ip: *InternPool, ty: Index, target: std.Target) std.lang.Type.Int .usize_type => return .{ .signedness = .unsigned, .bits = target.ptrBitWidth() }, .isize_type => return .{ .signedness = .signed, .bits = target.ptrBitWidth() }, - .c_char_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.char) }, - .c_short_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.short) }, - .c_ushort_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.ushort) }, - .c_int_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.int) }, - .c_uint_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.uint) }, - .c_long_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.long) }, - .c_ulong_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.ulong) }, - .c_longlong_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.longlong) }, - .c_ulonglong_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.ulonglong) }, - .c_longdouble_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.longdouble) }, + .c_char_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.char).? }, + .c_short_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.short).? }, + .c_ushort_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.ushort).? }, + .c_int_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.int).? }, + .c_uint_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.uint).? }, + .c_long_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.long).? }, + .c_ulong_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.ulong).? }, + .c_longlong_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.longlong).? }, + .c_ulonglong_type => return .{ .signedness = .unsigned, .bits = target.cTypeBitSize(.ulonglong).? }, + .c_longdouble_type => return .{ .signedness = .signed, .bits = target.cTypeBitSize(.longdouble).? }, // TODO revisit this when error sets support custom int types (comment taken from zig codebase) .anyerror_type => return .{ .signedness = .unsigned, .bits = 16 }, @@ -3394,7 +3394,7 @@ pub fn floatBits(ip: *InternPool, ty: Index, target: std.Target) u16 { .f64_type => 64, .f80_type => 80, .f128_type, .comptime_float_type => 128, - .c_longdouble_type => target.cTypeBitSize(.longdouble), + .c_longdouble_type => target.cTypeBitSize(.longdouble).?, else => unreachable, }; diff --git a/src/analyser/error_msg.zig b/src/analyser/error_msg.zig index 78fba2c60c..0f1f0d6857 100644 --- a/src/analyser/error_msg.zig +++ b/src/analyser/error_msg.zig @@ -92,6 +92,7 @@ pub const ErrorMsg = union(enum) { .@"anyframe" => "anyframe", .vector => "vector", .enum_literal => "enum literal", + .spirv => "SPIR-V type", }; try writer.print( "expected {s} type, found '{f}'", diff --git a/src/analyser/segmented_list.zig b/src/analyser/segmented_list.zig index 6b8fd449c0..8771b1f421 100644 --- a/src/analyser/segmented_list.zig +++ b/src/analyser/segmented_list.zig @@ -123,7 +123,7 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type pub const prealloc_count = prealloc_item_count; fn AtType(comptime SelfType: type) type { - if (@typeInfo(SelfType).pointer.is_const) { + if (@typeInfo(SelfType).pointer.attrs.@"const") { return *const T; } else { return *T; diff --git a/src/ast.zig b/src/ast.zig index 6ffa3d70f7..e2a2ba2011 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -22,31 +22,50 @@ fn fullPtrTypeComponents(tree: *const Ast, info: full.PtrType.Components) full.P .allowzero_token = null, .const_token = null, .volatile_token = null, + .duplicate_token = null, .ast = info, }; // We need to be careful that we don't iterate over any sub-expressions // here while looking for modifiers as that could result in false // positives. Therefore, start after a sentinel if there is one and // skip over any align node and bit range nodes. - var i = if (info.sentinel.unwrap()) |sentinel| lastToken(tree, sentinel) + 1 else switch (size) { - .many, .c => info.main_token + 1, - else => info.main_token, - }; + var i = (if (info.sentinel.unwrap()) |sentinel| tree.lastToken(sentinel) + 1 else switch (size) { + .one => info.main_token, + .slice => info.main_token + 1, + .many => info.main_token + 2, + .c => info.main_token + 3, + }) + 1; const end = tree.firstToken(info.child_type); while (i < end) : (i += 1) { switch (tree.tokenTag(i)) { - .keyword_allowzero => result.allowzero_token = i, - .keyword_const => result.const_token = i, - .keyword_volatile => result.volatile_token = i, + .keyword_allowzero => { + if (result.allowzero_token != null) { + result.duplicate_token = i; + } + result.allowzero_token = i; + }, + .keyword_const => { + if (result.const_token != null) { + result.duplicate_token = i; + } + result.const_token = i; + }, + .keyword_volatile => { + if (result.volatile_token != null) { + result.duplicate_token = i; + } + result.volatile_token = i; + }, .keyword_align => { - const align_node = info.align_node.unwrap().?; if (info.bit_range_end.unwrap()) |bit_range_end| { + std.debug.assert(info.bit_range_start != .none); i = lastToken(tree, bit_range_end) + 1; - } else { + } else if (info.align_node.unwrap()) |align_node| { i = lastToken(tree, align_node) + 1; - } + } else unreachable; }, - else => {}, + .keyword_addrspace => i = lastToken(tree, info.addrspace_node.unwrap().?) + 1, + else => unreachable, } } return result; diff --git a/src/configuration.zig b/src/configuration.zig index 0e3c44d9ab..138903bc70 100644 --- a/src/configuration.zig +++ b/src/configuration.zig @@ -97,8 +97,8 @@ pub const Manager = struct { defer manager.impl.arena = arena_allocator.state; var duped: UnresolvedConfig = .{}; - inline for (std.meta.fields(UnresolvedConfig)) |field| { - @field(duped, field.name) = try option.dupe(field.type, @field(config, field.name), arena_allocator.allocator()); + inline for (comptime std.meta.fieldNames(UnresolvedConfig), comptime std.meta.fieldTypes(UnresolvedConfig)) |field_name, FieldType| { + @field(duped, field_name) = try option.dupe(FieldType, @field(config, field_name), arena_allocator.allocator()); } manager.impl.configs.set(tag, duped); manager.impl.is_dirty = true; @@ -111,8 +111,8 @@ pub const Manager = struct { config: *const Config, ) error{OutOfMemory}!void { var cfg: UnresolvedConfig = .{}; - inline for (std.meta.fields(Config)) |field| { - @field(cfg, field.name) = @field(config, field.name); + inline for (comptime std.meta.fieldNames(Config)) |field_name| { + @field(cfg, field_name) = @field(config, field_name); } try manager.setConfiguration(tag, &cfg); } @@ -150,9 +150,9 @@ pub const Manager = struct { .global_cache_path = if (builtin.os.tag == .wasi) "/cache" else null, }; for (manager.impl.configs.values) |unresolved_config| { - inline for (std.meta.fields(UnresolvedConfig)) |field| { - if (@field(unresolved_config, field.name)) |new_value| { - @field(config, field.name) = new_value; + inline for (comptime std.meta.fieldNames(UnresolvedConfig)) |field_name| { + if (@field(unresolved_config, field_name)) |new_value| { + @field(config, field_name) = new_value; } } } @@ -364,15 +364,15 @@ pub const Manager = struct { var did_change: DidConfigChange = .{}; - inline for (std.meta.fields(Config)) |field| { - const old_value = &@field(manager.config, field.name); - const new_value = @field(config, field.name); + inline for (comptime std.meta.fieldNames(Config), comptime std.meta.fieldTypes(Config)) |field_name, FieldType| { + const old_value = &@field(manager.config, field_name); + const new_value = @field(config, field_name); - const is_eql = option.eql(field.type, old_value.*, new_value); - @field(did_change, field.name) = !is_eql; + const is_eql = option.eql(FieldType, old_value.*, new_value); + @field(did_change, field_name) = !is_eql; if (!is_eql) { - old_value.* = try option.dupe(field.type, new_value, arena_allocator.allocator()); + old_value.* = try option.dupe(FieldType, new_value, arena_allocator.allocator()); } } @@ -698,11 +698,11 @@ comptime { /// The same struct as `Config` but every field is optional. pub const UnresolvedConfig = blk: { const struct_info: std.lang.Type.Struct = @typeInfo(Config).@"struct"; - var field_types: [struct_info.fields.len]type = undefined; - var field_attrs: [struct_info.fields.len]std.lang.Type.StructField.Attributes = undefined; - for (&field_types, &field_attrs, struct_info.fields) |*ty, *attr, field| { - ty.* = if (@typeInfo(field.type) != .optional) ?field.type else field.type; - attr.* = .{ .default_value_ptr = &@as(ty.*, null) }; + var field_types = struct_info.field_types[0..].*; + var field_attrs: [field_types.len]std.lang.Type.Struct.FieldAttributes = undefined; + for (&field_types, &field_attrs) |*field_type, *attr| { + field_type.* = if (@typeInfo(field_type.*) != .optional) ?field_type.* else field_type.*; + attr.* = .{ .default_value_ptr = &@as(field_type.*, null) }; } break :blk @Struct(.auto, null, std.meta.fieldNames(Config), &field_types, &field_attrs); }; diff --git a/src/features/code_actions.zig b/src/features/code_actions.zig index eb334657f6..5a6a1c4f71 100644 --- a/src/features/code_actions.zig +++ b/src/features/code_actions.zig @@ -296,7 +296,7 @@ pub fn collectAutoDiscardDiagnostics( .severity = .Information, .code = null, .source = "zls", - .message = "auto discard for unused variable", + .message = .{ .string = "auto discard for unused variable" }, .relatedInformation = related_info, }); } @@ -1133,10 +1133,9 @@ const DiagnosticKind = union(enum) { } fn parseEnum(comptime T: type, message: []const u8) ?T { - inline for (std.meta.fields(T)) |field| { - if (std.mem.startsWith(u8, message, field.name)) { - // is there a better way to achieve this? - return @as(T, @enumFromInt(field.value)); + inline for (comptime std.meta.fieldNames(T)) |field_name| { + if (std.mem.startsWith(u8, message, field_name)) { + return @field(T, field_name); } } diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 58aac91c13..2690a9ae88 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -181,7 +181,7 @@ fn collectWarnStyleDiagnostics( .severity = .Hint, .code = .{ .string = "dot_slash_import" }, .source = "zls", - .message = "A ./ is not needed in imports", + .message = .{ .string = "A ./ is not needed in imports" }, }); } } @@ -212,7 +212,7 @@ fn collectWarnStyleDiagnostics( .severity = .Hint, .code = .{ .string = "bad_style" }, .source = "zls", - .message = "Functions should be camelCase", + .message = .{ .string = "Functions should be camelCase" }, }); } else if (is_type_function and !isPascalCase(func_name)) { try diagnostics.append(arena, .{ @@ -220,7 +220,7 @@ fn collectWarnStyleDiagnostics( .severity = .Hint, .code = .{ .string = "bad_style" }, .source = "zls", - .message = "Type functions should be PascalCase", + .message = .{ .string = "Type functions should be PascalCase" }, }); } } @@ -270,7 +270,7 @@ fn collectGlobalVarDiagnostics( .severity = .Hint, .code = .{ .string = "highlight_global_var_declarations" }, .source = "zls", - .message = "Global var declaration", + .message = .{ .string = "Global var declaration" }, }); }, else => {}, diff --git a/src/features/document_symbol.zig b/src/features/document_symbol.zig index 6212f3bed6..ce8c85f1ba 100644 --- a/src/features/document_symbol.zig +++ b/src/features/document_symbol.zig @@ -69,8 +69,8 @@ pub fn getDocumentSymbols( }, }; - try stack.append(arena, stack.getLast().?); - const stack_entry: *StackEntry = &stack.items[stack.items.len - 1]; + try stack.append(arena, stack.last().?); + const stack_entry = stack.lastPtr().?; const symbol: Symbol = switch (tree.nodeTag(node)) { .global_var_decl, diff --git a/src/features/inlay_hints.zig b/src/features/inlay_hints.zig index de5cae7966..ad2ec842a5 100644 --- a/src/features/inlay_hints.zig +++ b/src/features/inlay_hints.zig @@ -49,6 +49,7 @@ const excluded_builtins_set: std.EnumArray(std.zig.BuiltinFn.Tag, bool) = .init( .c_va_copy = false, .c_va_end = false, .c_va_start = false, + .div_ceil = true, .div_exact = true, .div_floor = true, .div_trunc = true, @@ -73,6 +74,8 @@ const excluded_builtins_set: std.EnumArray(std.zig.BuiltinFn.Tag, bool) = .init( .import = true, .in_comptime = true, // no parameters .int_cast = true, + .backing_int = true, + .from_backing_int = true, .enum_from_int = true, .error_from_int = true, .float_from_int = true, @@ -132,6 +135,7 @@ const excluded_builtins_set: std.EnumArray(std.zig.BuiltinFn.Tag, bool) = .init( .Struct = false, .Union = false, .Enum = false, + .SpirvType = true, .type_info = true, .type_name = true, .TypeOf = true, // variadic diff --git a/src/features/semantic_tokens.zig b/src/features/semantic_tokens.zig index 909ccbe76e..b371a3319f 100644 --- a/src/features/semantic_tokens.zig +++ b/src/features/semantic_tokens.zig @@ -63,14 +63,14 @@ pub const TokenModifiers = packed struct(u16) { pub fn format(modifiers: TokenModifiers, writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.writeAll(".{"); var i: usize = 0; - inline for (std.meta.fields(TokenModifiers)) |field| { - if ((comptime !std.mem.eql(u8, field.name, "_")) and @field(modifiers, field.name)) { + inline for (comptime std.meta.fieldNames(TokenModifiers)) |field_name| { + if ((comptime !std.mem.eql(u8, field_name, "_")) and @field(modifiers, field_name)) { if (i == 0) { try writer.writeAll(" ."); } else { try writer.writeAll(", ."); } - try writer.writeAll(field.name); + try writer.writeAll(field_name); try writer.writeAll(" = true"); i += 1; } diff --git a/src/main.zig b/src/main.zig index 7979b1b538..27582cb63b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -46,7 +46,7 @@ var log_transport: ?*zls.lsp.Transport = null; var log_stderr: bool = true; /// Log messages to the given file. var log_file: ?std.Io.File = null; -var log_level: std.log.Level = if (zig_builtin.mode == .Debug) .debug else .info; +var log_level: std.log.Level = if (zig_builtin.mode == .debug) .debug else .info; fn logFn( comptime level: std.log.Level, diff --git a/src/offsets.zig b/src/offsets.zig index 6b77589810..e34d13044f 100644 --- a/src/offsets.zig +++ b/src/offsets.zig @@ -177,7 +177,7 @@ pub fn sourceIndexToTokenIndex(tree: *const Ast, source_index: usize) SourceInde } test sourceIndexToTokenIndex { - var tree: Ast = try .parse(std.testing.allocator, " a bb; ", .zig); + var tree: Ast = try .parse(std.testing.allocator, " a bb; ", .{}); defer tree.deinit(std.testing.allocator); try std.testing.expectEqualSlices( @@ -203,7 +203,7 @@ test sourceIndexToTokenIndex { } test "sourceIndexToTokenIndex - token at end" { - var tree: Ast = try .parse(std.testing.allocator, " a", .zig); + var tree: Ast = try .parse(std.testing.allocator, " a", .{}); defer tree.deinit(std.testing.allocator); try std.testing.expectEqualSlices( @@ -327,7 +327,7 @@ pub fn identifierTokenAndLocFromIndex(tree: *const Ast, source_index: usize) ?st test identifierLocFromIndex { var tree = try Ast.parse(std.testing.allocator, \\ name @builtin @"escaped" @"s p a c e" end - , .zig); + , .{}); defer tree.deinit(std.testing.allocator); try std.testing.expectEqualSlices( @@ -441,7 +441,7 @@ test tokenToLoc { } fn testTokenToLoc(text: [:0]const u8, token_index: Ast.TokenIndex, start: usize, end: usize) !void { - var tree = try Ast.parse(std.testing.allocator, text, .zig); + var tree = try Ast.parse(std.testing.allocator, text, .{}); defer tree.deinit(std.testing.allocator); const actual = tokenToLoc(&tree, token_index); diff --git a/src/print_ast.zig b/src/print_ast.zig index ae44fc5188..a0115ba437 100644 --- a/src/print_ast.zig +++ b/src/print_ast.zig @@ -858,7 +858,9 @@ pub fn main(init: std.process.Init) !u8 { defer gpa.free(source); const mode: Ast.Mode = if (std.mem.endsWith(u8, file_path, ".zon")) .zon else .zig; - var tree: Ast = try .parse(gpa, source, mode); + var tree: Ast = try .parse(gpa, source, .{ + .mode = mode, + }); defer tree.deinit(gpa); try renderToFile( @@ -879,7 +881,7 @@ test PrintAst { \\} ; - var tree: Ast = try .parse(std.testing.allocator, source, .zig); + var tree: Ast = try .parse(std.testing.allocator, source, .{}); defer tree.deinit(std.testing.allocator); var aw: std.Io.Writer.Allocating = .init(std.testing.allocator); @@ -936,7 +938,7 @@ test PrintAst { const printed_source = try aw.toOwnedSliceSentinel(0); defer std.testing.allocator.free(printed_source); - var printed_tree: Ast = try .parse(std.testing.allocator, printed_source, .zig); + var printed_tree: Ast = try .parse(std.testing.allocator, printed_source, .{}); defer printed_tree.deinit(std.testing.allocator); try std.testing.expectEqual(0, printed_tree.errors.len); diff --git a/src/tools/config_gen.zig b/src/tools/config_gen.zig index adebe8b859..02899d1920 100644 --- a/src/tools/config_gen.zig +++ b/src/tools/config_gen.zig @@ -167,7 +167,7 @@ fn generateConfigFile( const source_unformatted = try aw.toOwnedSliceSentinel(0); defer allocator.free(source_unformatted); - var tree: std.zig.Ast = try .parse(allocator, source_unformatted, .zig); + var tree: std.zig.Ast = try .parse(allocator, source_unformatted, .{}); defer tree.deinit(allocator); std.debug.assert(tree.errors.len == 0); diff --git a/tests/ErrorBuilder.zig b/tests/ErrorBuilder.zig index b4cebee2c2..8a434d6f86 100644 --- a/tests/ErrorBuilder.zig +++ b/tests/ErrorBuilder.zig @@ -107,7 +107,7 @@ pub fn removeUnusedFiles(builder: *ErrorBuilder) void { // fn assertFmt(ok: bool, comptime fmt_str: []const u8, args: anytype) void { - if (builtin.mode == .Debug or builtin.is_test) { + if (builtin.mode == .debug or builtin.is_test) { if (!ok) std.debug.panic(fmt_str, args); } else { std.debug.assert(ok); diff --git a/tests/analysis/builtins.zig b/tests/analysis/builtins.zig index 4d575fcf07..9955d3dd44 100644 --- a/tests/analysis/builtins.zig +++ b/tests/analysis/builtins.zig @@ -181,21 +181,21 @@ const Struct = @Struct(.auto, undefined, &.{"foo"}, &.{i32}, &.{.{}}); // ^^^^^^ (type)() // ^^^^^ (ContainerLayout)() // ^ ([1][]const u8)() -// ^ ([?]Attributes)() -// ^ (Attributes)() +// ^ ([?]FieldAttributes)() +// ^ (FieldAttributes)() const Union = @Union(.auto, undefined, &.{"foo"}, &.{i32}, &.{.{}}); // ^^^^^ (type)() // ^^^^^ (ContainerLayout)() // ^ ([1][]const u8)() -// ^ ([?]Attributes)() -// ^ (Attributes)() +// ^ ([?]FieldAttributes)() +// ^ (FieldAttributes)() const Enum = @Enum(undefined, .exhaustive, undefined, undefined); // ^^^^ (type)() // ^^^^^^^^^^^ (Mode)() const Fn = @Fn(&.{i32}, &.{.{}}, undefined, .{}); // ^^ (type)() -// ^ ([?]Attributes)() -// ^ (Attributes)() +// ^ ([?]ParamAttributes)() +// ^ (ParamAttributes)() // ^ (Attributes)() const type_enum_literal: @EnumLiteral() = .foo; diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 5dcdca0b78..71175dcffd 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -3173,7 +3173,7 @@ test "builtin fns return type" { .{ .label = "null", .kind = .Field, .detail = "void" }, .{ .label = "optional", .kind = .Field, .detail = "Optional" }, .{ .label = "error_union", .kind = .Field, .detail = "ErrorUnion" }, - .{ .label = "error_set", .kind = .Field, .detail = "?[]const Error" }, + .{ .label = "error_set", .kind = .Field, .detail = "ErrorSet" }, .{ .label = "@\"enum\"", .kind = .Field, .detail = "Enum" }, .{ .label = "@\"union\"", .kind = .Field, .detail = "Union" }, .{ .label = "@\"fn\"", .kind = .Field, .detail = "Fn" }, @@ -3182,6 +3182,7 @@ test "builtin fns return type" { .{ .label = "@\"anyframe\"", .kind = .Field, .detail = "AnyFrame" }, .{ .label = "vector", .kind = .Field, .detail = "Vector" }, .{ .label = "enum_literal", .kind = .Field, .detail = "void" }, + .{ .label = "spirv", .kind = .Struct, .detail = "Spirv" }, // why is only this .Struct? }); } diff --git a/tests/lsp_features/diagnostics.zig b/tests/lsp_features/diagnostics.zig index 286f00c453..7538da6983 100644 --- a/tests/lsp_features/diagnostics.zig +++ b/tests/lsp_features/diagnostics.zig @@ -25,7 +25,7 @@ test "ast error" { }, .severity = .Error, .source = "zls", - .message = "expected ';' after declaration", + .message = .{ .string = "expected ';' after declaration" }, }, }, .{}); } @@ -44,7 +44,7 @@ test "ast-check" { .severity = .Error, .tags = &.{.Unnecessary}, .source = "zls", - .message = "unused local constant", + .message = .{ .string = "unused local constant" }, }, }, .{}); @@ -61,7 +61,7 @@ test "ast-check" { .severity = .Error, .tags = &.{.Unnecessary}, .source = "zls", - .message = "unused local variable", + .message = .{ .string = "unused local variable" }, }, }, .{}); } @@ -78,7 +78,7 @@ test "warn style" { .code = .{ .string = "dot_slash_import" }, .severity = .Hint, .source = "zls", - .message = "A ./ is not needed in imports", + .message = .{ .string = "A ./ is not needed in imports" }, }, }, .{ .warn_style = true }); try testDiagnostics( @@ -93,7 +93,7 @@ test "warn style" { .code = .{ .string = "bad_style" }, .severity = .Hint, .source = "zls", - .message = "Functions should be camelCase", + .message = .{ .string = "Functions should be camelCase" }, }, .{ .range = .{ @@ -103,7 +103,7 @@ test "warn style" { .code = .{ .string = "bad_style" }, .severity = .Hint, .source = "zls", - .message = "Type functions should be PascalCase", + .message = .{ .string = "Type functions should be PascalCase" }, }, }, .{ .warn_style = true }); } @@ -120,7 +120,7 @@ test "highlight global var decls" { .code = .{ .string = "highlight_global_var_declarations" }, .severity = .Hint, .source = "zls", - .message = "Global var declaration", + .message = .{ .string = "Global var declaration" }, }, }, .{ .highlight_global_var_declarations = true }); } @@ -139,7 +139,7 @@ test "autofix comment" { }, .severity = .Information, .source = "zls", - .message = "auto discard for unused variable", + .message = .{ .string = "auto discard for unused variable" }, .relatedInformation = &.{ .{ .location = .{ diff --git a/tests/utility/ast.zig b/tests/utility/ast.zig index c9bbacc9bd..7049155eaf 100644 --- a/tests/utility/ast.zig +++ b/tests/utility/ast.zig @@ -85,7 +85,7 @@ fn testNodesAtLoc(source: []const u8) !void { const new_source = try allocator.dupeSentinel(u8, ccp.new_source, 0); defer allocator.free(new_source); - var tree: std.zig.Ast = try .parse(allocator, new_source, .zig); + var tree: std.zig.Ast = try .parse(allocator, new_source, .{}); defer tree.deinit(allocator); const nodes = try ast.nodesAtLoc(allocator, &tree, inner_loc); diff --git a/tests/utility/position_context.zig b/tests/utility/position_context.zig index 0bc2eb7537..e42260efb9 100644 --- a/tests/utility/position_context.zig +++ b/tests/utility/position_context.zig @@ -731,7 +731,7 @@ fn testContext(source: []const u8, expected_tag: std.meta.Tag(Analyser.PositionC const new_source = try allocator.dupeSentinel(u8, phr.new_source, 0); defer allocator.free(new_source); - var tree: std.zig.Ast = try .parse(allocator, new_source, .zig); + var tree: std.zig.Ast = try .parse(allocator, new_source, .{}); defer tree.deinit(allocator); const ctx = try Analyser.getPositionContext(allocator, &tree, cursor_index, lookahead); From 8c7fac463f982f46fbd64aad822066b75d603b79 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Fri, 4 Sep 2026 16:51:58 +0200 Subject: [PATCH 08/14] remove build runner won't be missed --- .github/workflows/build_runner.yml | 46 - build.zig | 8 +- schema.json | 5 - src/Config.zig | 3 - src/DocumentStore.zig | 136 +- src/Server.zig | 27 +- src/build_runner/build_runner.zig | 1243 ----------------- src/build_runner/check.zig | 283 ---- src/build_runner/shared.zig | 158 --- src/configuration.zig | 364 ++++- src/features/diagnostics.zig | 252 ---- src/tools/config.json | 6 - src/zls.zig | 3 - tests/add_build_runner_cases.zig | 78 -- tests/analysis_check.zig | 1 - tests/build_runner_cases/add_module.json | 14 - tests/build_runner_cases/add_module.zig | 7 - tests/build_runner_cases/add_options.json | 19 - tests/build_runner_cases/add_options.zig | 11 - tests/build_runner_cases/empty.json | 10 - tests/build_runner_cases/empty.zig | 5 - .../module_root_source_file_collision.json | 31 - .../module_root_source_file_collision.zig | 37 - .../module_self_import.json | 16 - .../build_runner_cases/module_self_import.zig | 9 - .../multiple_module_import_names.json | 27 - .../multiple_module_import_names.zig | 18 - .../no_root_source_file.json | 19 - .../no_root_source_file.zig | 19 - .../public_module_with_generated_file.json | 14 - .../public_module_with_generated_file.zig | 10 - tests/build_runner_check.zig | 113 -- 32 files changed, 306 insertions(+), 2686 deletions(-) delete mode 100644 .github/workflows/build_runner.yml delete mode 100644 src/build_runner/build_runner.zig delete mode 100644 src/build_runner/check.zig delete mode 100644 src/build_runner/shared.zig delete mode 100644 tests/add_build_runner_cases.zig delete mode 100644 tests/build_runner_cases/add_module.json delete mode 100644 tests/build_runner_cases/add_module.zig delete mode 100644 tests/build_runner_cases/add_options.json delete mode 100644 tests/build_runner_cases/add_options.zig delete mode 100644 tests/build_runner_cases/empty.json delete mode 100644 tests/build_runner_cases/empty.zig delete mode 100644 tests/build_runner_cases/module_root_source_file_collision.json delete mode 100644 tests/build_runner_cases/module_root_source_file_collision.zig delete mode 100644 tests/build_runner_cases/module_self_import.json delete mode 100644 tests/build_runner_cases/module_self_import.zig delete mode 100644 tests/build_runner_cases/multiple_module_import_names.json delete mode 100644 tests/build_runner_cases/multiple_module_import_names.zig delete mode 100644 tests/build_runner_cases/no_root_source_file.json delete mode 100644 tests/build_runner_cases/no_root_source_file.zig delete mode 100644 tests/build_runner_cases/public_module_with_generated_file.json delete mode 100644 tests/build_runner_cases/public_module_with_generated_file.zig delete mode 100644 tests/build_runner_check.zig diff --git a/.github/workflows/build_runner.yml b/.github/workflows/build_runner.yml deleted file mode 100644 index 58d424464e..0000000000 --- a/.github/workflows/build_runner.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: BuildRunner - -on: - push: - branches: - - master - pull_request: - paths: - - ".github/workflows/build_runner.yml" - - "src/build_runner/**" - # Ensure that the build runner checks get run when the minimum Zig version gets modified - - "build.zig" - - "build.zig.zon" - workflow_dispatch: - -jobs: - check_build_runner: - # re-enable once there is a tagged release or mach nominated version that the build runner supports - if: false - strategy: - fail-fast: false - matrix: - include: - - zig-version: 0.16.0 - # - zig-version: mach-latest - # Zig master is handled by 'zig build test' in the main CI - runs-on: ubuntu-22.04 - timeout-minutes: 10 - - steps: - - uses: actions/checkout@v4 - - - uses: mlugg/setup-zig@v2 - with: - version: ${{ matrix.zig-version }} - - - name: Create temp zig project - run: | - mkdir $RUNNER_TEMP/TEMP_ZIG_PROJECT - cd $RUNNER_TEMP/TEMP_ZIG_PROJECT - zig init - - - name: Check build_runner builds - run: | - cd $RUNNER_TEMP/TEMP_ZIG_PROJECT - zig build --build-runner $GITHUB_WORKSPACE/src/build_runner/build_runner.zig diff --git a/build.zig b/build.zig index 2ded4914b5..9caeee023e 100644 --- a/build.zig +++ b/build.zig @@ -256,13 +256,11 @@ pub fn build(b: *Build) !void { .use_lld = use_llvm, }); - blk: { // zig build test, zig build test-build-runner, zig build test-analysis + blk: { // zig build test, zig build test-analysis const test_step = b.step("test", "Run all the tests"); - const test_build_runner_step = b.step("test-build-runner", "Run all the build runner tests"); const test_analysis_step = b.step("test-analysis", "Run all the analysis tests"); // Create run steps - @import("tests/add_build_runner_cases.zig").addCases(b, test_build_runner_step, test_filters); @import("tests/add_analysis_cases.zig").addCases(b, target, optimize, test_analysis_step, test_filters); const run_tests = b.addRunArtifact(tests); @@ -277,7 +275,6 @@ pub fn build(b: *Build) !void { test_step.dependOn(&run_tests.step); test_step.dependOn(&run_src_tests.step); test_step.dependOn(test_analysis_step); - if (target.query.eql(b.graph.host.query)) test_step.dependOn(test_build_runner_step); if (!coverage) break :blk; @@ -285,9 +282,6 @@ pub fn build(b: *Build) !void { var run_test_steps: std.ArrayList(*std.Build.Step.Run) = .empty; run_test_steps.append(b.allocator, run_tests) catch @panic("OOM"); run_test_steps.append(b.allocator, run_src_tests) catch @panic("OOM"); - for (test_build_runner_step.dependencies.items) |step| { - run_test_steps.append(b.allocator, step.cast(std.Build.Step.Run).?) catch @panic("OOM"); - } for (test_analysis_step.dependencies.items) |step| { run_test_steps.append(b.allocator, step.cast(std.Build.Step.Run).?) catch @panic("OOM"); } diff --git a/schema.json b/schema.json index c7ee96244a..8d79c3edf8 100644 --- a/schema.json +++ b/schema.json @@ -117,11 +117,6 @@ "type": "string", "default": null }, - "build_runner_path": { - "description": "Specify a custom build runner to resolve build system information.", - "type": "string", - "default": null - }, "global_cache_path": { "description": "Path to a directory that will be used as zig's cache. Will default to `${KnownFolders.Cache}/zls`.", "type": "string", diff --git a/src/Config.zig b/src/Config.zig index 846e0e73b9..fd576eb4bb 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -75,9 +75,6 @@ zig_lib_path: ?[]const u8 = null, /// Specify the path to the Zig executable (not the directory). If unset, zig is looked up in `PATH`. e.g. `/path/to/zig-templeos-armless-1.0.0/zig`. zig_exe_path: ?[]const u8 = null, -/// Specify a custom build runner to resolve build system information. -build_runner_path: ?[]const u8 = null, - /// Path to a directory that will be used as zig's cache. Will default to `${KnownFolders.Cache}/zls`. global_cache_path: ?[]const u8 = null, diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 3e7f4f5bfc..3ae09102c8 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -9,12 +9,13 @@ const log = std.log.scoped(.store); const lsp = @import("lsp"); const Ast = std.zig.Ast; const BuildAssociatedConfig = @import("BuildAssociatedConfig.zig"); -pub const BuildConfig = @import("build_runner/shared.zig").BuildConfig; const tracy = @import("tracy"); const DocumentScope = @import("DocumentScope.zig"); const DiagnosticsCollection = @import("DiagnosticsCollection.zig"); const TrigramStore = @import("TrigramStore.zig"); +const BuildConfig = @compileError("https://github.com/zigtools/zls/issues/3208"); + const DocumentStore = @This(); io: std.Io, @@ -34,13 +35,13 @@ lsp_capabilities: struct { supports_inlay_hints_refresh: bool = false, } = .{}, -pub const supports_build_system = std.process.can_spawn; +/// https://github.com/zigtools/zls/issues/3208 +pub const supports_build_system = false and std.process.can_spawn; pub const Config = struct { environ_map: *const std.process.Environ.Map, zig_exe_path: ?[]const u8, zig_lib_dir: ?std.Build.Cache.Directory, - build_runner_path: ?[]const u8, builtin_path: ?[]const u8, global_cache_dir: ?std.Build.Cache.Directory, wasi_preopens: switch (builtin.os.tag) { @@ -918,7 +919,6 @@ pub fn invalidateBuildFile(self: *DocumentStore, build_file_uri: Uri) void { comptime std.debug.assert(supports_build_system); if (self.config.zig_exe_path == null) return; - if (self.config.build_runner_path == null) return; if (self.config.global_cache_dir == null) return; if (self.config.zig_lib_dir == null) return; @@ -1171,6 +1171,7 @@ fn invalidateBuildFileWorker(self: *DocumentStore, build_file: *BuildFile) std.I build_file.impl.version += 1; const new_version = build_file.impl.version; + const loadBuildConfiguration = if (true) @compileError("https://github.com/zigtools/zls/issues/3208"); const build_config = loadBuildConfiguration(self, build_file.uri, new_version) catch |err| switch (err) { error.Canceled => return error.Canceled, else => |e| { @@ -1282,133 +1283,6 @@ fn loadBuildAssociatedConfiguration(io: std.Io, allocator: std.mem.Allocator, bu ); } -fn prepareBuildRunnerArgs(self: *DocumentStore, build_file_uri: Uri) error{OutOfMemory}![][]const u8 { - const tracy_zone = tracy.trace(@src()); - defer tracy_zone.end(); - - const base_args = &[_][]const u8{ - self.config.zig_exe_path.?, - "build", - "--build-runner", - self.config.build_runner_path.?, - "--zig-lib-dir", - self.config.zig_lib_dir.?.path orelse ".", - }; - - var args: std.ArrayList([]const u8) = try .initCapacity(self.allocator, base_args.len); - errdefer { - for (args.items) |arg| self.allocator.free(arg); - args.deinit(self.allocator); - } - - for (base_args) |arg| { - args.appendAssumeCapacity(try self.allocator.dupe(u8, arg)); - } - - if (self.getBuildFile(build_file_uri)) |build_file| blk: { - const build_config = build_file.build_associated_config orelse break :blk; - const build_options = build_config.value.build_options orelse break :blk; - - try args.ensureUnusedCapacity(self.allocator, build_options.len); - for (build_options) |option| { - args.appendAssumeCapacity(try option.formatParam(self.allocator)); - } - } - - return try args.toOwnedSlice(self.allocator); -} - -/// Runs the build.zig and extracts include directories and packages -fn loadBuildConfiguration(self: *DocumentStore, build_file_uri: Uri, build_file_version: u32) !std.json.Parsed(BuildConfig) { - const tracy_zone = tracy.trace(@src()); - defer tracy_zone.end(); - - std.debug.assert(self.config.zig_exe_path != null); - std.debug.assert(self.config.build_runner_path != null); - std.debug.assert(self.config.global_cache_dir != null); - std.debug.assert(self.config.zig_lib_dir != null); - - const build_file_path = try build_file_uri.toFsPath(self.allocator); - defer self.allocator.free(build_file_path); - - const cwd = std.Io.Dir.path.dirname(build_file_path).?; - - const args = try self.prepareBuildRunnerArgs(build_file_uri); - defer { - for (args) |arg| self.allocator.free(arg); - self.allocator.free(args); - } - - const zig_run_result = blk: { - const tracy_zone2 = tracy.trace(@src()); - defer tracy_zone2.end(); - break :blk try std.process.run( - self.allocator, - self.io, - .{ - .argv = args, - .cwd = .{ .path = cwd }, - .stderr_limit = .limited(16 * 1024 * 1024), - .stdout_limit = .limited(16 * 1024 * 1024), - }, - ); - }; - defer self.allocator.free(zig_run_result.stdout); - defer self.allocator.free(zig_run_result.stderr); - - const is_ok = switch (zig_run_result.term) { - .exited => |exit_code| exit_code == 0, - else => false, - }; - - const diagnostic_tag: DiagnosticsCollection.Tag = tag: { - var hasher: std.hash.Wyhash = .init(47); // Chosen by the following prompt: Pwease give a wandom nyumbew - hasher.update(build_file_uri.raw); - break :tag @enumFromInt(@as(u32, @truncate(hasher.final()))); - }; - - if (!is_ok) { - const joined = try std.mem.join(self.allocator, " ", args); - defer self.allocator.free(joined); - - log.err( - "Failed to execute build runner to collect build configuration, command:\ncd {s};{s}\nError: {s}", - .{ cwd, joined, zig_run_result.stderr }, - ); - - var error_bundle = try @import("features/diagnostics.zig").getErrorBundleFromStderr( - self.allocator, - zig_run_result.stderr, - false, - .{ .dynamic = .{ .document_store = self, .base_path = cwd } }, - ); - defer error_bundle.deinit(self.allocator); - - try self.diagnostics_collection.pushErrorBundle(diagnostic_tag, build_file_version, cwd, error_bundle); - try self.diagnostics_collection.publishDiagnostics(); - return error.RunFailed; - } else { - try self.diagnostics_collection.pushErrorBundle(diagnostic_tag, build_file_version, null, .empty); - try self.diagnostics_collection.publishDiagnostics(); - } - - const parse_options: std.json.ParseOptions = .{ - // We ignore unknown fields so people can roll - // their own build runners in libraries with - // the only requirement being general adherence - // to the BuildConfig type - .ignore_unknown_fields = true, - .allocate = .alloc_always, - }; - - return std.json.parseFromSlice( - BuildConfig, - self.allocator, - zig_run_result.stdout, - parse_options, - ) catch return error.InvalidBuildConfig; -} - /// Checks if the build.zig file is accessible in dir. fn buildDotZigExists(io: std.Io, dir_path: []const u8) std.Io.Cancelable!bool { var dir = std.Io.Dir.openDirAbsolute(io, dir_path, .{}) catch |err| switch (err) { diff --git a/src/Server.zig b/src/Server.zig index 18b3c4d07c..b96966e24d 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -20,7 +20,6 @@ const diff = @import("diff.zig"); const Uri = @import("Uri.zig"); const InternPool = @import("analyser/analyser.zig").InternPool; const DiagnosticsCollection = @import("DiagnosticsCollection.zig"); -const build_runner_shared = @import("build_runner/shared.zig"); const signature_help = @import("features/signature_help.zig"); const references = @import("features/references.zig"); @@ -35,8 +34,12 @@ const hover_handler = @import("features/hover.zig"); const selection_range = @import("features/selection_range.zig"); const diagnostics_gen = @import("features/diagnostics.zig"); -const BuildOnSave = diagnostics_gen.BuildOnSave; -const BuildOnSaveSupport = build_runner_shared.BuildOnSaveSupport; +const BuildOnSave = @compileError("https://github.com/zigtools/zls/issues/3208"); +const BuildOnSaveSupport = struct { + pub inline fn isSupportedComptime() bool { + return false; + } +}; const log = std.log.scoped(.server); @@ -825,7 +828,6 @@ const Workspace = struct { const zig_exe_path = config.zig_exe_path orelse return; const zig_lib_path = config.zig_lib_path orelse return; - const build_runner_path = config.build_runner_path orelse return; const workspace_path = workspace.uri.toFsPath(args.server.allocator) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -842,7 +844,6 @@ const Workspace = struct { .check_step_only = config.enable_build_on_save == null, .zig_exe_path = zig_exe_path, .zig_lib_path = zig_lib_path, - .build_runner_path = build_runner_path, .collection = &args.server.diagnostics_collection, }) catch |err| switch (err) { error.Canceled => return error.Canceled, @@ -994,7 +995,6 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void const new_zig_exe_path: bool = result.did_change.zig_exe_path; const new_zig_lib_path: bool = result.did_change.zig_lib_path; - const new_build_runner_path: bool = result.did_change.build_runner_path; const new_enable_build_on_save: bool = result.did_change.enable_build_on_save; const new_build_on_save_args: bool = result.did_change.build_on_save_args; const new_force_autofix: bool = result.did_change.force_autofix; @@ -1010,7 +1010,6 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void const should_restart = new_zig_exe_path or new_zig_lib_path or - new_build_runner_path or new_enable_build_on_save or new_build_on_save_args; @@ -1023,7 +1022,7 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void } if (DocumentStore.supports_build_system) { - if (new_zig_exe_path or new_zig_lib_path or new_build_runner_path) { + if (new_zig_exe_path or new_zig_lib_path) { for (server.document_store.build_files.keys()) |build_file_uri| { server.document_store.invalidateBuildFile(build_file_uri); } @@ -1060,12 +1059,11 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void check: { if (server.status != .initialized) break :check; - switch (server.config_manager.build_runner_supported) { - .yes, .no_dont_error => break :check, - .no => {}, - } + const zig_exe = server.config_manager.zig_exe orelse break :check; + + if (zig_exe.supported) break :check; - const zig_version = server.config_manager.zig_exe.?.version; + const zig_version = zig_exe.version; const zls_version = build_options.version; const zig_version_is_tagged = zig_version.pre == null; @@ -1100,8 +1098,6 @@ pub fn resolveConfiguration(server: *Server) error{ Canceled, OutOfMemory }!void log.warn("'enable_build_on_save' is ignored because Zig could not be found", .{}); } else if (!server.client_capabilities.supports_publish_diagnostics) { log.warn("'enable_build_on_save' is ignored because it is not supported by {s}", .{server.client_capabilities.client_name orelse "your editor"}); - } else if (server.status == .initialized and server.config_manager.build_runner_supported == .no and server.config_manager.config.build_runner_path == null) { - log.warn("'enable_build_on_save' is ignored because no build runner is available", .{}); } else if (server.status == .initialized and server.config_manager.zig_exe != null) { switch (BuildOnSaveSupport.isSupportedRuntime(server.config_manager.zig_exe.?.version)) { .supported => {}, @@ -1131,7 +1127,6 @@ fn createDocumentStoreConfig(config_manager: *const configuration.Manager) Docum .environ_map = config_manager.environ_map, .zig_exe_path = config_manager.config.zig_exe_path, .zig_lib_dir = config_manager.zig_lib_dir, - .build_runner_path = config_manager.config.build_runner_path, .builtin_path = config_manager.config.builtin_path, .global_cache_dir = config_manager.global_cache_dir, .wasi_preopens = config_manager.wasi_preopens, diff --git a/src/build_runner/build_runner.zig b/src/build_runner/build_runner.zig deleted file mode 100644 index e78338379c..0000000000 --- a/src/build_runner/build_runner.zig +++ /dev/null @@ -1,1243 +0,0 @@ -//! PLEASE READ THE FOLLOWING MESSAGE BEFORE EDITING THIS FILE: -//! -//! To support the Zig build system, ZLS uses a custom build runner which -//! interfaces with the compiler that is installed on the user's system. -//! -//! The Zig version on the user's system may not be the same version that ZLS -//! has been built with. The minimum runtime Zig version in the `build.zig` -//! specifies which Zig versions need to be supported by this build runner. -//! -//! When a breaking change occurs, this is what need to be done: -//! - If it is a tagged release (e.g 0.x.y) , update the build runner code to handle multiple Zig versions. -//! - If it is a development version (e.g 0.x.y-dev), bump the minimum runtime Zig version. -//! -//! Handling multiple Zig versions can be achieved with one of the following strategies: -//! - use `@hasDecl` or `@hasField` (recommended) -//! - use `builtin.zig_version` -//! -//! The build runner can be tested on ZLS's `build.zig` with the following command: -//! `zig build --build-runner src/build_runner/build_runner.zig` -//! -//! To test with a different `build.zig`, use one of the following commands: -//! `zig build --build-file /path/to/build.zig --build-runner /path/to/zls/src/build_runner/build_runner.zig` -//! `zig build --build-runner /path/to/zls/src/build_runner/build_runner.zig` (if the cwd contains build.zig) -//! - -const std = @import("std"); -const builtin = @import("builtin"); -const Io = std.Io; -const assert = std.debug.assert; -const mem = std.mem; -const process = std.process; -const File = Io.File; -const Step = std.Build.Step; -const Allocator = std.mem.Allocator; -const fatal = std.process.fatal; -const runner = @This(); - -pub const root = @import("@build"); -pub const dependencies = @import("@dependencies"); - -pub const std_options: std.Options = .{ - .side_channels_mitigations = .none, - .http_disable_tls = true, - .networking = false, -}; - -pub fn main(init: process.Init.Minimal) !void { - // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not - // always the case. So, we do need a true gpa for some things. - var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_gpa_state.deinit(); - const gpa = debug_gpa_state.allocator(); - - var threaded: Io.Threaded = .init(gpa, .{ - .environ = init.environ, - .argv0 = .init(init.args), - }); - defer threaded.deinit(); - const io = threaded.io(); - - // ...but we'll back our arena by `std.heap.page_allocator` for efficiency. - var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - const args = try init.args.toSlice(arena); - - // skip my own exe name - var arg_idx: usize = 1; - - const zig_exe = nextArg(args, &arg_idx) orelse fatal("missing zig compiler path", .{}); - const zig_lib_dir = nextArg(args, &arg_idx) orelse fatal("missing zig lib directory path", .{}); - const build_root = nextArg(args, &arg_idx) orelse fatal("missing build root directory path", .{}); - const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{}); - const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{}); - - const cwd: Io.Dir = .cwd(); - - const zig_lib_directory: std.Build.Cache.Directory = .{ - .path = zig_lib_dir, - .handle = try cwd.openDir(io, zig_lib_dir, .{}), - }; - - const build_root_directory: std.Build.Cache.Directory = .{ - .path = build_root, - .handle = try cwd.openDir(io, build_root, .{}), - }; - - const local_cache_directory: std.Build.Cache.Directory = .{ - .path = cache_root, - .handle = try cwd.createDirPathOpen(io, cache_root, .{}), - }; - - const global_cache_directory: std.Build.Cache.Directory = .{ - .path = global_cache_root, - .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}), - }; - - var graph: std.Build.Graph = .{ - .io = io, - .arena = arena, - .cache = .{ - .io = io, - .gpa = gpa, - .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), - .cwd = try process.currentPathAlloc(io, arena), - }, - .zig_exe = zig_exe, - .environ_map = try init.environ.createMap(arena), - .global_cache_root = global_cache_directory, - .zig_lib_directory = zig_lib_directory, - .host = .{ - .query = .{}, - .result = try std.zig.system.resolveTargetQuery(io, .{}), - }, - .time_report = false, - }; - - graph.cache.addPrefix(.{ .path = null, .handle = cwd }); - graph.cache.addPrefix(build_root_directory); - graph.cache.addPrefix(local_cache_directory); - graph.cache.addPrefix(global_cache_directory); - graph.cache.hash.addBytes(builtin.zig_version_string); - - const builder = try std.Build.create( - &graph, - build_root_directory, - local_cache_directory, - dependencies.root_deps, - ); - - var targets: std.ArrayList([]const u8) = .empty; - - var install_prefix: ?[]const u8 = null; - var dir_list: std.Build.DirList = .{}; - var max_rss: u64 = 0; - var skip_oom_steps = false; - var output_tmp_nonce: ?[16]u8 = null; - var watch = false; - var check_step_only = false; - var debounce_interval_ms: u16 = 50; - - while (nextArg(args, &arg_idx)) |arg| { - if (mem.startsWith(u8, arg, "-Z")) { - if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg}); - output_tmp_nonce = arg[2..18].*; - } else if (mem.startsWith(u8, arg, "-D")) { - const option_contents = arg[2..]; - if (option_contents.len == 0) - fatalWithHint("expected option name after '-D'", .{}); - if (mem.findScalar(u8, option_contents, '=')) |name_end| { - const option_name = option_contents[0..name_end]; - const option_value = option_contents[name_end + 1 ..]; - if (try builder.addUserInputOption(option_name, option_value)) - fatal(" access the help menu with 'zig build -h'", .{}); - } else { - if (try builder.addUserInputFlag(option_contents)) - fatal(" access the help menu with 'zig build -h'", .{}); - } - } else if (mem.startsWith(u8, arg, "-")) { - if (mem.eql(u8, arg, "--verbose")) { - builder.verbose = true; - } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - fatal("argument '{s}' is not available", .{arg}); - } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) { - install_prefix = nextArgOrFatal(args, &arg_idx); - } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) { - fatal("argument '{s}' is not available", .{arg}); - } else if (mem.startsWith(u8, arg, "-fsys=")) { - const name = arg["-fsys=".len..]; - graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM"); - } else if (mem.startsWith(u8, arg, "-fno-sys=")) { - const name = arg["-fno-sys=".len..]; - graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM"); - } else if (mem.eql(u8, arg, "--release")) { - builder.release_mode = .any; - } else if (mem.startsWith(u8, arg, "--release=")) { - const text = arg["--release=".len..]; - builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse { - fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{ - arg, text, - }); - }; - } else if (mem.eql(u8, arg, "--prefix-lib-dir")) { - dir_list.lib_dir = nextArgOrFatal(args, &arg_idx); - } else if (mem.eql(u8, arg, "--prefix-exe-dir")) { - dir_list.exe_dir = nextArgOrFatal(args, &arg_idx); - } else if (mem.eql(u8, arg, "--prefix-include-dir")) { - dir_list.include_dir = nextArgOrFatal(args, &arg_idx); - } else if (mem.eql(u8, arg, "--sysroot")) { - builder.sysroot = nextArgOrFatal(args, &arg_idx); - } else if (mem.eql(u8, arg, "--maxrss")) { - const max_rss_text = nextArgOrFatal(args, &arg_idx); - max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| { - std.debug.print("invalid byte size: '{s}': {s}\n", .{ - max_rss_text, @errorName(err), - }); - process.exit(1); - }; - } else if (mem.eql(u8, arg, "--skip-oom-steps")) { - skip_oom_steps = true; - } else if (mem.eql(u8, arg, "--test-timeout")) { - const next_arg = nextArgOrFatal(args, &arg_idx); - _ = next_arg; - } else if (mem.eql(u8, arg, "--search-prefix")) { - const search_prefix = nextArgOrFatal(args, &arg_idx); - builder.addSearchPrefix(search_prefix); - } else if (mem.eql(u8, arg, "--libc")) { - builder.libc_file = nextArgOrFatal(args, &arg_idx); - } else if (mem.eql(u8, arg, "--color")) { - const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected [auto|on|off] after '{s}'", .{arg}); - _ = next_arg; - } else if (mem.eql(u8, arg, "--error-style")) { - const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected style after '{s}'", .{arg}); - _ = next_arg; - } else if (mem.eql(u8, arg, "--multiline-errors")) { - const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected style after '{s}'", .{arg}); - _ = next_arg; - } else if (mem.eql(u8, arg, "--summary")) { - const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected [all|new|failures|line|none] after '{s}'", .{arg}); - _ = next_arg; - } else if (mem.eql(u8, arg, "--seed")) { - const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected u32 after '{s}'", .{arg}); - graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { - fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{ - next_arg, @errorName(err), - }); - }; - } else if (mem.eql(u8, arg, "--build-id")) { - builder.build_id = .fast; - } else if (mem.startsWith(u8, arg, "--build-id=")) { - const style = arg["--build-id=".len..]; - builder.build_id = std.zig.BuildId.parse(style) catch |err| { - fatal("unable to parse --build-id style '{s}': {s}", .{ - style, @errorName(err), - }); - }; - } else if (mem.eql(u8, arg, "--debounce")) { - const next_arg = nextArg(args, &arg_idx) orelse - fatalWithHint("expected u16 after '{s}'", .{arg}); - debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| { - fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {t}\n", .{ - next_arg, err, - }); - }; - } else if (mem.eql(u8, arg, "--webui")) { - fatal("argument '{s}' is not available", .{arg}); - } else if (mem.startsWith(u8, arg, "--webui=")) { - fatal("argument '{s}' is not available", .{arg}); - } else if (mem.eql(u8, arg, "--debug-log")) { - fatal("argument '{s}' is not available", .{arg}); - } else if (mem.eql(u8, arg, "--debug-pkg-config")) { - builder.debug_pkg_config = true; - } else if (mem.cutPrefix(u8, arg, "--debug-rt=")) |rest| { - graph.debug_compiler_runtime_libs = - std.meta.stringToEnum(std.lang.OptimizeMode, rest) orelse - fatal("unrecognized optimization mode: '{s}'", .{rest}); - } else if (mem.eql(u8, arg, "--debug-compile-errors")) { - builder.debug_compile_errors = true; - } else if (mem.eql(u8, arg, "--debug-incremental")) { - builder.debug_incremental = true; - } else if (mem.eql(u8, arg, "--system")) { - // The usage text shows another argument after this parameter - // but it is handled by the parent process. The build runner - // only sees this flag. - graph.system_package_mode = true; - } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) { - // --glibc-runtimes was the old name of the flag; kept for compatibility for now. - builder.libc_runtimes_dir = nextArgOrFatal(args, &arg_idx); - } else if (mem.eql(u8, arg, "--verbose-link")) { - builder.verbose_link = true; - } else if (mem.eql(u8, arg, "--verbose-air")) { - builder.verbose_air = true; - } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { - builder.verbose_llvm_ir = "-"; - } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) { - builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..]; - } else if (mem.startsWith(u8, arg, "--verbose-llvm-bc=")) { - builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..]; - } else if (mem.eql(u8, arg, "--verbose-cc")) { - builder.verbose_cc = true; - } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) { - builder.verbose_llvm_cpu_features = true; - } else if (mem.eql(u8, arg, "--watch")) { - watch = true; - } else if (mem.eql(u8, arg, "--check-only")) { // ZLS only - check_step_only = true; - } else if (mem.eql(u8, arg, "--time-report")) { - fatal("argument '{s}' is not available", .{arg}); - } else if (mem.startsWith(u8, arg, "--fuzz")) { - fatal("argument '{s}' is not available", .{arg}); - } else if (mem.eql(u8, arg, "-fincremental")) { - graph.incremental = true; - } else if (mem.eql(u8, arg, "-fno-incremental")) { - graph.incremental = false; - } else if (mem.eql(u8, arg, "-fwine")) { - builder.enable_wine = true; - } else if (mem.eql(u8, arg, "-fno-wine")) { - builder.enable_wine = false; - } else if (mem.eql(u8, arg, "-fqemu")) { - builder.enable_qemu = true; - } else if (mem.eql(u8, arg, "-fno-qemu")) { - builder.enable_qemu = false; - } else if (mem.eql(u8, arg, "-fwasmtime")) { - builder.enable_wasmtime = true; - } else if (mem.eql(u8, arg, "-fno-wasmtime")) { - builder.enable_wasmtime = false; - } else if (mem.eql(u8, arg, "-frosetta")) { - builder.enable_rosetta = true; - } else if (mem.eql(u8, arg, "-fno-rosetta")) { - builder.enable_rosetta = false; - } else if (mem.eql(u8, arg, "-fdarling")) { - builder.enable_darling = true; - } else if (mem.eql(u8, arg, "-fno-darling")) { - builder.enable_darling = false; - } else if (mem.eql(u8, arg, "-fallow-so-scripts")) { - graph.allow_so_scripts = true; - } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) { - graph.allow_so_scripts = false; - } else if (mem.eql(u8, arg, "-freference-trace")) { - builder.reference_trace = 256; - } else if (mem.startsWith(u8, arg, "-freference-trace=")) { - const num = arg["-freference-trace=".len..]; - builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| { - std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) }); - process.exit(1); - }; - } else if (mem.eql(u8, arg, "-fno-reference-trace")) { - builder.reference_trace = null; - } else if (mem.cutPrefix(u8, arg, "-j")) |text| { - const n = std.fmt.parseUnsigned(u32, text, 10) catch |err| - fatal("unable to parse jobs count '{s}': {t}", .{ text, err }); - if (n < 1) fatal("number of jobs must be at least 1", .{}); - threaded.setAsyncLimit(.limited(n)); - } else if (mem.eql(u8, arg, "--")) { - builder.args = argsRest(args, arg_idx); - break; - } else { - fatalWithHint("unrecognized argument: '{s}'", .{arg}); - } - } else { - try targets.append(arena, arg); - } - } - - const main_progress_node = std.Progress.start(io, .{ - .disable_printing = true, - }); - defer main_progress_node.end(); - - builder.resolveInstallPrefix(install_prefix, dir_list); - { - var prog_node = main_progress_node.start("Configure", 0); - defer prog_node.end(); - try builder.runBuild(root); - createModuleDependencies(builder) catch @panic("OOM"); - } - - if (graph.needed_lazy_dependencies.entries.len != 0) { - var buffer: std.ArrayList(u8) = .empty; - for (graph.needed_lazy_dependencies.keys()) |k| { - try buffer.appendSlice(arena, k); - try buffer.append(arena, '\n'); - } - const s = std.Io.Dir.path.sep_str; - const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{})); - local_cache_directory.handle.writeFile(io, .{ - .sub_path = tmp_sub_path, - .data = buffer.items, - .flags = .{ .exclusive = true }, - }) catch |err| { - fatal("unable to write configuration results to '{f}{s}': {s}", .{ - local_cache_directory, tmp_sub_path, @errorName(err), - }); - }; - process.exit(3); // Indicate configure phase failed with meaningful stdout. - } - - if (builder.validateUserInputDidItFail()) { - fatal(" access the help menu with 'zig build -h'", .{}); - } - - validateSystemLibraryOptions(builder); - - var run: Run = .{ - .gpa = gpa, - - .available_rss = max_rss, - .max_rss_is_default = false, - .max_rss_mutex = .init, - .skip_oom_steps = skip_oom_steps, - .watch = watch, - .memory_blocked_steps = .empty, - - .cycle = 0, - }; - defer run.memory_blocked_steps.deinit(gpa); - - if (run.available_rss == 0) { - run.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64); - run.max_rss_is_default = true; - } - - if (!watch) { - try extractBuildInformation( - builder, - arena, - main_progress_node, - &run, - ); - std.process.exit(0); - } - - var w = try Watch.init(io, graph.cache.cwd); - - const message_thread = try std.Thread.spawn(.{}, struct { - fn do(ww: *Watch) void { - while (true) { - var buffer: [1]u8 = undefined; - var stdin_reader = Io.File.stdin().reader(ww.io, &buffer); - const byte = stdin_reader.interface.takeByte() catch |err| switch (err) { - error.ReadFailed => process.exit(1), - error.EndOfStream => process.exit(0), - }; - switch (byte) { - '\x00' => ww.trigger(), - else => process.exit(1), - } - } - } - }.do, .{&w}); - message_thread.detach(); - - var step_stack = try resolveStepNames(gpa, builder, targets.items, check_step_only); - defer step_stack.deinit(gpa); - if (step_stack.count() == 0) { - // This means that `enable_build_on_save == null` and the project contains no "check" step. - return; - } - - const starting_steps = try gpa.dupe(*Step, step_stack.keys()); - defer gpa.free(starting_steps); - - prepare(builder, &step_stack, &run) catch |err| switch (err) { - error.DependencyLoopDetected => process.exit(1), - else => |e| return e, - }; - - rebuild: while (true) : (run.cycle += 1) { - try runSteps( - builder, - &step_stack, - main_progress_node, - &run, - ); - - try w.update(gpa, step_stack.keys()); - - // Wait until a file system notification arrives. Read all such events - // until the buffer is empty. Then wait for a debounce interval, resetting - // if any more events come in. After the debounce interval has passed, - // trigger a rebuild on all steps with modified inputs, as well as their - // recursive dependants. - var debounce_timeout: Io.Timeout = .none; - while (true) switch (try w.wait(gpa, debounce_timeout)) { - .timeout => { - markFailedStepsDirty(gpa, step_stack.keys()); - continue :rebuild; - }, - .dirty => if (debounce_timeout == .none) { - debounce_timeout = .{ .duration = .{ .raw = .fromMilliseconds(debounce_interval_ms), .clock = .real } }; - }, - .clean => {}, - }; - } -} - -fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { - for (all_steps) |step| switch (step.state) { - .dependency_failure, .failure, .skipped => _ = step.invalidateResult(gpa), - else => continue, - }; - // Now that all dirty steps have been found, the remaining steps that - // succeeded from last run shall be marked "cached". - for (all_steps) |step| switch (step.state) { - .success => step.result_cached = true, - else => continue, - }; -} - -/// A wrapper around `std.Build.Watch` that supports manually triggering recompilations. -const Watch = struct { - io: std.Io, - fs_watch: std.Build.Watch, - supports_fs_watch: bool, - manual_event: Io.Event, - steps: []const *Step, - - fn init(io: std.Io, cwd_path: []const u8) !Watch { - return .{ - .io = io, - .fs_watch = if (@TypeOf(std.Build.Watch) != void) try std.Build.Watch.init(cwd_path) else {}, - .supports_fs_watch = @TypeOf(std.Build.Watch) != void and shared.BuildOnSaveSupport.isSupportedRuntime(builtin.zig_version) == .supported, - .manual_event = .unset, - .steps = &.{}, - }; - } - - fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { - if (@TypeOf(std.Build.Watch) != void and w.supports_fs_watch) { - return try w.fs_watch.update(gpa, steps); - } - w.steps = steps; - } - - fn trigger(w: *Watch) void { - if (w.supports_fs_watch) { - @panic("received manualy filesystem event even though std.Build.Watch is supported"); - } - w.manual_event.set(w.io); - } - - fn wait(w: *Watch, gpa: Allocator, timeout: Io.Timeout) !std.Build.Watch.WaitResult { - if (@TypeOf(std.Build.Watch) != void and w.supports_fs_watch) { - return try w.fs_watch.wait(gpa, w.io, switch (timeout) { - .none => .none, - .duration => |d| .{ .ms = @intCast(d.raw.toMilliseconds()) }, - .deadline => unreachable, - }); - } - w.manual_event.waitTimeout(w.io, timeout) catch |err| switch (err) { - error.Canceled => unreachable, - error.Timeout => return .timeout, - }; - w.manual_event.reset(); - markStepsDirty(gpa, w.steps); - return .dirty; - } - - fn markStepsDirty(gpa: Allocator, all_steps: []const *Step) void { - for (all_steps) |step| switch (step.state) { - .precheck_done => continue, - else => _ = step.invalidateResult(gpa), - }; - } -}; - -const Run = struct { - gpa: Allocator, - - available_rss: usize, - max_rss_is_default: bool, - max_rss_mutex: Io.Mutex, - skip_oom_steps: bool, - watch: bool, - /// Allocated into `gpa`. - memory_blocked_steps: std.ArrayList(*Step), - - cycle: u32, -}; - -fn resolveStepNames( - gpa: Allocator, - b: *std.Build, - step_names: []const []const u8, - check_step_only: bool, -) !std.array_hash_map.Auto(*Step, void) { - var starting_steps: std.array_hash_map.Auto(*Step, void) = .empty; - errdefer starting_steps.deinit(gpa); - - if (step_names.len == 0) { - if (b.top_level_steps.get("check")) |tls| { - try starting_steps.put(gpa, &tls.step, {}); - } else if (!check_step_only) { - try starting_steps.put(gpa, b.default_step, {}); - } - } else { - try starting_steps.ensureUnusedCapacity(gpa, step_names.len); - for (0..step_names.len) |i| { - const step_name = step_names[step_names.len - i - 1]; - const s = b.top_level_steps.get(step_name) orelse { - std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name}); - process.exit(1); - }; - starting_steps.putAssumeCapacity(&s.step, {}); - } - } - - return starting_steps; -} - -fn prepare( - b: *std.Build, - unpopulated_step_stack: *std.array_hash_map.Auto(*Step, void), - run: *Run, -) error{ OutOfMemory, DependencyLoopDetected }!void { - const gpa = run.gpa; - - const starting_steps = try gpa.dupe(*Step, unpopulated_step_stack.keys()); - defer gpa.free(starting_steps); - - var rng = std.Random.DefaultPrng.init(b.graph.random_seed); - const rand = rng.random(); - rand.shuffle(*Step, starting_steps); - - for (starting_steps) |s| { - try constructGraphAndCheckForDependencyLoop(gpa, b, s, unpopulated_step_stack, rand); - } - const step_stack = unpopulated_step_stack; - - { - // Check that we have enough memory to complete the build. - var any_problems = false; - for (step_stack.keys()) |s| { - if (s.max_rss == 0) continue; - if (s.max_rss > run.available_rss) { - if (run.skip_oom_steps) { - s.state = .skipped_oom; - for (s.dependants.items) |dependant| { - dependant.pending_deps -= 1; - } - } else { - std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{ - s.owner.dep_prefix, s.name, s.max_rss, run.available_rss, - }); - any_problems = true; - } - } - } - if (any_problems) { - if (run.max_rss_is_default) { - std.debug.print("note: use --maxrss to override the default", .{}); - } - } - } -} - -fn runSteps( - b: *std.Build, - step_stack: *const std.array_hash_map.Auto(*Step, void), - parent_prog_node: std.Progress.Node, - run: *Run, -) (Io.Cancelable || mem.Allocator.Error)!void { - const gpa = run.gpa; - const io = b.graph.io; - - // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, - // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking - // a step is initial when it actually became ready due to an earlier initial step. - var initial_set: std.ArrayList(*Step) = .empty; - defer initial_set.deinit(gpa); - try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); - for (step_stack.keys()) |s| { - if (s.state == .precheck_done and s.pending_deps == 0) { - initial_set.appendAssumeCapacity(s); - } - } - - const step_prog = parent_prog_node.start("steps", step_stack.count()); - defer step_prog.end(); - - var group: Io.Group = .init; - defer group.cancel(io); - // Start working on all of the initial steps... - for (initial_set.items) |s| try stepReady(&group, b, step_stack, s, step_prog, run); - // ...and `makeStep` will trigger every other step when their last dependency finishes. - try group.await(io); -} - -/// Traverse the dependency graph depth-first and make it undirected by having -/// steps know their dependants (they only know dependencies at start). -/// Along the way, check that there is no dependency loop, and record the steps -/// in traversal order in `step_stack`. -/// Each step has its dependencies traversed in random order, this accomplishes -/// two things: -/// - `step_stack` will be in randomized-depth-first order, so the build runner -/// spawns initial steps in a random order -/// - each step's `dependants` list is also filled in a random order, so that -/// when it finishes executing in `makeStep`, it spawns next steps to run in -/// random order -fn constructGraphAndCheckForDependencyLoop( - gpa: Allocator, - b: *std.Build, - s: *Step, - step_stack: *std.array_hash_map.Auto(*Step, void), - rand: std.Random, -) !void { - switch (s.state) { - .precheck_started => { - return error.DependencyLoopDetected; - }, - .precheck_unstarted => { - s.state = .precheck_started; - - try step_stack.ensureUnusedCapacity(gpa, s.dependencies.items.len); - - // We dupe to avoid shuffling the steps in the summary, it depends - // on s.dependencies' order. - const deps = gpa.dupe(*Step, s.dependencies.items) catch @panic("OOM"); - defer gpa.free(deps); - - rand.shuffle(*Step, deps); - - for (deps) |dep| { - try step_stack.put(gpa, dep, {}); - try dep.dependants.append(b.allocator, s); - constructGraphAndCheckForDependencyLoop(gpa, b, dep, step_stack, rand) catch |err| { - return err; - }; - } - - s.state = .precheck_done; - s.pending_deps = @intCast(s.dependencies.items.len); - }, - .precheck_done => {}, - - // These don't happen until we actually run the step graph. - .dependency_failure => unreachable, - .success => unreachable, - .failure => unreachable, - .skipped => unreachable, - .skipped_oom => unreachable, - } -} - -/// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready -/// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must -/// have already subtracted this value from `run.available_rss`. This function will release the RSS -/// claim (i.e. add `s.max_rss` back into `run.available_rss`) and queue any viable memory-blocked -/// steps after "make" completes for `s`. -fn makeStep( - group: *Io.Group, - b: *std.Build, - steps_stack: *const std.array_hash_map.Auto(*Step, void), - s: *Step, - root_prog_node: std.Progress.Node, - run: *Run, -) Io.Cancelable!void { - const io = b.graph.io; - const gpa = run.gpa; - - { - const step_prog_node = root_prog_node.start(s.name, 0); - defer step_prog_node.end(); - - const new_state: Step.State = for (s.dependencies.items) |dep| { - switch (@atomicLoad(Step.State, &dep.state, .monotonic)) { - .precheck_unstarted => unreachable, - .precheck_started => unreachable, - .precheck_done => unreachable, - - .failure, - .dependency_failure, - .skipped_oom, - => break .dependency_failure, - - .success, .skipped => {}, - } - } else if (s.make(.{ - .progress_node = step_prog_node, - .watch = run.watch, - .web_server = null, - .unit_test_timeout_ns = null, - .gpa = gpa, - })) state: { - break :state .success; - } else |err| switch (err) { - error.MakeFailed => .failure, - error.MakeSkipped => .skipped, - }; - - @atomicStore(Step.State, &s.state, new_state, .monotonic); - - switch (new_state) { - .precheck_unstarted => unreachable, - .precheck_started => unreachable, - .precheck_done => unreachable, - - .failure, - .dependency_failure, - .skipped_oom, - => { - std.Progress.setStatus(.failure_working); - }, - - .success, - .skipped, - => {}, - } - } - - if (run.watch) { - const step_id: u32 = @intCast(steps_stack.getIndex(s).?); - // missing fields: - // - result_error_msgs - // - result_stderr - serveWatchErrorBundle(b.graph.io, step_id, run.cycle, s.result_error_bundle) catch @panic("failed to send watch errors"); - } - - if (s.max_rss != 0) { - var dispatch_set: std.ArrayList(*Step) = .empty; - defer dispatch_set.deinit(gpa); - - // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set` - // as a staging buffer to avoid recursing into `makeStep` while `run.max_rss_mutex` is held. - { - try run.max_rss_mutex.lock(io); - defer run.max_rss_mutex.unlock(io); - run.available_rss += s.max_rss; - dispatch_set.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch @panic("OOM"); - while (run.memory_blocked_steps.getLast()) |candidate| { - if (run.available_rss < candidate.max_rss) break; - assert(run.memory_blocked_steps.pop() == candidate); - dispatch_set.appendAssumeCapacity(candidate); - } - } - for (dispatch_set.items) |candidate| { - group.async(io, makeStep, .{ group, b, steps_stack, candidate, root_prog_node, run }); - } - } - - for (s.dependants.items) |dependant| { - // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0. - if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) { - try stepReady(group, b, steps_stack, dependant, root_prog_node, run); - } - } -} - -fn stepReady( - group: *Io.Group, - b: *std.Build, - steps_stack: *const std.array_hash_map.Auto(*Step, void), - s: *Step, - root_prog_node: std.Progress.Node, - run: *Run, -) !void { - const io = b.graph.io; - if (s.max_rss != 0) { - try run.max_rss_mutex.lock(io); - defer run.max_rss_mutex.unlock(io); - if (run.available_rss < s.max_rss) { - // Running this step right now could possibly exceed the allotted RSS. - run.memory_blocked_steps.append(run.gpa, s) catch @panic("OOM"); - return; - } - run.available_rss -= s.max_rss; - } - group.async(io, makeStep, .{ group, b, steps_stack, s, root_prog_node, run }); -} - -fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { - if (idx.* >= args.len) return null; - defer idx.* += 1; - return args[idx.*]; -} - -fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { - return nextArg(args, idx) orelse { - std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.* - 1]}); - process.exit(1); - }; -} - -fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 { - if (idx >= args.len) return null; - return args[idx..]; -} - -fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { - std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args); - process.exit(1); -} - -fn validateSystemLibraryOptions(b: *std.Build) void { - var bad = false; - for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| { - switch (v) { - .user_disabled, .user_enabled => { - // The user tried to enable or disable a system library integration, but - // the build script did not recognize that option. - std.debug.print("system library name not recognized by build script: '{s}'\n", .{k}); - bad = true; - }, - .declared_disabled, .declared_enabled => {}, - } - } - if (bad) { - std.debug.print(" access the help menu with 'zig build -h'\n", .{}); - process.exit(1); - } -} - -/// Starting from all top-level steps in `b`, traverses the entire step graph -/// and adds all step dependencies implied by module graphs. -fn createModuleDependencies(b: *std.Build) Allocator.Error!void { - const arena = b.graph.arena; - - var all_steps: std.array_hash_map.Auto(*Step, void) = .empty; - var next_step_idx: usize = 0; - - try all_steps.ensureUnusedCapacity(arena, b.top_level_steps.count()); - for (b.top_level_steps.values()) |tls| { - all_steps.putAssumeCapacityNoClobber(&tls.step, {}); - } - - while (next_step_idx < all_steps.count()) { - const step = all_steps.keys()[next_step_idx]; - next_step_idx += 1; - - // Set up any implied dependencies for this step. It's important that we do this first, so - // that the loop below discovers steps implied by the module graph. - try createModuleDependenciesForStep(step); - - try all_steps.ensureUnusedCapacity(arena, step.dependencies.items.len); - for (step.dependencies.items) |other_step| { - all_steps.putAssumeCapacity(other_step, {}); - } - } -} - -/// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which -/// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`. -fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { - const root_module = if (step.cast(Step.Compile)) |cs| root: { - break :root cs.root_module; - } else return; // not a compile step so no module dependencies - - // Starting from `root_module`, discover all modules in this graph. - const modules = root_module.getGraph().modules; - - // For each of those modules, set up the implied step dependencies. - for (modules) |mod| { - if (mod.root_source_file) |lp| lp.addStepDependencies(step); - for (mod.include_dirs.items) |include_dir| switch (include_dir) { - .path, - .path_system, - .path_after, - .framework_path, - .framework_path_system, - .embed_path, - => |lp| lp.addStepDependencies(step), - - .other_step => |other| { - other.getEmittedIncludeTree().addStepDependencies(step); - step.dependOn(&other.step); - }, - - .config_header_step => |other| step.dependOn(&other.step), - }; - for (mod.lib_paths.items) |lp| lp.addStepDependencies(step); - for (mod.rpaths.items) |rpath| switch (rpath) { - .lazy_path => |lp| lp.addStepDependencies(step), - .special => {}, - }; - for (mod.link_objects.items) |link_object| switch (link_object) { - .static_path, - .assembly_file, - => |lp| lp.addStepDependencies(step), - .other_step => |other| step.dependOn(&other.step), - .system_lib => {}, - .c_source_file => |source| source.file.addStepDependencies(step), - .c_source_files => |source_files| source_files.root.addStepDependencies(step), - .win32_resource_file => |rc_source| { - rc_source.file.addStepDependencies(step); - for (rc_source.include_paths) |lp| lp.addStepDependencies(step); - }, - }; - } -} - -// -// -// ZLS code -// -// - -const shared = @import("shared.zig"); -const BuildConfig = shared.BuildConfig; - -fn extractBuildInformation( - b: *std.Build, - arena: Allocator, - main_progress_node: std.Progress.Node, - run: *Run, -) !void { - const helper = struct { - fn addLazyPathStepDependencies(allocator: Allocator, set: *std.array_hash_map.Auto(*Step, void), lazy_path: std.Build.LazyPath) !void { - switch (lazy_path) { - .src_path, .cwd_relative, .dependency => {}, - .generated => |gen| try set.put(allocator, gen.file.step, {}), - } - } - /// Only adds the necessary dependencies to resolve the `root_source_file`. Does not include dependencies of imported modules. - fn addModuleDependencies(allocator: Allocator, set: *std.array_hash_map.Auto(*Step, void), module: *std.Build.Module) !void { - if (module.root_source_file) |root_source_file| { - try addLazyPathStepDependencies(allocator, set, root_source_file); - } - } - fn processModule( - allocator: Allocator, - modules: *std.array_hash_map.String(BuildConfig.Module), - module: *std.Build.Module, - compile: ?*Step.Compile, - ) !void { - _ = compile; - const root_source_file = module.root_source_file orelse return; - - const cwd = module.owner.graph.cache.cwd; - - const root_source_file_path = try std.Io.Dir.path.resolve(allocator, &.{ cwd, root_source_file.getPath2(module.owner, null) }); - - // All modules with the same root source file are merged. This limitation may be lifted in the future. - const gop = try modules.getOrPutValue(allocator, root_source_file_path, .{ - .import_table = .{}, - }); - - for (module.import_table.keys(), module.import_table.values()) |name, import| { - const import_root_source_file = import.root_source_file orelse continue; - const gop_import = try gop.value_ptr.import_table.map.getOrPut(allocator, name); - // This does not account for the possibility of collisions (i.e. modules with same root source file import different modules under the same name). - if (!gop_import.found_existing) { - gop_import.value_ptr.* = try std.Io.Dir.path.resolve(allocator, &.{ cwd, import_root_source_file.getPath2(import.owner, null) }); - } - } - } - }; - const gpa = run.gpa; - - // The value tracks whether the step is a decendant of the "install" step. - var all_steps: std.array_hash_map.Auto(*Step, bool) = .empty; - defer all_steps.deinit(gpa); - - // collect all steps that are decendants of the "install" step. - { - try all_steps.putNoClobber(gpa, b.getInstallStep(), true); - - var i: usize = 0; - while (i < all_steps.count()) : (i += 1) { - const step = all_steps.keys()[i]; - - try all_steps.ensureUnusedCapacity(gpa, step.dependencies.items.len); - for (step.dependencies.items) |other_step| { - all_steps.putAssumeCapacity(other_step, true); - } - } - } - - // collect all other steps - { - var i: usize = all_steps.count(); - - try all_steps.ensureUnusedCapacity(gpa, b.top_level_steps.count()); - for (b.top_level_steps.values()) |tls| { - all_steps.putAssumeCapacity(&tls.step, true); - } - - while (i < all_steps.count()) : (i += 1) { - const step = all_steps.keys()[i]; - - try all_steps.ensureUnusedCapacity(gpa, step.dependencies.items.len); - for (step.dependencies.items) |other_step| { - all_steps.putAssumeCapacity(other_step, false); - } - } - } - - // Collect all steps that need to be run so that we can resolve the lazy paths we are interested in (e.g. root_source_file). - { - var needed_steps: std.array_hash_map.Auto(*Step, void) = .empty; - defer needed_steps.deinit(gpa); - - var modules: std.array_hash_map.Auto(*std.Build.Module, void) = .empty; - defer modules.deinit(gpa); - - // collect all modules of root modules - for (b.modules.values()) |root_module| { - const graph = root_module.getGraph(); - try modules.ensureUnusedCapacity(gpa, graph.modules.len); - for (graph.modules) |module| modules.putAssumeCapacity(module, {}); - } - - // collect all modules of `Step.Compile` - for (all_steps.keys()) |step| { - const compile = step.cast(Step.Compile) orelse continue; - const graph = compile.root_module.getGraph(); - try modules.ensureUnusedCapacity(gpa, graph.modules.len); - for (graph.modules) |module| modules.putAssumeCapacity(module, {}); - } - - // collect all dependencies of all found modules - for (modules.keys()) |module| { - try helper.addModuleDependencies(gpa, &needed_steps, module); - } - - prepare(b, &needed_steps, run) catch |err| switch (err) { - error.DependencyLoopDetected => process.exit(1), - else => |e| return e, - }; - - try runSteps( - b, - &needed_steps, - main_progress_node, - run, - ); - } - - // We collect modules in the following order: - // - public modules (`std.Build.addModule`) - // - modules that are reachable from the "install" step - // - all other reachable modules - var modules: std.array_hash_map.String(BuildConfig.Module) = .empty; - - for (b.modules.values()) |root_module| { - const graph = root_module.getGraph(); - for (graph.modules) |module| { - try helper.processModule(arena, &modules, module, null); - } - } - - // We loop twice through all steps so that decendants of the "install" step are processed first. - for ([_]bool{ true, false }) |want_install_step_decendant| { - for (all_steps.keys(), all_steps.values()) |step, is_install_step_decendant| { - if (is_install_step_decendant != want_install_step_decendant) continue; - - const compile = step.cast(Step.Compile) orelse continue; - const graph = compile.root_module.getGraph(); - for (graph.modules) |module| { - try helper.processModule(arena, &modules, module, compile); - } - } - } - - var compilations: std.ArrayList(BuildConfig.Compile) = .empty; - for (all_steps.keys()) |step| { - const compile = step.cast(Step.Compile) orelse continue; - const root_source_file = compile.root_module.root_source_file orelse continue; - const root_source_file_path = try std.Io.Dir.path.resolve(arena, &.{ b.graph.cache.cwd, root_source_file.getPath2(compile.root_module.owner, null) }); - try compilations.append(arena, .{ - .root_module = root_source_file_path, - }); - } - - // Sample `@dependencies` structure: - // pub const packages = struct { - // pub const @"1220363c7e27b2d3f39de6ff6e90f9537a0634199860fea237a55ddb1e1717f5d6a5" = struct { - // pub const build_root = "/home/rad/.cache/zig/p/1220363c7e27b2d3f39de6ff6e90f9537a0634199860fea237a55ddb1e1717f5d6a5"; - // pub const build_zig = @import("1220363c7e27b2d3f39de6ff6e90f9537a0634199860fea237a55ddb1e1717f5d6a5"); - // pub const deps: []const struct { []const u8, []const u8 } = &.{}; - // }; - // ... - // }; - // pub const root_deps: []const struct { []const u8, []const u8 } = &.{ - // .{ "known_folders", "1220bb12c9bfe291eed1afe6a2070c7c39918ab1979f24a281bba39dfb23f5bcd544" }, - // .{ "diffz", "122089a8247a693cad53beb161bde6c30f71376cd4298798d45b32740c3581405864" }, - // }; - - // Collect the dependencies from `build.zig.zon` - var root_dependencies: std.array_hash_map.String([]const u8) = .empty; - for (dependencies.root_deps) |root_dep| { - inline for (comptime std.meta.declarations(dependencies.packages)) |package| blk: { - if (std.mem.eql(u8, package.name, root_dep[1])) { - const package_info = @field(dependencies.packages, package.name); - if (!@hasDecl(package_info, "build_root")) break :blk; - if (!@hasDecl(package_info, "build_zig")) break :blk; - try root_dependencies.put( - arena, - root_dep[0], - try std.Io.Dir.path.join(arena, &.{ package_info.build_root, "build.zig" }), - ); - } - } - } - - var available_options: std.array_hash_map.String(BuildConfig.AvailableOption) = .empty; - try available_options.ensureTotalCapacity(arena, b.available_options_map.count()); - - var it = b.available_options_map.iterator(); - while (it.next()) |available_option| { - available_options.putAssumeCapacityNoClobber(available_option.key_ptr.*, available_option.value_ptr.*); - } - - const stringified_build_config = try std.json.Stringify.valueAlloc( - gpa, - BuildConfig{ - .dependencies = .{ .map = root_dependencies }, - .modules = .{ .map = modules }, - .compilations = compilations.items, - .top_level_steps = b.top_level_steps.keys(), - .available_options = .{ .map = available_options }, - }, - .{ .whitespace = .indent_2 }, - ); - - var file_writer = Io.File.stdout().writer(b.graph.io, &.{}); - file_writer.interface.writeAll(stringified_build_config) catch return file_writer.err.?; -} - -fn serveWatchErrorBundle( - io: std.Io, - step_id: u32, - cycle: u32, - error_bundle: std.zig.ErrorBundle, -) Io.File.Writer.Error!void { - const bytes_len = @sizeOf(shared.ServerToClient.ErrorBundle) + @sizeOf(u32) * error_bundle.extra.len + error_bundle.string_bytes.len; - - var header: shared.ServerToClient.Header = .{ - .tag = .watch_error_bundle, - .bytes_len = @intCast(bytes_len), - }; - - var error_bundle_header: shared.ServerToClient.ErrorBundle = .{ - .step_id = step_id, - .cycle = cycle, - .extra_len = @intCast(error_bundle.extra.len), - .string_bytes_len = @intCast(error_bundle.string_bytes.len), - }; - - const need_bswap = builtin.target.cpu.arch.endian() != .little; - - if (need_bswap) { - std.mem.byteSwapAllFields(shared.ServerToClient.Header, &header); - std.mem.byteSwapAllFields(shared.ServerToClient.ErrorBundle, &error_bundle_header); - std.mem.byteSwapAllElements(u32, @constCast(error_bundle.extra)); // trust me bro - } - - var file_writer = Io.File.stdout().writer(io, &.{}); - const writer = &file_writer.interface; - - var data = [_][]const u8{ - std.mem.asBytes(&header), - std.mem.asBytes(&error_bundle_header), - std.mem.sliceAsBytes(error_bundle.extra), - error_bundle.string_bytes, - }; - writer.writeVecAll(&data) catch return file_writer.err.?; -} diff --git a/src/build_runner/check.zig b/src/build_runner/check.zig deleted file mode 100644 index 822116cf1f..0000000000 --- a/src/build_runner/check.zig +++ /dev/null @@ -1,283 +0,0 @@ -const std = @import("std"); - -pub fn isBuildRunnerSupported(runtime_zig_version: std.SemanticVersion) bool { - const build_options = @import("build_options"); - const is_zls_version_tagged = build_options.version.pre == null; - const min_runtime_zig_version = comptime std.SemanticVersion.parse(build_options.minimum_runtime_zig_version_string) catch unreachable; - return isBuildRunnerSupportedInternal( - min_runtime_zig_version, - runtime_zig_version, - is_zls_version_tagged, - ); -} - -fn isBuildRunnerSupportedInternal( - minimum_zig_version: std.SemanticVersion, - param_zig_version: std.SemanticVersion, - /// If set, a non-tagged zig version cannot be used when `minimum_runtime_zig_version` is a tagged version. - /// Example: Zig `0.13.0-dev` cannot be used when the minimum Zig version is `0.12.0`. - /// - /// Will be set iff ZLS is a tagged release. - strict: bool, -) bool { - const minimum_version_is_tagged = minimum_zig_version.pre == null; - - var zig_version = param_zig_version; - var version_is_tagged = zig_version.pre == null; - - if (!version_is_tagged and zig_version.patch != 0) { - // A zig version like `0.12.2-dev` has the same compatibility as `0.12.1` - zig_version.patch -= 1; - zig_version.pre = null; - version_is_tagged = true; - } - - if (strict and !version_is_tagged) { - std.debug.assert(minimum_version_is_tagged); - // A tagged release of ZLS must be used with a tagged release of Zig. - return false; - } - - if (zig_version.major != minimum_zig_version.major) return false; - - if (minimum_version_is_tagged) { - if (version_is_tagged) { - if (zig_version.order(minimum_zig_version) == .lt) return false; - const next_minor_release: std.SemanticVersion = .{ - .major = minimum_zig_version.major, - .minor = minimum_zig_version.minor + 1, - .patch = 0, - }; - return zig_version.order(next_minor_release) == .lt; - } else { - std.debug.assert(zig_version.patch == 0); - return zig_version.minor == 1 + minimum_zig_version.minor; - } - } else { - if (version_is_tagged) return false; - if (zig_version.minor != minimum_zig_version.minor) return false; - return zig_version.order(minimum_zig_version) != .lt; - } -} - -test { - // The build runner must support the Zig version that ZLS is being built with - const current_zig_version = @import("builtin").zig_version; - try std.testing.expect(isBuildRunnerSupported(current_zig_version)); - const is_zls_version_tagged_release = current_zig_version.pre == null; - - if (is_zls_version_tagged_release) { - // A tagged release of ZLS should support the same tagged release of Zig - // Example: ZLS 0.12.0 should support Zig 0.12.x -- It is possible that ZLS requires a minimum patch version - try std.testing.expect(isBuildRunnerSupported(.{ .major = current_zig_version.major, .minor = current_zig_version.minor, .patch = 999 })); - } -} - -// Version order for reference: -// 0.11.0-dev < 0.11.0 < 0.12.0-dev < 0.12.0 < 0.13.0-dev < 0.13.0 - -test isBuildRunnerSupportedInternal { - var did_fail = false; - for (test_cases) |test_case| { - const minimum_runtime_zig_version: std.SemanticVersion = try .parse(test_case.minimum_runtime_zig_version); - const runtime_zig_version: std.SemanticVersion = try .parse(test_case.runtime_zig_version); - const minimum_runtime_version_is_tagged = minimum_runtime_zig_version.pre == null; - const expected_if_strict, const expected_if_not_strict = switch (test_case.is_supported) { - .yes => .{ true, true }, - .no => .{ false, false }, - .if_not_strict => .{ false, true }, - }; - - const actual_if_not_strict = isBuildRunnerSupportedInternal(minimum_runtime_zig_version, runtime_zig_version, false); - if (expected_if_not_strict != actual_if_not_strict) { - std.debug.print("minimum={f}, actual={f}, strict={} -> expected {} but got {}\n", .{ - minimum_runtime_zig_version, - runtime_zig_version, - false, - expected_if_not_strict, - actual_if_not_strict, - }); - did_fail = true; - } - - if (minimum_runtime_version_is_tagged) { - const actual_if_strict = isBuildRunnerSupportedInternal(minimum_runtime_zig_version, runtime_zig_version, true); - if (expected_if_strict != actual_if_strict) { - std.debug.print("minimum={f}, actual={f}, strict={} -> expected {} but got {}\n", .{ - minimum_runtime_zig_version, - runtime_zig_version, - true, - expected_if_strict, - actual_if_strict, - }); - did_fail = true; - } - } - } - if (did_fail) return error.Unexpected; -} - -const test_cases: []const struct { - minimum_runtime_zig_version: []const u8, - runtime_zig_version: []const u8, - is_supported: enum { yes, no, if_not_strict }, -} = &.{ - // Minimum Zig Version: 0.12.0 - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.11.0-dev", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.11.0", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.12.0-dev", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.12.0", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.12.1-dev", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.12.1", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.13.0-dev", - .is_supported = .if_not_strict, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.13.0", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0", - .runtime_zig_version = "0.13.1-dev", - .is_supported = .no, - }, - // Minimum Zig Version: 0.12.1 - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.11.0", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.11.1", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.12.0-dev", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.12.0", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.12.1-dev", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.12.1", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.12.2-dev", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.12.2", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.13.0-dev", - .is_supported = .if_not_strict, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.13.0", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.13.1-dev", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.1", - .runtime_zig_version = "0.13.1", - .is_supported = .no, - }, - // Minimum Zig Version: 0.12.0-dev.5 - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.11.0-dev", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.11.0", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.12.0-dev.1", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.12.0-dev.4", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.12.0-dev.5", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.12.0-dev.10", - .is_supported = .yes, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.12.0", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.12.1", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.13.0-dev.10", - .is_supported = .no, - }, - .{ - .minimum_runtime_zig_version = "0.12.0-dev.5", - .runtime_zig_version = "0.13.0", - .is_supported = .no, - }, -}; diff --git a/src/build_runner/shared.zig b/src/build_runner/shared.zig deleted file mode 100644 index 486fe5a08e..0000000000 --- a/src/build_runner/shared.zig +++ /dev/null @@ -1,158 +0,0 @@ -//! Contains shared code between ZLS and it's custom build runner - -const std = @import("std"); -const builtin = @import("builtin"); - -pub const BuildConfig = struct { - /// The `dependencies` in `build.zig.zon`. - dependencies: std.json.ArrayHashMap([]const u8), - /// The key is the `root_source_file`. - /// All modules with the same root source file are merged. This limitation may be lifted in the future. - modules: std.json.ArrayHashMap(Module), - /// List of all compilations units. - compilations: []const Compile, - /// The names of all top level steps. - top_level_steps: []const []const u8, - available_options: std.json.ArrayHashMap(AvailableOption), - - pub const Module = struct { - import_table: std.json.ArrayHashMap([]const u8), - }; - - pub const Compile = struct { - /// Key in `BuildConfig.modules`. - root_module: []const u8, - - // may contain additional information in the future like `target` or `link_libc`. - }; - - /// Equivalent to `std.Build.AvailableOption` which is not accessible because it non-pub. - pub const AvailableOption = @FieldType(@FieldType(std.Build, "available_options_map").KV, "value"); -}; - -pub const ServerToClient = struct { - pub const Header = extern struct { - tag: Tag, - bytes_len: u32, - - pub const Tag = enum(u32) { - /// Body is an ErrorBundle. - watch_error_bundle, - - _, - }; - }; - - /// Trailing: - /// * extra: [extra_len]u32, - /// * string_bytes: [string_bytes_len]u8, - /// See `std.zig.ErrorBundle`. - pub const ErrorBundle = extern struct { - step_id: u32, - cycle: u32, - extra_len: u32, - string_bytes_len: u32, - }; -}; - -pub const BuildOnSaveSupport = union(enum) { - supported, - invalid_linux_kernel_version: if (builtin.os.tag == .linux) @FieldType(std.os.linux.utsname, "release") else noreturn, - unsupported_linux_kernel_version: if (builtin.os.tag == .linux) std.SemanticVersion else noreturn, - unsupported_zig_version: if (@TypeOf(os_support) == std.SemanticVersion) void else noreturn, - unsupported_os: if (@TypeOf(os_support) == bool and !os_support) void else noreturn, - - // We can't rely on `std.Build.Watch.have_impl` because we need to - // check the runtime Zig version instead of Zig version that ZLS - // has been built with. - pub const os_support = switch (builtin.os.tag) { - .linux, - .windows, - .dragonfly, - .freebsd, - .netbsd, - .openbsd, - .ios, - .macos, - .tvos, - .visionos, - .watchos, - .haiku, - => true, - else => false, - }; - - /// std.build.Watch requires `AT_HANDLE_FID` which is Linux 6.5+ - /// https://github.com/ziglang/zig/issues/20720 - pub const minimum_linux_version: std.SemanticVersion = .{ .major = 6, .minor = 5, .patch = 0 }; - - /// Returns true if is comptime known that build on save is supported. - pub inline fn isSupportedComptime() bool { - if (!std.process.can_spawn) return false; - if (builtin.single_threaded) return false; - return true; - } - - pub fn isSupportedRuntime(runtime_zig_version: std.SemanticVersion) BuildOnSaveSupport { - comptime std.debug.assert(isSupportedComptime()); - - if (builtin.os.tag == .linux) blk: { - var utsname: std.os.linux.utsname = undefined; - std.debug.assert(std.os.linux.uname(&utsname) == 0); - const unparsed_version = std.mem.sliceTo(&utsname.release, 0); - const version = parseUnameKernelVersion(unparsed_version) catch - return .{ .invalid_linux_kernel_version = utsname.release }; - - if (version.order(minimum_linux_version) != .lt) break :blk; - std.debug.assert(version.build == null and version.pre == null); // Otherwise, returning the `std.SemanticVersion` would be unsafe - return .{ - .unsupported_linux_kernel_version = version, - }; - } - - switch (@TypeOf(os_support)) { - bool => { - if (!os_support) { - return .unsupported_os; - } - }, - std.SemanticVersion => { - if (runtime_zig_version.order(os_support) == .lt) { - return .unsupported_zig_version; - } - }, - else => unreachable, - } - - return .supported; - } -}; - -/// Parses a Linux Kernel Version. The result will ignore pre-release and build metadata. -fn parseUnameKernelVersion(kernel_version: []const u8) !std.SemanticVersion { - const extra_index = for (kernel_version, 0..) |c, i| { - switch (c) { - '-', '+' => break i, - else => continue, - } - } else null; - const required = kernel_version[0..(extra_index orelse kernel_version.len)]; - var it = std.mem.splitScalar(u8, required, '.'); - return .{ - .major = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), - .minor = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), - .patch = try std.fmt.parseUnsigned(usize, it.next() orelse return error.InvalidVersion, 10), - }; -} - -test parseUnameKernelVersion { - try std.testing.expectFmt("5.17.0", "{f}", .{try parseUnameKernelVersion("5.17.0")}); - try std.testing.expectFmt("6.12.9", "{f}", .{try parseUnameKernelVersion("6.12.9-rc7")}); - try std.testing.expectFmt("6.6.71", "{f}", .{try parseUnameKernelVersion("6.6.71-42-generic")}); - try std.testing.expectFmt("5.15.167", "{f}", .{try parseUnameKernelVersion("5.15.167.4-microsoft-standard-WSL2")}); // WSL2 - try std.testing.expectFmt("4.4.0", "{f}", .{try parseUnameKernelVersion("4.4.0-20241-Microsoft")}); // WSL1 - - try std.testing.expectError(error.InvalidCharacter, parseUnameKernelVersion("")); - try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5")); - try std.testing.expectError(error.InvalidVersion, parseUnameKernelVersion("5.5")); -} diff --git a/src/configuration.zig b/src/configuration.zig index 138903bc70..54cb586106 100644 --- a/src/configuration.zig +++ b/src/configuration.zig @@ -17,6 +17,8 @@ pub const Manager = struct { path: []const u8, version: std.SemanticVersion, env: Env, + /// Whether the `version` satifies the minimum runtime zig version. + supported: bool, }, zig_lib_dir: ?std.Build.Cache.Directory, global_cache_dir: ?std.Build.Cache.Directory, @@ -24,14 +26,6 @@ pub const Manager = struct { .wasi => std.process.Preopens, else => void, }, - build_runner_supported: union(enum) { - /// If returned, guarantees `zig_exe != null`. - yes, - /// no suitable build runner could be resolved based on the `zig_exe` - /// If returned, guarantees `zig_exe != null`. - no, - no_dont_error, - }, impl: struct { is_dirty: bool, configs: std.EnumArray(Tag, UnresolvedConfig), @@ -52,7 +46,6 @@ pub const Manager = struct { .zig_exe = null, .zig_lib_dir = null, .global_cache_dir = null, - .build_runner_supported = .no_dont_error, .wasi_preopens = switch (builtin.os.tag) { .wasi => try std.process.Preopens.init(arena_allocator.allocator()), else => {}, @@ -182,10 +175,15 @@ pub const Manager = struct { break :unresolved_zig; }; + const build_options = @import("build_options"); + const is_zls_version_tagged = build_options.version.pre == null; + const min_runtime_zig_version = comptime std.SemanticVersion.parse(build_options.minimum_runtime_zig_version_string) catch unreachable; + manager.zig_exe = .{ .path = zig_env.zig_exe, .version = zig_version, .env = zig_env, + .supported = zigVersionCheck(min_runtime_zig_version, zig_version, is_zls_version_tagged), }; } config.zig_exe_path = if (manager.zig_exe) |exe| exe.path else null; @@ -253,71 +251,6 @@ pub const Manager = struct { comptime unreachable; } - if (config.build_runner_path == null) blk: { - if (!std.process.can_spawn) break :blk; - const zig_exe = manager.zig_exe orelse break :blk; - const global_cache_dir = manager.global_cache_dir orelse break :blk; - - if (!@import("build_runner/check.zig").isBuildRunnerSupported(zig_exe.version)) { - manager.build_runner_supported = .no; - break :blk; - } - - const build_runner_source = @embedFile("build_runner/build_runner.zig"); - const build_runner_config_source = @embedFile("build_runner/shared.zig"); - - const build_runner_hash = get_hash: { - const Hasher = std.crypto.auth.siphash.SipHash128(1, 3); - - var hasher: Hasher = .init(&@splat(0)); - hasher.update(build_runner_source); - hasher.update(build_runner_config_source); - break :get_hash hasher.finalResult(); - }; - - const cache_path = try global_cache_dir.join(manager.allocator, &.{ "build_runner", &std.fmt.bytesToHex(build_runner_hash, .lower) }); - defer manager.allocator.free(cache_path); - - std.debug.assert(std.Io.Dir.path.isAbsolute(cache_path)); - var cache_dir = std.Io.Dir.cwd().createDirPathOpen(io, cache_path, .{}) catch |err| switch (err) { - error.Canceled => return error.Canceled, - else => { - log.err("failed to open directory '{s}': {}", .{ cache_path, err }); - break :blk; - }, - }; - defer cache_dir.close(io); - - cache_dir.writeFile(io, .{ - .sub_path = "shared.zig", - .data = build_runner_config_source, - .flags = .{ .exclusive = true }, - }) catch |err| switch (err) { - error.Canceled => return error.Canceled, - error.PathAlreadyExists => {}, - else => { - log.err("failed to write file '{s}/shared.zig': {}", .{ cache_path, err }); - break :blk; - }, - }; - - cache_dir.writeFile(io, .{ - .sub_path = "build_runner.zig", - .data = build_runner_source, - .flags = .{ .exclusive = true }, - }) catch |err| switch (err) { - error.Canceled => return error.Canceled, - error.PathAlreadyExists => {}, - else => { - log.err("failed to write file '{s}/build_runner.zig': {}", .{ cache_path, err }); - break :blk; - }, - }; - - config.build_runner_path = try std.Io.Dir.path.join(arena, &.{ cache_path, "build_runner.zig" }); - manager.build_runner_supported = .yes; - } - if (config.builtin_path == null) blk: { if (!std.process.can_spawn) break :blk; const zig_exe = manager.zig_exe orelse break :blk; @@ -672,7 +605,6 @@ pub const FileConfigInfo = struct { pub const file_system_config_options: []const FileConfigInfo = &.{ .{ .name = "zig_exe_path", .kind = .file, .is_accessible = true }, .{ .name = "builtin_path", .kind = .file, .is_accessible = true }, - .{ .name = "build_runner_path", .kind = .file, .is_accessible = true }, .{ .name = "zig_lib_path", .kind = .directory, .is_accessible = true }, .{ .name = "global_cache_path", .kind = .directory, .is_accessible = false }, }; @@ -775,3 +707,285 @@ pub fn findZig( } return null; } + +fn zigVersionCheck( + minimum_zig_version: std.SemanticVersion, + param_zig_version: std.SemanticVersion, + /// If set, a non-tagged zig version cannot be used when `minimum_runtime_zig_version` is a tagged version. + /// Example: Zig `0.13.0-dev` cannot be used when the minimum Zig version is `0.12.0`. + /// + /// Will be set iff ZLS is a tagged release. + strict: bool, +) bool { + const minimum_version_is_tagged = minimum_zig_version.pre == null; + + var zig_version = param_zig_version; + var version_is_tagged = zig_version.pre == null; + + if (!version_is_tagged and zig_version.patch != 0) { + // A zig version like `0.12.2-dev` has the same compatibility as `0.12.1` + zig_version.patch -= 1; + zig_version.pre = null; + version_is_tagged = true; + } + + if (strict and !version_is_tagged) { + std.debug.assert(minimum_version_is_tagged); + // A tagged release of ZLS must be used with a tagged release of Zig. + return false; + } + + if (zig_version.major != minimum_zig_version.major) return false; + + if (minimum_version_is_tagged) { + if (version_is_tagged) { + if (zig_version.order(minimum_zig_version) == .lt) return false; + const next_minor_release: std.SemanticVersion = .{ + .major = minimum_zig_version.major, + .minor = minimum_zig_version.minor + 1, + .patch = 0, + }; + return zig_version.order(next_minor_release) == .lt; + } else { + std.debug.assert(zig_version.patch == 0); + return zig_version.minor == 1 + minimum_zig_version.minor; + } + } else { + if (version_is_tagged) return false; + if (zig_version.minor != minimum_zig_version.minor) return false; + return zig_version.order(minimum_zig_version) != .lt; + } +} + +test { + const build_options = @import("build_options"); + const current_zig_version = @import("builtin").zig_version; + const is_zls_version_tagged = build_options.version.pre == null; + const min_runtime_zig_version = comptime std.SemanticVersion.parse(build_options.minimum_runtime_zig_version_string) catch unreachable; + + // The build runner must support the Zig version that ZLS is being built with + try std.testing.expect(zigVersionCheck( + min_runtime_zig_version, + current_zig_version, + is_zls_version_tagged, + )); + + if (is_zls_version_tagged) { + // A tagged release of ZLS should support the same tagged release of Zig + // Example: ZLS 0.12.0 should support Zig 0.12.x -- It is possible that ZLS requires a minimum patch version + try std.testing.expect(zigVersionCheck( + .{ .major = current_zig_version.major, .minor = current_zig_version.minor, .patch = 999 }, + min_runtime_zig_version, + is_zls_version_tagged, + )); + } +} + +// Version order for reference: +// 0.11.0-dev < 0.11.0 < 0.12.0-dev < 0.12.0 < 0.13.0-dev < 0.13.0 + +test zigVersionCheck { + var did_fail = false; + for (test_cases) |test_case| { + const minimum_runtime_zig_version: std.SemanticVersion = try .parse(test_case.minimum_runtime_zig_version); + const runtime_zig_version: std.SemanticVersion = try .parse(test_case.runtime_zig_version); + const minimum_runtime_version_is_tagged = minimum_runtime_zig_version.pre == null; + const expected_if_strict, const expected_if_not_strict = switch (test_case.is_supported) { + .yes => .{ true, true }, + .no => .{ false, false }, + .if_not_strict => .{ false, true }, + }; + + const actual_if_not_strict = zigVersionCheck(minimum_runtime_zig_version, runtime_zig_version, false); + if (expected_if_not_strict != actual_if_not_strict) { + std.debug.print("minimum={f}, actual={f}, strict={} -> expected {} but got {}\n", .{ + minimum_runtime_zig_version, + runtime_zig_version, + false, + expected_if_not_strict, + actual_if_not_strict, + }); + did_fail = true; + } + + if (minimum_runtime_version_is_tagged) { + const actual_if_strict = zigVersionCheck(minimum_runtime_zig_version, runtime_zig_version, true); + if (expected_if_strict != actual_if_strict) { + std.debug.print("minimum={f}, actual={f}, strict={} -> expected {} but got {}\n", .{ + minimum_runtime_zig_version, + runtime_zig_version, + true, + expected_if_strict, + actual_if_strict, + }); + did_fail = true; + } + } + } + if (did_fail) return error.Unexpected; +} + +const test_cases: []const struct { + minimum_runtime_zig_version: []const u8, + runtime_zig_version: []const u8, + is_supported: enum { yes, no, if_not_strict }, +} = &.{ + // Minimum Zig Version: 0.12.0 + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.11.0-dev", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.11.0", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.12.0-dev", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.12.0", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.12.1-dev", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.12.1", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.13.0-dev", + .is_supported = .if_not_strict, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.13.0", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0", + .runtime_zig_version = "0.13.1-dev", + .is_supported = .no, + }, + // Minimum Zig Version: 0.12.1 + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.11.0", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.11.1", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.12.0-dev", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.12.0", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.12.1-dev", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.12.1", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.12.2-dev", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.12.2", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.13.0-dev", + .is_supported = .if_not_strict, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.13.0", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.13.1-dev", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.1", + .runtime_zig_version = "0.13.1", + .is_supported = .no, + }, + // Minimum Zig Version: 0.12.0-dev.5 + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.11.0-dev", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.11.0", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.12.0-dev.1", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.12.0-dev.4", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.12.0-dev.5", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.12.0-dev.10", + .is_supported = .yes, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.12.0", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.12.1", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.13.0-dev.10", + .is_supported = .no, + }, + .{ + .minimum_runtime_zig_version = "0.12.0-dev.5", + .runtime_zig_version = "0.13.0", + .is_supported = .no, + }, +}; diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 2690a9ae88..1c259010ad 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -495,255 +495,3 @@ pub fn getErrorBundleFromStderr( return try error_bundle.toOwnedBundle(""); } - -pub const BuildOnSave = struct { - io: std.Io, - allocator: std.mem.Allocator, - worker: std.Io.Future(void), - worker_state: *WorkerState, - - const shared = @import("../build_runner/shared.zig"); - const ServerToClient = shared.ServerToClient; - - const WorkerState = struct { - mutex: std.Io.Mutex, - child_process: std.process.Child, - }; - - pub const InitOptions = struct { - io: std.Io, - allocator: std.mem.Allocator, - workspace_path: []const u8, - build_on_save_args: []const []const u8, - check_step_only: bool, - zig_exe_path: []const u8, - zig_lib_path: []const u8, - build_runner_path: []const u8, - - collection: *DiagnosticsCollection, - }; - - pub fn init(options: InitOptions) error{ Canceled, ConcurrencyUnavailable, OutOfMemory }!?BuildOnSave { - const base_args: []const []const u8 = &.{ - options.zig_exe_path, - "build", - "--build-runner", - options.build_runner_path, - "--zig-lib-dir", - options.zig_lib_path, - "--watch", - }; - var argv: std.ArrayList([]const u8) = try .initCapacity( - options.allocator, - base_args.len + options.build_on_save_args.len + @intFromBool(options.check_step_only), - ); - defer argv.deinit(options.allocator); - - argv.appendSliceAssumeCapacity(base_args); - if (options.check_step_only) argv.appendAssumeCapacity("--check-only"); - argv.appendSliceAssumeCapacity(options.build_on_save_args); - - var child_process = std.process.spawn(options.io, .{ - .argv = argv.items, - .stdin = .pipe, - .stdout = .pipe, - .stderr = .pipe, - .cwd = .{ .path = options.workspace_path }, - }) catch |err| switch (err) { - error.Canceled => return error.Canceled, - else => { - log.err("failed to spawn zig build-on-save process: {}", .{err}); - return null; - }, - }; - errdefer child_process.kill(options.io); - - const duped_workspace_path = try options.allocator.dupe(u8, options.workspace_path); - errdefer options.allocator.free(duped_workspace_path); - - const worker_state = try options.allocator.create(WorkerState); - errdefer options.allocator.destroy(worker_state); - - worker_state.* = .{ - .mutex = .init, - .child_process = child_process, - }; - - const worker = try options.io.concurrent(loop, .{ - options.io, - options.allocator, - worker_state, - options.collection, - duped_workspace_path, - }); - errdefer comptime unreachable; - - return .{ - .io = options.io, - .allocator = options.allocator, - .worker = worker, - .worker_state = worker_state, - }; - } - - pub fn deinit(self: *BuildOnSave) void { - self.worker.cancel(self.io); - self.allocator.destroy(self.worker_state); - self.* = undefined; - } - - pub fn sendManualWatchUpdate(build_on_save: *BuildOnSave) void { - const io = build_on_save.io; - - build_on_save.worker_state.mutex.lockUncancelable(io); - defer build_on_save.worker_state.mutex.unlock(io); - if (build_on_save.worker_state.child_process.stdin) |stdin| { - const old_cancel_protect = io.swapCancelProtection(.blocked); - defer _ = io.swapCancelProtection(old_cancel_protect); - stdin.writeStreamingAll(io, "\x00") catch {}; - } - } - - fn loop( - io: std.Io, - allocator: std.mem.Allocator, - state: *WorkerState, - collection: *DiagnosticsCollection, - workspace_path: []const u8, - ) void { - defer allocator.free(workspace_path); - - var multi_reader_buffer: std.Io.File.MultiReader.Buffer(2) = undefined; - var multi_reader: std.Io.File.MultiReader = undefined; - defer multi_reader.deinit(); - multi_reader.init( - allocator, - io, - multi_reader_buffer.toStreams(), - &.{ state.child_process.stdout.?, state.child_process.stderr.? }, - ); - - const stdout = multi_reader.reader(0); - const stderr = multi_reader.reader(1); - - var diagnostic_tags: std.array_hash_map.Auto(DiagnosticsCollection.Tag, void) = .empty; - defer diagnostic_tags.deinit(allocator); - - defer { - for (diagnostic_tags.keys()) |tag| collection.clearErrorBundle(tag); - collection.publishDiagnostics() catch { - // cancellation should be fine since we are returning anyway - }; - } - - var did_print_status = false; - outer: while (true) { - while (stdout.bufferedLen() < @sizeOf(ServerToClient.Header)) { - multi_reader.fill(64, .none) catch |err| switch (err) { - error.Canceled, error.EndOfStream => break :outer, - else => break :outer log.err("failed to receive message from zig build-on-save runner: {}", .{err}), - }; - } - const header = stdout.takeStruct(ServerToClient.Header, .little) catch unreachable; - while (stdout.bufferedLen() < header.bytes_len) { - multi_reader.fill(64, .none) catch |err| switch (err) { - error.Canceled => break :outer, - else => break :outer log.err("failed to receive message from zig build-on-save runner: {}", .{err}), - }; - } - multi_reader.checkAnyError() catch |err| switch (err) { - error.Canceled => break :outer, - else => break :outer log.err("failed to receive message from zig build-on-save runner: {}", .{err}), - }; - const body = stdout.take(header.bytes_len) catch unreachable; - - if (!did_print_status) { - log.info("Build-On-Save is running for '{s}'", .{workspace_path}); - did_print_status = true; - } - - switch (header.tag) { - .watch_error_bundle => { - handleWatchErrorBundle( - allocator, - body, - collection, - workspace_path, - &diagnostic_tags, - ) catch |err| switch (err) { - error.Canceled => break :outer, - else => |e| log.err("failed to handle error bundle message from zig build-on-save runner: {}", .{e}), - }; - }, - else => |tag| { - log.warn("received unexpected message from zig build-on-save runner: {}", .{tag}); - break :outer; - }, - } - } - - const old_cancel_protect = io.swapCancelProtection(.blocked); - defer _ = io.swapCancelProtection(old_cancel_protect); - - state.mutex.lockUncancelable(io); - defer state.mutex.unlock(io); - - state.child_process.stdin.?.close(io); - state.child_process.stdin = null; - - const term = state.child_process.wait(io) catch |err| { - log.warn("Failed to await zig build-on-save runner: {}", .{err}); - return; - }; - - const stderr_msg_prefix = if (stderr.bufferedLen() > 0) " and stderr:\n" else ""; - - switch (term) { - .exited => |code| if (code != 0) log.warn("zig build-on-save runner exited with with code: {d}{s}{s}", .{ code, stderr_msg_prefix, stderr.buffered() }), - .signal => |sig| std.log.err("zig build-on-save runner failed with signal {t}{s}{s}", .{ sig, stderr_msg_prefix, stderr.buffered() }), - .stopped => |sig| std.log.err("zig build-on-save runner stopped with signal {d}{s}{s}", .{ sig, stderr_msg_prefix, stderr.buffered() }), - .unknown => |code| std.log.err("zig build-on-save runner failed for unknown reason with code {d}{s}{s}", .{ code, stderr_msg_prefix, stderr.buffered() }), - } - } - - fn handleWatchErrorBundle( - allocator: std.mem.Allocator, - body: []u8, - collection: *DiagnosticsCollection, - workspace_path: []const u8, - diagnostic_tags: *std.array_hash_map.Auto(DiagnosticsCollection.Tag, void), - ) (error{ OutOfMemory, InvalidMessage } || std.Io.File.Writer.Error)!void { - var reader: std.Io.Reader = .fixed(body); - - const header = reader.takeStruct(ServerToClient.ErrorBundle, .little) catch return error.InvalidMessage; - - const extra = reader.readSliceEndianAlloc(allocator, u32, header.extra_len, .little) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.EndOfStream => return error.InvalidMessage, - error.ReadFailed => unreachable, - }; - defer allocator.free(extra); - - const string_bytes = reader.readAlloc(allocator, header.string_bytes_len) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.EndOfStream => return error.InvalidMessage, - error.ReadFailed => unreachable, - }; - defer allocator.free(string_bytes); - - if (reader.bufferedLen() != 0) return error.InvalidMessage; // ensure that we read the entire body - - const error_bundle: std.zig.ErrorBundle = .{ .string_bytes = string_bytes, .extra = extra }; - - var hasher: std.hash.Wyhash = .init(0); - hasher.update(workspace_path); - std.hash.autoHash(&hasher, header.step_id); - - const diagnostic_tag: DiagnosticsCollection.Tag = @enumFromInt(@as(u32, @truncate(hasher.final()))); - - try diagnostic_tags.put(allocator, diagnostic_tag, {}); - - try collection.pushErrorBundle(diagnostic_tag, header.cycle, workspace_path, error_bundle); - try collection.publishDiagnostics(); - } -}; diff --git a/src/tools/config.json b/src/tools/config.json index 3bfd193df6..e0febf3b06 100644 --- a/src/tools/config.json +++ b/src/tools/config.json @@ -131,12 +131,6 @@ "type": "?[]const u8", "default": null }, - { - "name": "build_runner_path", - "description": "Specify a custom build runner to resolve build system information.", - "type": "?[]const u8", - "default": null - }, { "name": "global_cache_path", "description": "Path to a directory that will be used as zig's cache. Will default to `${KnownFolders.Cache}/zls`.", diff --git a/src/zls.zig b/src/zls.zig index 485a1e7145..657f6e655e 100644 --- a/src/zls.zig +++ b/src/zls.zig @@ -36,7 +36,4 @@ pub const signature_help = @import("features/signature_help.zig"); comptime { const std = @import("std"); std.testing.refAllDecls(@This()); - - std.testing.refAllDecls(@import("build_runner/check.zig")); - std.testing.refAllDecls(@import("build_runner/shared.zig")); } diff --git a/tests/add_build_runner_cases.zig b/tests/add_build_runner_cases.zig deleted file mode 100644 index 7e19e92308..0000000000 --- a/tests/add_build_runner_cases.zig +++ /dev/null @@ -1,78 +0,0 @@ -//! This file is imported by `../build.zig` to add build runner tests to the build system. -//! See the `./build_runner_cases` subdirectory. - -const std = @import("std"); - -pub fn addCases( - b: *std.Build, - test_step: *std.Build.Step, - test_filters: []const []const u8, -) void { - const cases_path = "tests/build_runner_cases"; - - const global_cache: std.Build.LazyPath = .{ .relative = .{ .base = .global_cache } }; - - const check_exe = b.addExecutable(.{ - .name = "build_runner_check", - .root_module = b.createModule(.{ - .root_source_file = b.path("tests/build_runner_check.zig"), - .target = b.graph.host, - .imports = &.{ - .{ .name = "zls", .module = b.modules.get("zls").? }, - }, - }), - }); - - b.dependOnDirectory(b.path(cases_path)); - - var dir = b.root.openDir(b.graph.io, cases_path, .{ .iterate = true }) catch |err| - std.debug.panic("failed to open '{f}': {}", .{ b.path(cases_path), err }); - defer dir.close(b.graph.io); - - var it = dir.iterate(); - - while (true) { - const entry = it.next(b.graph.io) catch |err| - std.debug.panic("failed to walk directory '{f}': {}", .{ b.path(cases_path), err }) orelse break; - - if (entry.kind != .file) continue; - if (!std.mem.eql(u8, std.Io.Dir.path.extension(entry.name), ".zig")) continue; - - for (test_filters) |test_filter| { - if (std.mem.find(u8, entry.name, test_filter) != null) break; - } else if (test_filters.len > 0) continue; - - const build_file = b.path(cases_path).path(b, entry.name); - const expected_build_config_json = b.path(cases_path).path(b, std.Io.Dir.path.stem(entry.name)); - - const build_cmd = std.Build.Step.Run.create(b, b.fmt("run build runner ({s})", .{entry.name})); - build_cmd.addFileArg(.zig_exe); - build_cmd.addArg("build"); - build_cmd.addArg("--build-file"); - build_cmd.addFileArg(build_file); - build_cmd.addArg("--build-runner"); - build_cmd.addFileArg(b.path("src/build_runner/build_runner.zig")); - build_cmd.addArg("--cache-dir"); - build_cmd.addDirectoryArg(.cache_root); - build_cmd.addArg("--global-cache-dir"); - build_cmd.addDirectoryArg(global_cache); - build_cmd.addArg("--zig-lib-dir"); - build_cmd.addDirectoryArg(.zig_lib); - - build_cmd.addFileInput(b.path("src/build_runner/shared.zig")); - - const actual_build_config_json = build_cmd.captureStdOut(.{}); - - const run_diff = b.addRunArtifact(check_exe); - run_diff.setName(b.fmt("run {s} ({s})", .{ check_exe.name, entry.name })); - run_diff.setCwd(b.path(cases_path)); - run_diff.addFileArg(expected_build_config_json); - run_diff.addFileArg(actual_build_config_json); - run_diff.addArg("--cache-dir"); - run_diff.addDirectoryArg(.cache_root); - run_diff.addArg("--global-cache-dir"); - run_diff.addDirectoryArg(global_cache); - - test_step.dependOn(&run_diff.step); - } -} diff --git a/tests/analysis_check.zig b/tests/analysis_check.zig index 7ada71e535..af882f40e2 100644 --- a/tests/analysis_check.zig +++ b/tests/analysis_check.zig @@ -103,7 +103,6 @@ pub fn main(init: std.process.Init) Error!void { .environ_map = &environ_map, .zig_exe_path = zig_exe_path, .zig_lib_dir = zig_lib_dir, - .build_runner_path = null, .builtin_path = null, .global_cache_dir = null, .wasi_preopens = switch (builtin.target.os.tag) { diff --git a/tests/build_runner_cases/add_module.json b/tests/build_runner_cases/add_module.json deleted file mode 100644 index cd2bb42097..0000000000 --- a/tests/build_runner_cases/add_module.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "dependencies": {}, - "modules": { - "root.zig": { - "import_table": {} - } - }, - "compilations": [], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/add_module.zig b/tests/build_runner_cases/add_module.zig deleted file mode 100644 index 30bef6678c..0000000000 --- a/tests/build_runner_cases/add_module.zig +++ /dev/null @@ -1,7 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - _ = b.addModule("foo", .{ - .root_source_file = b.path("root.zig"), - }); -} diff --git a/tests/build_runner_cases/add_options.json b/tests/build_runner_cases/add_options.json deleted file mode 100644 index e91002f54b..0000000000 --- a/tests/build_runner_cases/add_options.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "dependencies": {}, - "modules": { - "root.zig": { - "import_table": { - "options": ".zig-local-cache/options.zig" - } - }, - ".zig-local-cache/options.zig": { - "import_table": {} - } - }, - "compilations": [], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/add_options.zig b/tests/build_runner_cases/add_options.zig deleted file mode 100644 index 10b98eb10b..0000000000 --- a/tests/build_runner_cases/add_options.zig +++ /dev/null @@ -1,11 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const module = b.addModule("root", .{ - .root_source_file = b.path("root.zig"), - }); - - const options = b.addOptions(); - options.addOption(bool, "enabled", true); - module.addOptions("options", options); -} diff --git a/tests/build_runner_cases/empty.json b/tests/build_runner_cases/empty.json deleted file mode 100644 index 8faf4b9590..0000000000 --- a/tests/build_runner_cases/empty.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "dependencies": {}, - "modules": {}, - "compilations": [], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/empty.zig b/tests/build_runner_cases/empty.zig deleted file mode 100644 index 5b3ba7fda8..0000000000 --- a/tests/build_runner_cases/empty.zig +++ /dev/null @@ -1,5 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - _ = b; -} diff --git a/tests/build_runner_cases/module_root_source_file_collision.json b/tests/build_runner_cases/module_root_source_file_collision.json deleted file mode 100644 index 529d905338..0000000000 --- a/tests/build_runner_cases/module_root_source_file_collision.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "dependencies": {}, - "modules": { - "main.zig": { - "import_table": { - "collision": "first.zig", - "first": "first.zig", - "second": "second.zig" - } - }, - "first.zig": { - "import_table": {} - }, - "second.zig": { - "import_table": {} - }, - "third.zig": { - "import_table": {} - } - }, - "compilations": [ - { - "root_module": "main.zig" - } - ], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/module_root_source_file_collision.zig b/tests/build_runner_cases/module_root_source_file_collision.zig deleted file mode 100644 index cb5b4d5b27..0000000000 --- a/tests/build_runner_cases/module_root_source_file_collision.zig +++ /dev/null @@ -1,37 +0,0 @@ -//! There are three different module with `main.zig` as their root source file -//! and every one of them defines an import with the name `collision`. - -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const first = b.createModule(.{ .root_source_file = b.path("first.zig") }); - const second = b.createModule(.{ .root_source_file = b.path("second.zig") }); - const third = b.createModule(.{ .root_source_file = b.path("third.zig") }); - - const exe = b.addExecutable(.{ - .name = "exe", - .root_module = b.createModule(.{ - .root_source_file = b.path("main.zig"), - .target = b.graph.host, - .imports = &.{ - .{ .name = "collision", .module = third }, - }, - }), - }); - b.installArtifact(exe); - - _ = b.addModule("foo", .{ - .root_source_file = b.path("main.zig"), - .imports = &.{ - .{ .name = "collision", .module = first }, - .{ .name = "first", .module = first }, - }, - }); - _ = b.addModule("bar", .{ - .root_source_file = b.path("main.zig"), - .imports = &.{ - .{ .name = "collision", .module = second }, - .{ .name = "second", .module = second }, - }, - }); -} diff --git a/tests/build_runner_cases/module_self_import.json b/tests/build_runner_cases/module_self_import.json deleted file mode 100644 index 08ca05c395..0000000000 --- a/tests/build_runner_cases/module_self_import.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "dependencies": {}, - "modules": { - "root.zig": { - "import_table": { - "bar": "root.zig" - } - } - }, - "compilations": [], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/module_self_import.zig b/tests/build_runner_cases/module_self_import.zig deleted file mode 100644 index dc29a955f7..0000000000 --- a/tests/build_runner_cases/module_self_import.zig +++ /dev/null @@ -1,9 +0,0 @@ -const std = @import("std"); - -// https://github.com/zigtools/zls/issues/2117 -pub fn build(b: *std.Build) void { - const module = b.addModule("foo", .{ - .root_source_file = b.path("root.zig"), - }); - module.addImport("bar", module); -} diff --git a/tests/build_runner_cases/multiple_module_import_names.json b/tests/build_runner_cases/multiple_module_import_names.json deleted file mode 100644 index 7b3be8ba7c..0000000000 --- a/tests/build_runner_cases/multiple_module_import_names.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "dependencies": {}, - "modules": { - "foo.zig": { - "import_table": { - "bar_in_foo": "bar.zig" - } - }, - "bar.zig": { - "import_table": { - "foo_in_bar": "foo.zig" - } - }, - "main.zig": { - "import_table": { - "foo_in_main": "foo.zig", - "bar_in_main": "bar.zig" - } - } - }, - "compilations": [], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/multiple_module_import_names.zig b/tests/build_runner_cases/multiple_module_import_names.zig deleted file mode 100644 index 352813d9b5..0000000000 --- a/tests/build_runner_cases/multiple_module_import_names.zig +++ /dev/null @@ -1,18 +0,0 @@ -const std = @import("std"); - -// https://github.com/zigtools/zls/issues/2118 -pub fn build(b: *std.Build) void { - const foo = b.addModule("foo", .{ .root_source_file = b.path("foo.zig") }); - const bar = b.addModule("bar", .{ .root_source_file = b.path("bar.zig") }); - - foo.addImport("bar_in_foo", bar); - bar.addImport("foo_in_bar", foo); - - _ = b.addModule("main", .{ - .root_source_file = b.path("main.zig"), - .imports = &.{ - .{ .name = "foo_in_main", .module = foo }, - .{ .name = "bar_in_main", .module = bar }, - }, - }); -} diff --git a/tests/build_runner_cases/no_root_source_file.json b/tests/build_runner_cases/no_root_source_file.json deleted file mode 100644 index ea65f5e235..0000000000 --- a/tests/build_runner_cases/no_root_source_file.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "dependencies": {}, - "modules": { - "baz.zig": { - "import_table": {} - }, - "bar.zig": { - "import_table": { - "with-root-source-file": "baz.zig" - } - } - }, - "compilations": [], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/no_root_source_file.zig b/tests/build_runner_cases/no_root_source_file.zig deleted file mode 100644 index 6c2628d170..0000000000 --- a/tests/build_runner_cases/no_root_source_file.zig +++ /dev/null @@ -1,19 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const with_root_source_file = b.createModule(.{ .root_source_file = b.path("baz.zig") }); - const without_root_source_file = b.createModule(.{}); - _ = b.addModule("foo", .{ - .imports = &.{ - .{ .name = "with-root-source-file", .module = with_root_source_file }, - .{ .name = "without-root-source-file", .module = without_root_source_file }, - }, - }); - _ = b.addModule("bar", .{ - .root_source_file = b.path("bar.zig"), - .imports = &.{ - .{ .name = "with-root-source-file", .module = with_root_source_file }, - .{ .name = "without-root-source-file", .module = without_root_source_file }, - }, - }); -} diff --git a/tests/build_runner_cases/public_module_with_generated_file.json b/tests/build_runner_cases/public_module_with_generated_file.json deleted file mode 100644 index d89b8f79d6..0000000000 --- a/tests/build_runner_cases/public_module_with_generated_file.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "dependencies": {}, - "modules": { - ".zig-local-cache/generated.zig": { - "import_table": {} - } - }, - "compilations": [], - "top_level_steps": [ - "install", - "uninstall" - ], - "available_options": {} -} \ No newline at end of file diff --git a/tests/build_runner_cases/public_module_with_generated_file.zig b/tests/build_runner_cases/public_module_with_generated_file.zig deleted file mode 100644 index e03c2f002e..0000000000 --- a/tests/build_runner_cases/public_module_with_generated_file.zig +++ /dev/null @@ -1,10 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const write_files = b.addWriteFiles(); - const generated = write_files.add("generated.zig", ""); - - _ = b.addModule("root", .{ - .root_source_file = generated, - }); -} diff --git a/tests/build_runner_check.zig b/tests/build_runner_check.zig deleted file mode 100644 index 3ea9458646..0000000000 --- a/tests/build_runner_check.zig +++ /dev/null @@ -1,113 +0,0 @@ -//! This file implements a standalone executable that is used by -//! `add_build_runner_cases.zig` to run build runner tests. -//! See the `./build_runner_cases` subdirectory. - -const std = @import("std"); -const zls = @import("zls"); - -pub fn main(init: std.process.Init) !u8 { - const io = init.io; - const gpa = init.gpa; - const arena_allocator = init.arena.allocator(); - const args = try init.minimal.args.toSlice(arena_allocator); - - if (args.len != 7) @panic("invalid arguments"); - - const cwd = try std.process.currentPathAlloc(io, gpa); - defer gpa.free(cwd); - - const expected = std.Io.Dir.cwd().readFileAlloc(io, args[1], gpa, .limited(16 * 1024 * 1024)) catch |err| - std.debug.panic("could no open/read file '{s}': {}", .{ args[1], err }); - defer gpa.free(expected); - - const actual_unsanitized = std.Io.Dir.cwd().readFileAlloc(io, args[2], gpa, .limited(16 * 1024 * 1024)) catch |err| - std.debug.panic("could no open/read file '{s}': {}", .{ args[2], err }); - defer gpa.free(actual_unsanitized); - - std.debug.assert(std.mem.eql(u8, args[3], "--cache-dir")); - const local_cache_dir = try std.Io.Dir.path.resolve(gpa, &.{ cwd, args[4] }); - defer gpa.free(local_cache_dir); - - std.debug.assert(std.mem.eql(u8, args[5], "--global-cache-dir")); - const global_cache_dir = try std.Io.Dir.path.resolve(gpa, &.{ cwd, args[6] }); - defer gpa.free(global_cache_dir); - - const actual_sanitized = sanitized: { - const parsed = try std.json.parseFromSlice(zls.DocumentStore.BuildConfig, gpa, actual_unsanitized, .{}); - defer parsed.deinit(); - - var new: zls.DocumentStore.BuildConfig = parsed.value; - const arena = parsed.arena.allocator(); - - for (new.dependencies.map.keys()) |*str| str.* = try sanitizePath(arena, str.*, cwd, local_cache_dir, global_cache_dir); - try new.dependencies.map.reIndex(arena); - - for (new.modules.map.keys()) |*str| str.* = try sanitizePath(arena, str.*, cwd, local_cache_dir, global_cache_dir); - try new.modules.map.reIndex(arena); - - for (new.modules.map.values()) |*mod| { - for (mod.import_table.map.values()) |*str| str.* = try sanitizePath(arena, str.*, cwd, local_cache_dir, global_cache_dir); - try mod.import_table.map.reIndex(arena); - } - - for (new.compilations) |*compile| { - @as(*[]const u8, @constCast(&compile.root_module)).* = try sanitizePath(arena, compile.root_module, cwd, local_cache_dir, global_cache_dir); - } - - break :sanitized try std.json.Stringify.valueAlloc( - gpa, - new, - .{ .whitespace = .indent_2 }, - ); - }; - defer gpa.free(actual_sanitized); - - if (std.mem.eql(u8, expected, actual_sanitized)) return 0; - - zls.testing.renderLineDiff(gpa, expected, actual_sanitized); - - return 1; -} - -fn stripBasePath(base_dir: []const u8, path: []const u8) ?[]const u8 { - if (!std.mem.startsWith(u8, path, base_dir)) return null; - if (!std.mem.startsWith(u8, path[base_dir.len..], std.Io.Dir.path.sep_str)) return null; - return path[base_dir.len + std.Io.Dir.path.sep_str.len ..]; -} - -fn sanitizePath( - arena: std.mem.Allocator, - path: []const u8, - cwd: []const u8, - local_cache_dir: []const u8, - global_cache_dir: []const u8, -) error{OutOfMemory}![]const u8 { - const new = try arena.dupe(u8, new: { - if (stripBasePath(cwd, path)) |foo| { - break :new foo; - } - if (stripBasePath(local_cache_dir, path)) |to| { - var it = std.Io.Dir.path.componentIterator(to); - _ = it.next().?; // skip cache namespace (o, c, h, z, tmp, ...) - std.debug.assert(it.next().?.name.len == std.Build.Cache.hex_digest_len); - break :new try std.fmt.allocPrint(arena, ".zig-local-cache/{s}", .{to[it.end_index + 1 ..]}); - } - if (stripBasePath(global_cache_dir, path)) |to| { - var it = std.Io.Dir.path.componentIterator(to); - _ = it.next().?; // skip cache namespace (o, c, h, z, tmp, ...) - std.debug.assert(it.next().?.name.len == std.Build.Cache.hex_digest_len); - break :new try std.fmt.allocPrint(arena, ".zig-global-cache/{s}", .{to[it.end_index + 1 ..]}); - } - std.debug.assert(!std.Io.Dir.path.isAbsolute(path)); // got an absolute path that is not in cwd or any cache dir - break :new path; - }); - - // Convert windows style '\\' path separators to posix style '/'. - if (std.Io.Dir.path.sep == '\\') { - for (new) |*c| { - if (c.* == std.Io.Dir.path.sep) c.* = '/'; - } - } - - return new; -} From 93430bc33d0baeb9b97b5506977894c3d77c586b Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 3 Sep 2026 22:40:48 +0200 Subject: [PATCH 09/14] workaround ziglang/zig#35766 --- build.zig | 5 +++-- tests/context.zig | 10 ++++++---- tests/lifecycle.zig | 5 +++-- tests/lsp_features/completion.zig | 6 ++++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/build.zig b/build.zig index 9caeee023e..07c75fb379 100644 --- a/build.zig +++ b/build.zig @@ -66,8 +66,9 @@ pub fn build(b: *Build) !void { test_options.step.name = "ZLS test options"; test_options.addOptionPath("zig_exe_path", .zig_exe); - test_options.addOptionPath("zig_lib_path", .zig_lib); - test_options.addOptionPath("global_cache_path", std.Build.LazyPath.cache_root.join(b.allocator, "zls") catch @panic("OOM")); + // https://codeberg.org/ziglang/zig/issues/35766 + // test_options.addOptionPath("zig_lib_path", .zig_lib); + // test_options.addOptionPath("global_cache_path", std.Build.LazyPath.cache_root.join(b.allocator, "zls") catch @panic("OOM")); break :blk test_options.createModule(); }; diff --git a/tests/context.zig b/tests/context.zig index b3db24a05f..2f9419037c 100644 --- a/tests/context.zig +++ b/tests/context.zig @@ -35,8 +35,9 @@ pub const Context = struct { const cwd = try std.process.currentPathAlloc(io, allocator); defer allocator.free(cwd); config.zig_exe_path = try std.Io.Dir.path.resolve(allocator, &.{ cwd, test_options.zig_exe_path }); - config.zig_lib_path = try std.Io.Dir.path.resolve(allocator, &.{ cwd, test_options.zig_lib_path }); - config.global_cache_path = try std.Io.Dir.path.resolve(allocator, &.{ cwd, test_options.global_cache_path }); + // https://codeberg.org/ziglang/zig/issues/35766 + // config.zig_lib_path = try std.Io.Dir.path.resolve(allocator, &.{ cwd, test_options.zig_lib_path }); + // config.global_cache_path = try std.Io.Dir.path.resolve(allocator, &.{ cwd, test_options.global_cache_path }); } const environ_map = try cached_config_arena.allocator().create(std.process.Environ.Map); @@ -58,8 +59,9 @@ pub const Context = struct { std.debug.assert(server.config_manager.zig_lib_dir != null); std.debug.assert(server.document_store.config.zig_lib_dir != null); - std.debug.assert(server.config_manager.global_cache_dir != null); - std.debug.assert(server.document_store.config.global_cache_dir != null); + // https://codeberg.org/ziglang/zig/issues/35766 + // std.debug.assert(server.config_manager.global_cache_dir != null); + // std.debug.assert(server.document_store.config.global_cache_dir != null); var context: Context = .{ .server = server, diff --git a/tests/lifecycle.zig b/tests/lifecycle.zig index e89c8ed8f3..4dc52cf155 100644 --- a/tests/lifecycle.zig +++ b/tests/lifecycle.zig @@ -21,8 +21,9 @@ test "LSP lifecycle" { try config_manager.setConfiguration(.frontend, &.{ .zig_exe_path = try std.Io.Dir.path.resolve(arena, &.{ cwd, test_options.zig_exe_path }), - .zig_lib_path = try std.Io.Dir.path.resolve(arena, &.{ cwd, test_options.zig_lib_path }), - .global_cache_path = try std.Io.Dir.path.resolve(arena, &.{ cwd, test_options.global_cache_path }), + // https://codeberg.org/ziglang/zig/issues/35766 + // .zig_lib_path = try std.Io.Dir.path.resolve(arena, &.{ cwd, test_options.zig_lib_path }), + // .global_cache_path = try std.Io.Dir.path.resolve(arena, &.{ cwd, test_options.global_cache_path }), }); } diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 71175dcffd..a0440acc45 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -3685,6 +3685,9 @@ test "empty doc comment" { test "filesystem" { if (@import("builtin").target.cpu.arch.isWasm()) return error.SkipZigTest; + // https://codeberg.org/ziglang/zig/issues/35766 + if (true) return error.SkipZigTest; + try testCompletion( \\const foo = @import(""); , &.{ @@ -3702,6 +3705,9 @@ test "filesystem" { test "filesystem string literal ends with non ASCII symbol" { if (@import("builtin").target.cpu.arch.isWasm()) return error.SkipZigTest; + // https://codeberg.org/ziglang/zig/issues/35766 + if (true) return error.SkipZigTest; + try testCompletion( \\const foo = @import(" 🠁 , &.{ From 0e2d9a0e67bde2a551633b6f42b6a57de3a28e5b Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 8 Aug 2026 00:24:31 +0200 Subject: [PATCH 10/14] replace `@enumFromInt` and `@intFromEnum` with `@fromBackingInt` and `@backingInt` --- src/DiagnosticsCollection.zig | 4 +- src/DocumentScope.zig | 48 +++++----- src/DocumentStore.zig | 2 +- src/Server.zig | 24 ++--- src/TrigramStore.zig | 20 ++-- src/analyser/InternPool.zig | 124 ++++++++++++------------- src/analyser/string_pool.zig | 18 ++-- src/analysis.zig | 22 ++--- src/ast.zig | 40 ++++---- src/features/code_actions.zig | 10 +- src/features/completions.zig | 2 +- src/features/diagnostics.zig | 2 +- src/features/folding_range.zig | 2 +- src/features/semantic_tokens.zig | 2 +- src/features/workspace_symbols.zig | 6 +- src/main.zig | 2 +- src/testing.zig | 6 +- tests/analysis/switch.zig | 2 +- tests/lsp_features/semantic_tokens.zig | 2 +- 19 files changed, 169 insertions(+), 169 deletions(-) diff --git a/src/DiagnosticsCollection.zig b/src/DiagnosticsCollection.zig index 5e89cebbb0..fa47bdec4b 100644 --- a/src/DiagnosticsCollection.zig +++ b/src/DiagnosticsCollection.zig @@ -551,8 +551,8 @@ test DiagnosticsCollection { } { - try collection.pushErrorBundle(@enumFromInt(16), 4, null, eb2); - try collection.pushErrorBundle(@enumFromInt(17), 4, null, eb3); + try collection.pushErrorBundle(@fromBackingInt(16), 4, null, eb2); + try collection.pushErrorBundle(@fromBackingInt(17), 4, null, eb3); var diagnostics: std.ArrayList(lsp.types.Diagnostic) = .empty; try collection.collectLspDiagnosticsForDocument(uri, .@"utf-8", arena, &diagnostics); diff --git a/src/DocumentScope.zig b/src/DocumentScope.zig index a6ca091e9e..bb131e2605 100644 --- a/src/DocumentScope.zig +++ b/src/DocumentScope.zig @@ -126,7 +126,7 @@ pub const Declaration = union(enum) { pub fn getVarDeclNode(self: AssignDestructure, tree: *const Ast) Ast.Node.Index { const extra_index = tree.nodeData(self.node).extra_and_node[0]; - return @enumFromInt(tree.extra_data[@intFromEnum(extra_index) + 1 ..][self.index]); + return @fromBackingInt(tree.extra_data[@backingInt(extra_index) + 1 ..][self.index]); } pub fn getFullVarDecl(self: AssignDestructure, tree: *const Ast) Ast.full.VarDecl { @@ -151,7 +151,7 @@ pub const Declaration = union(enum) { _, pub fn toOptional(index: Index) OptionalIndex { - return @enumFromInt(@intFromEnum(index)); + return @fromBackingInt(@backingInt(index)); } }; @@ -161,7 +161,7 @@ pub const Declaration = union(enum) { pub fn unwrap(index: OptionalIndex) ?Index { if (index == .none) return null; - return @enumFromInt(@intFromEnum(index)); + return @fromBackingInt(@backingInt(index)); } }; @@ -291,7 +291,7 @@ pub const Scope = struct { _, pub fn toOptional(index: Index) OptionalIndex { - return @enumFromInt(@intFromEnum(index)); + return @fromBackingInt(@backingInt(index)); } }; @@ -302,7 +302,7 @@ pub const Scope = struct { pub fn unwrap(index: OptionalIndex) ?Index { if (index == .none) return null; - return @enumFromInt(@intFromEnum(index)); + return @fromBackingInt(@backingInt(index)); } }; }; @@ -357,10 +357,10 @@ const ScopeContext = struct { if (gop.found_existing) return; try doc_scope.declarations.append(allocator, declaration); - const declaration_index: Declaration.Index = @enumFromInt(doc_scope.declarations.len - 1); + const declaration_index: Declaration.Index = @fromBackingInt(@intCast(doc_scope.declarations.len - 1)); - const data = &doc_scope.scopes.items(.data)[@intFromEnum(pushed.scope)]; - const child_declarations = &doc_scope.scopes.items(.child_declarations)[@intFromEnum(pushed.scope)]; + const data = &doc_scope.scopes.items(.data)[@backingInt(pushed.scope)]; + const child_declarations = &doc_scope.scopes.items(.child_declarations)[@backingInt(pushed.scope)]; if (!data.is_child_decls_small) { try context.child_declarations_scratch.append(allocator, declaration_index); @@ -386,7 +386,7 @@ const ScopeContext = struct { const allocator = context.allocator; const slice = context.doc_scope.scopes.slice(); - const data = slice.items(.data)[@intFromEnum(pushed.scope)]; + const data = slice.items(.data)[@backingInt(pushed.scope)]; if (!data.is_child_decls_small) { const declaration_start = context.doc_scope.extra.items.len; @@ -394,7 +394,7 @@ const ScopeContext = struct { const declaration_end = context.doc_scope.extra.items.len; context.child_declarations_scratch.items.len = pushed.declarations_start; - slice.items(.child_declarations)[@intFromEnum(pushed.scope)] = .{ + slice.items(.child_declarations)[@backingInt(pushed.scope)] = .{ .other = .{ .start = @intCast(declaration_start), .end = @intCast(declaration_end), @@ -408,7 +408,7 @@ const ScopeContext = struct { const scope_end = context.doc_scope.extra.items.len; context.child_scopes_scratch.items.len = pushed.scopes_start; - slice.items(.child_scopes)[@intFromEnum(pushed.scope)] = .{ + slice.items(.child_scopes)[@backingInt(pushed.scope)] = .{ .other = .{ .start = @intCast(scope_start), .end = @intCast(scope_end), @@ -440,7 +440,7 @@ const ScopeContext = struct { .small = @splat(.none), }, }); - const new_scope_index: Scope.Index = @enumFromInt(context.doc_scope.scopes.len - 1); + const new_scope_index: Scope.Index = @fromBackingInt(@intCast(context.doc_scope.scopes.len - 1)); if (context.current_scope.unwrap()) |parent_scope| { try context.pushChildScope(parent_scope, new_scope_index); } @@ -462,8 +462,8 @@ const ScopeContext = struct { const doc_scope = context.doc_scope; const allocator = context.allocator; - const data = &doc_scope.scopes.items(.data)[@intFromEnum(scope_index)]; - const child_scopes = &doc_scope.scopes.items(.child_scopes)[@intFromEnum(scope_index)]; + const data = &doc_scope.scopes.items(.data)[@backingInt(scope_index)]; + const child_scopes = &doc_scope.scopes.items(.child_scopes)[@backingInt(scope_index)]; if (!data.is_child_scopes_small) { try context.child_scopes_scratch.append(allocator, child_scope_index); @@ -1245,14 +1245,14 @@ pub fn getScopeTag( doc_scope: DocumentScope, scope: Scope.Index, ) Scope.Tag { - return doc_scope.scopes.items(.data)[@intFromEnum(scope)].tag; + return doc_scope.scopes.items(.data)[@backingInt(scope)].tag; } pub fn getScopeParent( doc_scope: DocumentScope, scope: Scope.Index, ) Scope.OptionalIndex { - return doc_scope.scopes.items(.parent_scope)[@intFromEnum(scope)]; + return doc_scope.scopes.items(.parent_scope)[@backingInt(scope)]; } pub fn getScopeAstNode( @@ -1261,7 +1261,7 @@ pub fn getScopeAstNode( ) ?Ast.Node.Index { const slice = doc_scope.scopes.slice(); - const data = slice.items(.data)[@intFromEnum(scope)]; + const data = slice.items(.data)[@backingInt(scope)]; return switch (data.tag) { .container, .function, .block => data.data.ast_node, @@ -1274,7 +1274,7 @@ pub fn getScopeDeclaration( lookup: DeclarationLookup, ) Declaration.OptionalIndex { return if (doc_scope.declaration_lookup_map.getIndex(lookup)) |idx| - @enumFromInt(idx) + @fromBackingInt(@intCast(idx)) else .none; } @@ -1285,8 +1285,8 @@ pub fn getScopeDeclarationsConst( ) []const Declaration.Index { const slice = doc_scope.scopes.slice(); - if (slice.items(.data)[@intFromEnum(scope)].is_child_decls_small) { - const small = &slice.items(.child_declarations)[@intFromEnum(scope)].small; + if (slice.items(.data)[@backingInt(scope)].is_child_decls_small) { + const small = &slice.items(.child_declarations)[@backingInt(scope)].small; for (0..Scope.ChildDeclarations.small_size) |idx| { if (small[idx] == .none) { @@ -1296,7 +1296,7 @@ pub fn getScopeDeclarationsConst( return @ptrCast(small[0..Scope.ChildDeclarations.small_size]); } else { - const other = slice.items(.child_declarations)[@intFromEnum(scope)].other; + const other = slice.items(.child_declarations)[@backingInt(scope)].other; return @ptrCast(doc_scope.extra.items[other.start..other.end]); } } @@ -1307,8 +1307,8 @@ pub fn getScopeChildScopesConst( ) []const Scope.Index { const slice = doc_scope.scopes.slice(); - if (slice.items(.data)[@intFromEnum(scope)].is_child_scopes_small) { - const small = &slice.items(.child_scopes)[@intFromEnum(scope)].small; + if (slice.items(.data)[@backingInt(scope)].is_child_scopes_small) { + const small = &slice.items(.child_scopes)[@backingInt(scope)].small; for (0..Scope.ChildScopes.small_size) |idx| { if (small[idx] == .none) { @@ -1318,7 +1318,7 @@ pub fn getScopeChildScopesConst( return @ptrCast(small[0..Scope.ChildScopes.small_size]); } else { - const other = slice.items(.child_scopes)[@intFromEnum(scope)].other; + const other = slice.items(.child_scopes)[@backingInt(scope)].other; return @ptrCast(doc_scope.extra.items[other.start..other.end]); } } diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 3ae09102c8..94247da787 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -480,7 +480,7 @@ pub const Handle = struct { const node_tags = tree.nodes.items(.tag); for (node_tags, 0..) |tag, i| { - const node: Ast.Node.Index = @enumFromInt(i); + const node: Ast.Node.Index = @fromBackingInt(@intCast(i)); switch (tag) { .builtin_call, diff --git a/src/Server.zig b/src/Server.zig index b96966e24d..f03d2b3149 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -1888,19 +1888,19 @@ fn processMessageReportError(server: *Server, arena_state: std.heap.ArenaAllocat switch (message) { .request => |request| { const json_message = server.sendToClientResponseError(request.id, .{ - .code = @enumFromInt(switch (err) { + .code = @fromBackingInt(switch (err) { error.Canceled => unreachable, // checked above - error.OutOfMemory => @intFromEnum(types.ErrorCodes.InternalError), - error.ParseError => @intFromEnum(types.ErrorCodes.ParseError), - error.InvalidRequest => @intFromEnum(types.ErrorCodes.InvalidRequest), - error.MethodNotFound => @intFromEnum(types.ErrorCodes.MethodNotFound), - error.InvalidParams => @intFromEnum(types.ErrorCodes.InvalidParams), - error.InternalError => @intFromEnum(types.ErrorCodes.InternalError), - error.ServerNotInitialized => @intFromEnum(types.ErrorCodes.ServerNotInitialized), - error.RequestFailed => @intFromEnum(types.LSPErrorCodes.RequestFailed), - error.ServerCancelled => @intFromEnum(types.LSPErrorCodes.ServerCancelled), - error.ContentModified => @intFromEnum(types.LSPErrorCodes.ContentModified), - error.RequestCancelled => @intFromEnum(types.LSPErrorCodes.RequestCancelled), + error.OutOfMemory => @backingInt(types.ErrorCodes.InternalError), + error.ParseError => @backingInt(types.ErrorCodes.ParseError), + error.InvalidRequest => @backingInt(types.ErrorCodes.InvalidRequest), + error.MethodNotFound => @backingInt(types.ErrorCodes.MethodNotFound), + error.InvalidParams => @backingInt(types.ErrorCodes.InvalidParams), + error.InternalError => @backingInt(types.ErrorCodes.InternalError), + error.ServerNotInitialized => @backingInt(types.ErrorCodes.ServerNotInitialized), + error.RequestFailed => @backingInt(types.LSPErrorCodes.RequestFailed), + error.ServerCancelled => @backingInt(types.LSPErrorCodes.ServerCancelled), + error.ContentModified => @backingInt(types.LSPErrorCodes.ContentModified), + error.RequestCancelled => @backingInt(types.LSPErrorCodes.RequestCancelled), }), .message = @errorName(err), }) catch |send_err| switch (send_err) { diff --git a/src/TrigramStore.zig b/src/TrigramStore.zig index 57a230014b..9865863eee 100644 --- a/src/TrigramStore.zig +++ b/src/TrigramStore.zig @@ -293,7 +293,7 @@ fn appendOneTrigram( allocator: std.mem.Allocator, trigram: Trigram, ) error{OutOfMemory}!void { - const declaration_index: Declaration.Index = @enumFromInt(store.declarations.len); + const declaration_index: Declaration.Index = @fromBackingInt(@intCast(store.declarations.len)); const gop = try store.trigram_to_declarations.getOrPutValue(allocator, trigram, .empty); @@ -455,7 +455,7 @@ fn mergeIntersection( out_idx += 1; a_idx += 1; b_idx += 1; - } else if (@intFromEnum(a_val) < @intFromEnum(b_val)) { + } else if (@backingInt(a_val) < @backingInt(b_val)) { a_idx += 1; } else { b_idx += 1; @@ -484,7 +484,7 @@ const CuckooFilter = struct { pub fn oddHash(fingerprint: Fingerprint) u32 { assert(fingerprint != .none); - return precomputed_odd_hashes[@intFromEnum(fingerprint)]; + return precomputed_odd_hashes[@backingInt(fingerprint)]; } }; @@ -493,10 +493,10 @@ const CuckooFilter = struct { _, pub fn alternate(index: BucketIndex, fingerprint: Fingerprint, len: u32) BucketIndex { - assert(@intFromEnum(index) < len); + assert(@backingInt(index) < len); assert(fingerprint != .none); - const signed_index: i64 = @intFromEnum(index); + const signed_index: i64 = @backingInt(index); const odd_hash: i64 = fingerprint.oddHash(); const unbounded = switch (parity(signed_index)) { @@ -507,7 +507,7 @@ const CuckooFilter = struct { assert(parity(signed_index) != parity(bounded)); - return @enumFromInt(bounded); + return @fromBackingInt(bounded); } }; @@ -523,10 +523,10 @@ const CuckooFilter = struct { index_1: u32, } = @bitCast(std.hash.Murmur2_64.hash(&trigram)); - const index_1: BucketIndex = @enumFromInt(split.index_1 % len); + const index_1: BucketIndex = @fromBackingInt(split.index_1 % len); const fingerprint: Fingerprint = if (split.fingerprint == .none) - @enumFromInt(1) + @fromBackingInt(1) else split.fingerprint; @@ -582,7 +582,7 @@ const CuckooFilter = struct { } fn bucketAt(filter: CuckooFilter, index: BucketIndex) *Bucket { - return &filter.buckets[@intFromEnum(index)]; + return &filter.buckets[@backingInt(index)]; } fn appendToBucket(filter: CuckooFilter, index: BucketIndex, fingerprint: Fingerprint) bool { @@ -639,7 +639,7 @@ const CuckooFilter = struct { } fn parity(integer: anytype) enum(u1) { even, odd } { - return @enumFromInt(integer & 1); + return @fromBackingInt(@intCast(integer & 1)); } }; diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 44950930bb..ce8bbd5ab3 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -664,7 +664,7 @@ pub const Index = enum(u32) { assert(index < slice.len); ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return @enumFromInt(ip.extra.items[slice.start + index]); + return @fromBackingInt(ip.extra.items[slice.start + index]); } pub fn dupe(slice: Slice, allocator: Allocator, ip: *InternPool) error{OutOfMemory}![]Index { @@ -703,9 +703,9 @@ pub const Index = enum(u32) { // comptime { // const Zir = @import("../stage2/Zir.zig"); -// assert(@intFromEnum(Zir.Inst.Ref.generic_poison_type) == @intFromEnum(Index.generic_poison_type)); -// assert(@intFromEnum(Zir.Inst.Ref.undef) == @intFromEnum(Index.undefined_value)); -// assert(@intFromEnum(Zir.Inst.Ref.one_usize) == @intFromEnum(Index.one_usize)); +// assert(@backingInt(Zir.Inst.Ref.generic_poison_type) == @backingInt(Index.generic_poison_type)); +// assert(@backingInt(Zir.Inst.Ref.undef) == @backingInt(Index.undefined_value)); +// assert(@backingInt(Zir.Inst.Ref.one_usize) == @backingInt(Index.one_usize)); // } pub const StringSlice = struct { @@ -722,7 +722,7 @@ pub const StringSlice = struct { assert(index < slice.len); ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return @enumFromInt(ip.extra.items[slice.start + index]); + return @fromBackingInt(ip.extra.items[slice.start + index]); } pub fn dupe(slice: StringSlice, allocator: Allocator, ip: *InternPool) error{OutOfMemory}![]String { @@ -961,7 +961,7 @@ pub const Decl = struct { _, pub fn toOptional(i: Decl.Index) OptionalIndex { - return @enumFromInt(@intFromEnum(i)); + return @fromBackingInt(@backingInt(i)); } }; @@ -975,7 +975,7 @@ pub const Decl = struct { pub fn unwrap(oi: OptionalIndex) ?Decl.Index { if (oi == .none) return null; - return @enumFromInt(@intFromEnum(oi)); + return @fromBackingInt(@backingInt(oi)); } }; }; @@ -1152,7 +1152,7 @@ pub fn init(io: std.Io, gpa: Allocator) Allocator.Error!InternPool { } for (items, 0..) |item, i| { - assert(@intFromEnum(item.index) == i); + assert(@backingInt(item.index) == i); assert(item.index == ip.get(item.key) catch unreachable); } @@ -1195,27 +1195,27 @@ pub fn indexToKey(ip: *InternPool, index: Index) Key { fn indexToKeyNoLock(ip: *const InternPool, index: Index) Key { assert(index != .none); - const item = ip.items.get(@intFromEnum(index)); + const item = ip.items.get(@backingInt(index)); const data = item.data; return switch (item.tag) { - .simple_type => .{ .simple_type = @enumFromInt(data) }, - .simple_value => .{ .simple_value = @enumFromInt(data) }, + .simple_type => .{ .simple_type = @fromBackingInt(data) }, + .simple_value => .{ .simple_value = @fromBackingInt(data) }, .type_int_signed => .{ .int_type = .{ .signedness = .signed, .bits = @intCast(data) } }, .type_int_unsigned => .{ .int_type = .{ .signedness = .unsigned, .bits = @intCast(data) } }, .type_pointer => .{ .pointer_type = ip.extraData(Key.Pointer, data) }, .type_array => .{ .array_type = ip.extraData(Key.Array, data) }, - .type_optional => .{ .optional_type = .{ .payload_type = @enumFromInt(data) } }, - .type_anyframe => .{ .anyframe_type = .{ .child = @enumFromInt(data) } }, + .type_optional => .{ .optional_type = .{ .payload_type = @fromBackingInt(data) } }, + .type_anyframe => .{ .anyframe_type = .{ .child = @fromBackingInt(data) } }, .type_error_union => .{ .error_union_type = ip.extraData(Key.ErrorUnion, data) }, .type_error_set => .{ .error_set_type = ip.extraData(Key.ErrorSet, data) }, .type_function => .{ .function_type = ip.extraData(Key.Function, data) }, .type_tuple => .{ .tuple_type = ip.extraData(Key.Tuple, data) }, .type_vector => .{ .vector_type = ip.extraData(Key.Vector, data) }, - .type_struct => .{ .struct_type = @enumFromInt(data) }, - .type_enum => .{ .enum_type = @enumFromInt(data) }, - .type_union => .{ .union_type = @enumFromInt(data) }, + .type_struct => .{ .struct_type = @fromBackingInt(data) }, + .type_enum => .{ .enum_type = @fromBackingInt(data) }, + .type_union => .{ .union_type = @fromBackingInt(data) }, .int_u64 => .{ .int_u64_value = ip.extraData(Key.U64Value, data) }, .int_i64 => .{ .int_i64_value = ip.extraData(Key.I64Value, data) }, @@ -1245,9 +1245,9 @@ fn indexToKeyNoLock(ip: *const InternPool, index: Index) Key { .aggregate_value => .{ .aggregate = ip.extraData(Key.Aggregate, data) }, .union_value => .{ .union_value = ip.extraData(Key.UnionValue, data) }, .error_value => .{ .error_value = ip.extraData(Key.ErrorValue, data) }, - .null_value => .{ .null_value = .{ .ty = @enumFromInt(data) } }, - .undefined_value => .{ .undefined_value = .{ .ty = @enumFromInt(data) } }, - .unknown_value => .{ .unknown_value = .{ .ty = @enumFromInt(data) } }, + .null_value => .{ .null_value = .{ .ty = @fromBackingInt(data) } }, + .undefined_value => .{ .undefined_value = .{ .ty = @fromBackingInt(data) } }, + .unknown_value => .{ .unknown_value = .{ .ty = @fromBackingInt(data) } }, }; } @@ -1262,23 +1262,23 @@ pub fn get(ip: *InternPool, key: Key) Allocator.Error!Index { defer ip.lock.unlockShared(ip.io); const index = ip.map.getIndexAdapted(key, adapter) orelse break :not_found; - return @enumFromInt(index); + return @fromBackingInt(@intCast(index)); } ip.lock.lockUncancelable(ip.io); defer ip.lock.unlock(ip.io); const gop = try ip.map.getOrPutAdapted(ip.gpa, key, adapter); - if (gop.found_existing) return @enumFromInt(gop.index); + if (gop.found_existing) return @fromBackingInt(@intCast(gop.index)); const item: Item = switch (key) { .simple_type => |simple| .{ .tag = .simple_type, - .data = @intFromEnum(simple), + .data = @backingInt(simple), }, .simple_value => |simple| .{ .tag = .simple_value, - .data = @intFromEnum(simple), + .data = @backingInt(simple), }, .int_type => |int_ty| switch (int_ty.signedness) { .signed => .{ .tag = .type_int_signed, .data = int_ty.bits }, @@ -1294,11 +1294,11 @@ pub fn get(ip: *InternPool, key: Key) Allocator.Error!Index { }, .struct_type => |struct_index| .{ .tag = .type_struct, - .data = @intFromEnum(struct_index), + .data = @backingInt(struct_index), }, .optional_type => |optional_ty| .{ .tag = .type_optional, - .data = @intFromEnum(optional_ty.payload_type), + .data = @backingInt(optional_ty.payload_type), }, .error_union_type => |error_union_ty| .{ .tag = .type_error_union, @@ -1310,7 +1310,7 @@ pub fn get(ip: *InternPool, key: Key) Allocator.Error!Index { }, .enum_type => |enum_index| .{ .tag = .type_enum, - .data = @intFromEnum(enum_index), + .data = @backingInt(enum_index), }, .function_type => |function_ty| .{ .tag = .type_function, @@ -1318,7 +1318,7 @@ pub fn get(ip: *InternPool, key: Key) Allocator.Error!Index { }, .union_type => |union_index| .{ .tag = .type_union, - .data = @intFromEnum(union_index), + .data = @backingInt(union_index), }, .tuple_type => |tuple_ty| .{ .tag = .type_tuple, @@ -1330,7 +1330,7 @@ pub fn get(ip: *InternPool, key: Key) Allocator.Error!Index { }, .anyframe_type => |anyframe_ty| .{ .tag = .type_anyframe, - .data = @intFromEnum(anyframe_ty.child), + .data = @backingInt(anyframe_ty.child), }, .int_u64_value => |int_val| .{ @@ -1398,23 +1398,23 @@ pub fn get(ip: *InternPool, key: Key) Allocator.Error!Index { }, .null_value => |null_val| .{ .tag = .null_value, - .data = @intFromEnum(null_val.ty), + .data = @backingInt(null_val.ty), }, .undefined_value => |undefined_val| .{ .tag = .undefined_value, - .data = @intFromEnum(undefined_val.ty), + .data = @backingInt(undefined_val.ty), }, .unknown_value => |unknown_val| blk: { assert(unknown_val.ty != .type_type); // use .unknown_type instead break :blk .{ .tag = .unknown_value, - .data = @intFromEnum(unknown_val.ty), + .data = @backingInt(unknown_val.ty), }; }, }; try ip.items.append(ip.gpa, item); - return @enumFromInt(ip.items.len - 1); + return @fromBackingInt(@intCast(ip.items.len - 1)); } pub fn contains(ip: *InternPool, key: Key) ?Index { @@ -1425,7 +1425,7 @@ pub fn contains(ip: *InternPool, key: Key) ?Index { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); const index = ip.map.getIndexAdapted(key, adapter) orelse return null; - return @enumFromInt(index); + return @fromBackingInt(@intCast(index)); } pub fn getIndexSlice(ip: *InternPool, data: []const Index) error{OutOfMemory}!Index.Slice { @@ -1473,67 +1473,67 @@ fn getLimbSlice(ip: *InternPool, data: []const std.math.big.Limb) error{OutOfMem pub fn getDecl(ip: *InternPool, index: InternPool.Decl.Index) *const InternPool.Decl { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.decls.at(@intFromEnum(index)); + return ip.decls.at(@backingInt(index)); } pub fn getDeclMut(ip: *InternPool, index: InternPool.Decl.Index) *InternPool.Decl { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.decls.at(@intFromEnum(index)); + return ip.decls.at(@backingInt(index)); } pub fn getStruct(ip: *InternPool, index: Struct.Index) *const Struct { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.structs.at(@intFromEnum(index)); + return ip.structs.at(@backingInt(index)); } pub fn getStructMut(ip: *InternPool, index: Struct.Index) *Struct { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.structs.at(@intFromEnum(index)); + return ip.structs.at(@backingInt(index)); } pub fn getEnum(ip: *InternPool, index: Enum.Index) *const Enum { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.enums.at(@intFromEnum(index)); + return ip.enums.at(@backingInt(index)); } pub fn getEnumMut(ip: *InternPool, index: Enum.Index) *Enum { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.enums.at(@intFromEnum(index)); + return ip.enums.at(@backingInt(index)); } pub fn getUnion(ip: *InternPool, index: Union.Index) *const Union { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.unions.at(@intFromEnum(index)); + return ip.unions.at(@backingInt(index)); } pub fn getUnionMut(ip: *InternPool, index: Union.Index) *Union { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.unions.at(@intFromEnum(index)); + return ip.unions.at(@backingInt(index)); } pub fn createDecl(ip: *InternPool, decl: Decl) Allocator.Error!Decl.Index { ip.lock.lockUncancelable(ip.io); defer ip.lock.unlock(ip.io); try ip.decls.append(ip.gpa, decl); - return @enumFromInt(ip.decls.count() - 1); + return @fromBackingInt(@intCast(ip.decls.count() - 1)); } pub fn createStruct(ip: *InternPool, struct_info: Struct) Allocator.Error!Struct.Index { ip.lock.lockUncancelable(ip.io); defer ip.lock.unlock(ip.io); try ip.structs.append(ip.gpa, struct_info); - return @enumFromInt(ip.structs.count() - 1); + return @fromBackingInt(@intCast(ip.structs.count() - 1)); } pub fn createEnum(ip: *InternPool, enum_info: Enum) Allocator.Error!Enum.Index { ip.lock.lockUncancelable(ip.io); defer ip.lock.unlock(ip.io); try ip.enums.append(ip.gpa, enum_info); - return @enumFromInt(ip.enums.count() - 1); + return @fromBackingInt(@intCast(ip.enums.count() - 1)); } pub fn createUnion(ip: *InternPool, union_info: Union) Allocator.Error!Union.Index { ip.lock.lockUncancelable(ip.io); defer ip.lock.unlock(ip.io); try ip.unions.append(ip.gpa, union_info); - return @enumFromInt(ip.unions.count() - 1); + return @fromBackingInt(@intCast(ip.unions.count() - 1)); } fn addExtra(ip: *InternPool, comptime T: type, extra: T) Allocator.Error!u32 { @@ -1555,7 +1555,7 @@ fn addExtra(ip: *InternPool, comptime T: type, extra: T) Allocator.Error!u32 { StringPool.String, StringPool.OptionalString, std.lang.Type.Pointer.Size, - => ip.extra.appendAssumeCapacity(@intFromEnum(item)), + => ip.extra.appendAssumeCapacity(@backingInt(item)), u32, i32, @@ -1595,7 +1595,7 @@ fn extraData(ip: *const InternPool, comptime T: type, index: u32) T { std.lang.Type.Pointer.Size, // std.lang.AddressSpace, // std.lang.CallingConvention, - => @enumFromInt(item), + => @fromBackingInt(item), u32, i32, @@ -1633,7 +1633,7 @@ const KeyAdapter = struct { pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { _ = b_void; - return a.eqlNoLock(ctx.ip.indexToKeyNoLock(@enumFromInt(b_map_index)), ctx.ip); + return a.eqlNoLock(ctx.ip.indexToKeyNoLock(@fromBackingInt(@intCast(b_map_index))), ctx.ip); } pub fn hash(ctx: @This(), a: Key) u32 { @@ -2962,8 +2962,8 @@ fn panicOrElse(comptime T: type, message: []const u8, value: T) T { pub fn zigTypeTag(ip: *InternPool, index: Index) ?std.lang.TypeId { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return switch (ip.items.items(.tag)[@intFromEnum(index)]) { - .simple_type => switch (@as(SimpleType, @enumFromInt(ip.items.items(.data)[@intFromEnum(index)]))) { + return switch (ip.items.items(.tag)[@backingInt(index)]) { + .simple_type => switch (@as(SimpleType, @fromBackingInt(ip.items.items(.data)[@backingInt(index)]))) { .f16, .f32, .f64, @@ -3055,9 +3055,9 @@ pub fn zigTypeTag(ip: *InternPool, index: Index) ?std.lang.TypeId { pub fn typeOf(ip: *InternPool, index: Index) Index { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - const data = ip.items.items(.data)[@intFromEnum(index)]; - return switch (ip.items.items(.tag)[@intFromEnum(index)]) { - .simple_value => switch (@as(SimpleValue, @enumFromInt(data))) { + const data = ip.items.items(.data)[@backingInt(index)]; + return switch (ip.items.items(.tag)[@backingInt(index)]) { + .simple_value => switch (@as(SimpleValue, @fromBackingInt(data))) { .undefined_value => .undefined_type, .void_value => .void_type, .unreachable_value => .noreturn_type, @@ -3101,20 +3101,20 @@ pub fn typeOf(ip: *InternPool, index: Index) Index { .aggregate_value, .union_value, .error_value, - => @enumFromInt(ip.extra.items[ip.items.items(.data)[@intFromEnum(index)]]), + => @fromBackingInt(ip.extra.items[ip.items.items(.data)[@backingInt(index)]]), // the type is the `data` field .null_value, .undefined_value, .unknown_value, - => @enumFromInt(ip.items.items(.data)[@intFromEnum(index)]), + => @fromBackingInt(ip.items.items(.data)[@backingInt(index)]), }; } pub fn isType(ip: *InternPool, ty: Index) bool { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return switch (ip.items.items(.tag)[@intFromEnum(ty)]) { + return switch (ip.items.items(.tag)[@backingInt(ty)]) { .simple_type, .type_int_signed, .type_int_unsigned, @@ -3151,7 +3151,7 @@ pub fn isType(ip: *InternPool, ty: Index) bool { .null_value, .undefined_value, => false, - .unknown_value => .unknown_type == @as(Index, @enumFromInt(ip.items.items(.data)[@intFromEnum(ty)])), + .unknown_value => .unknown_type == @as(Index, @fromBackingInt(ip.items.items(.data)[@backingInt(ty)])), }; } @@ -3161,7 +3161,7 @@ pub fn isUndefined(ip: *InternPool, index: Index) bool { else => { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.items.items(.tag)[@intFromEnum(index)] == .undefined_value; + return ip.items.items(.tag)[@backingInt(index)] == .undefined_value; }, } } @@ -3172,7 +3172,7 @@ pub fn isUnknown(ip: *InternPool, index: Index) bool { else => { ip.lock.lockSharedUncancelable(ip.io); defer ip.lock.unlockShared(ip.io); - return ip.items.items(.tag)[@intFromEnum(index)] == .unknown_value; + return ip.items.items(.tag)[@backingInt(index)] == .unknown_value; }, } } @@ -4895,10 +4895,10 @@ test "test thread safety of InternPool" { // insert float_32_value from 0 to count + random work for (0..count) |i| { _ = intern_pool.get(.{ .float_32_value = @floatFromInt(i) }) catch @panic("OOM"); - _ = intern_pool.indexToKey(@enumFromInt(i)); + _ = intern_pool.indexToKey(@fromBackingInt(@intCast(i))); } for (0..count) |i| { - _ = intern_pool.indexToKey(@enumFromInt(i)); + _ = intern_pool.indexToKey(@fromBackingInt(@intCast(i))); } } }; @@ -4923,7 +4923,7 @@ test "test thread safety of InternPool" { // test that every Index stores a unique float_32_value for (0..size) |i| { - const index: Index = @enumFromInt(index_start + i); + const index: Index = @fromBackingInt(@intCast(index_start + i)); const key = ip.indexToKey(index); const value: usize = @intFromFloat(key.float_32_value); try std.testing.expect(value < size); diff --git a/src/analyser/string_pool.zig b/src/analyser/string_pool.zig index 1ba65449d9..0b4984ed96 100644 --- a/src/analyser/string_pool.zig +++ b/src/analyser/string_pool.zig @@ -34,7 +34,7 @@ pub fn StringPool(comptime config: Config) type { _, pub fn toOptional(self: String) OptionalString { - return @enumFromInt(@intFromEnum(self)); + return @fromBackingInt(@backingInt(self)); } pub fn fmt(self: String, io: std.Io, pool: *Pool) std.fmt.Alt(FormatContext, print) { @@ -48,7 +48,7 @@ pub fn StringPool(comptime config: Config) type { pub fn unwrap(self: OptionalString) ?String { if (self == .none) return null; - return @enumFromInt(@intFromEnum(self)); + return @fromBackingInt(@backingInt(self)); } }; @@ -69,7 +69,7 @@ pub fn StringPool(comptime config: Config) type { }; const index = pool.map.getKeyAdapted(str, adapter) orelse return null; - return @enumFromInt(index); + return @fromBackingInt(index); } /// Asserts that `str` contains no null bytes. @@ -94,7 +94,7 @@ pub fn StringPool(comptime config: Config) type { pool.bytes.ensureUnusedCapacity(allocator, str.len + 1) catch { // If allocation fails, try to do the lookup anyway. const index = pool.map.getKeyAdapted(str, adapter) orelse return error.OutOfMemory; - return @enumFromInt(index); + return @fromBackingInt(index); }; const gop = try pool.map.getOrPutContextAdapted( @@ -109,7 +109,7 @@ pub fn StringPool(comptime config: Config) type { pool.bytes.appendAssumeCapacity(0); gop.key_ptr.* = start_index; } - return @enumFromInt(gop.key_ptr.*); + return @fromBackingInt(gop.key_ptr.*); } /// Caller owns the memory. @@ -117,7 +117,7 @@ pub fn StringPool(comptime config: Config) type { pool.mutex.lockUncancelable(io); defer pool.mutex.unlock(io); const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); - const start = @intFromEnum(index); + const start = @backingInt(index); return try allocator.dupe(u8, std.mem.sliceTo(string_bytes + start, 0)); } @@ -126,7 +126,7 @@ pub fn StringPool(comptime config: Config) type { pool.mutex.lockUncancelable(io); defer pool.mutex.unlock(io); const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); - const start = @intFromEnum(index); + const start = @backingInt(index); return try allocator.dupeSentinel(u8, std.mem.sliceTo(string_bytes + start, 0), 0); } @@ -161,9 +161,9 @@ pub fn StringPool(comptime config: Config) type { /// returns the underlying slice from an interned string /// equal strings are guaranteed to share the same storage pub fn stringToSliceUnsafe(pool: *Pool, index: String) [:0]const u8 { - assert(@intFromEnum(index) < pool.bytes.items.len); + assert(@backingInt(index) < pool.bytes.items.len); const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); - const start = @intFromEnum(index); + const start = @backingInt(index); return std.mem.sliceTo(string_bytes + start, 0); } diff --git a/src/analysis.zig b/src/analysis.zig index 7b526719d7..273b6521d2 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -2853,7 +2853,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error var big_int: std.math.big.int.Managed = try .init(analyser.gpa); defer big_int.deinit(); const prefix_length: usize = if (base != .decimal) 2 else 0; - big_int.setString(@intFromEnum(base), bytes[prefix_length..]) catch |err| switch (err) { + big_int.setString(@backingInt(base), bytes[prefix_length..]) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => break :blk null, }; @@ -3396,7 +3396,7 @@ pub const Type = struct { } pub fn hashWithHasher(data: Data, hasher: anytype) void { - hasher.update(&.{@intFromEnum(data)}); + hasher.update(&.{@backingInt(data)}); switch (data) { .pointer => |info| { std.hash.autoHash(hasher, info.size); @@ -3462,7 +3462,7 @@ pub const Type = struct { } pub fn eql(a: Data, b: Data) bool { - if (@intFromEnum(a) != @intFromEnum(b)) return false; + if (@backingInt(a) != @backingInt(b)) return false; switch (a) { .pointer => |a_type| { @@ -6054,7 +6054,7 @@ pub fn collectDeclarationsOfContainer( const scope_decls = document_scope.getScopeDeclarationsConst(scope); for (scope_decls) |decl_index| { - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); const decl_with_handle: DeclWithHandle = .{ .decl = decl, .handle = handle, .container_type = container_type }; if (handle != original_handle and !decl_with_handle.isPublic()) continue; @@ -6121,7 +6121,7 @@ pub fn collectAllSymbolsAtSourceIndex( while (scope_iterator.next().unwrap()) |scope_index| { const scope_decls = document_scope.getScopeDeclarationsConst(scope_index); for (scope_decls) |decl_index| { - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); if (decl == .ast_node and handle.tree.nodeTag(decl.ast_node).isContainerField()) continue; if (decl == .label) continue; try decl_collection.append(analyser.arena, .{ .decl = decl, .handle = handle }); @@ -6145,7 +6145,7 @@ pub const EnclosingScopeIterator = struct { source_index: usize, fn compare(ctx: @This(), scope_index: Scope.Index) std.math.Order { - const child_scope = ctx.scope_locs[@intFromEnum(scope_index)]; + const child_scope = ctx.scope_locs[@backingInt(scope_index)]; if (ctx.source_index < child_scope.start) return .lt; if (child_scope.end < ctx.source_index) return .gt; return .eq; @@ -6176,7 +6176,7 @@ pub fn iterateLabels(handle: *DocumentStore.Handle, source_index: usize, comptim var scope_iterator = iterateEnclosingScopes(document_scope, source_index); while (scope_iterator.next().unwrap()) |scope_index| { for (document_scope.getScopeDeclarationsConst(scope_index)) |decl_index| { - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); if (decl != .label) continue; try callback(context, .{ .decl = decl, .handle = handle }); } @@ -6272,7 +6272,7 @@ pub fn lookupLabel( .name = symbol, .kind = .label, }).unwrap() orelse continue; - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); std.debug.assert(decl == .label); @@ -6297,7 +6297,7 @@ pub fn lookupSymbolGlobal( .name = symbol, .kind = .field, }).unwrap()) |decl_index| { - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); std.debug.assert(decl == .ast_node); var field = tree.fullContainerField(decl.ast_node).?; @@ -6314,7 +6314,7 @@ pub fn lookupSymbolGlobal( .name = symbol, .kind = .other, }).unwrap()) |decl_index| { - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); return .{ .decl = decl, .handle = handle }; } @@ -6342,7 +6342,7 @@ pub fn lookupSymbolContainer( .name = symbol, .kind = kind, }).unwrap()) |decl_index| { - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); return .{ .decl = decl, .handle = handle, .container_type = container_type }; } diff --git a/src/ast.zig b/src/ast.zig index e2a2ba2011..76a41738be 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -344,7 +344,7 @@ pub fn whileFull(tree: *const Ast, node: Node.Index) full.While { } pub fn forSimple(tree: *const Ast, node: Node.Index) full.For { - const data = &tree.nodes.items(.data)[@intFromEnum(node)].node_and_node; + const data = &tree.nodes.items(.data)[@backingInt(node)].node_and_node; return fullForComponents(tree, .{ .for_token = tree.nodeMainToken(node), .inputs = (&data[0])[0..1], @@ -356,8 +356,8 @@ pub fn forSimple(tree: *const Ast, node: Node.Index) full.For { pub fn forFull(tree: *const Ast, node: Node.Index) full.For { const extra_index, const extra = tree.nodeData(node).@"for"; const inputs = tree.extraDataSliceWithLen(extra_index, extra.inputs, Node.Index); - const then_expr: Node.Index = @enumFromInt(tree.extra_data[@intFromEnum(extra_index) + extra.inputs]); - const else_expr: Node.OptionalIndex = if (extra.has_else) @enumFromInt(tree.extra_data[@intFromEnum(extra_index) + extra.inputs + 1]) else .none; + const then_expr: Node.Index = @fromBackingInt(tree.extra_data[@backingInt(extra_index) + extra.inputs]); + const else_expr: Node.OptionalIndex = if (extra.has_else) @fromBackingInt(tree.extra_data[@backingInt(extra_index) + extra.inputs + 1]) else .none; return fullForComponents(tree, .{ .for_token = tree.nodeMainToken(node), .inputs = inputs, @@ -415,13 +415,13 @@ fn findMatchingRBrace(tree: *const Ast, start: Ast.TokenIndex) ?Ast.TokenIndex { pub fn isKeyword(tag: std.zig.Token.Tag) bool { const tags = std.zig.Token.keywords.values(); - var first_keyword: std.meta.Tag(std.zig.Token.Tag) = @intFromEnum(tags[0]); - var last_keyword: std.meta.Tag(std.zig.Token.Tag) = @intFromEnum(tags[tags.len - 1]); + var first_keyword: std.meta.Tag(std.zig.Token.Tag) = @backingInt(tags[0]); + var last_keyword: std.meta.Tag(std.zig.Token.Tag) = @backingInt(tags[tags.len - 1]); for (std.zig.Token.keywords.values()) |token_tag| { - first_keyword = @min(@intFromEnum(token_tag), first_keyword); - last_keyword = @max(@intFromEnum(token_tag), last_keyword); + first_keyword = @min(@backingInt(token_tag), first_keyword); + last_keyword = @max(@backingInt(token_tag), last_keyword); } - return first_keyword <= @intFromEnum(tag) and @intFromEnum(tag) <= last_keyword; + return first_keyword <= @backingInt(tag) and @backingInt(tag) <= last_keyword; } test isKeyword { @@ -643,8 +643,8 @@ pub fn lastToken(tree: *const Ast, node: Node.Index) Ast.TokenIndex { }, .@"for" => { const extra_index, const extra = tree.nodeData(n).@"for"; - const index = @intFromEnum(extra_index) + extra.inputs + @intFromBool(extra.has_else); - n = @enumFromInt(tree.extra_data[index]); + const index = @backingInt(extra_index) + extra.inputs + @intFromBool(extra.has_else); + n = @fromBackingInt(tree.extra_data[index]); }, .@"asm" => { _, const extra_index = tree.nodeData(n).node_and_extra; @@ -1249,9 +1249,9 @@ pub const Iterator = union(enum) { .assign_destructure => { const extra_index, const value_expr = tree.nodeData(node).extra_and_node; - const variable_count = tree.extra_data[@intFromEnum(extra_index)]; - const sub_range_start: Ast.ExtraIndex = @enumFromInt(@intFromEnum(extra_index) + 1); - const sub_range_end: Ast.ExtraIndex = @enumFromInt(@intFromEnum(sub_range_start) + variable_count); + const variable_count = tree.extra_data[@backingInt(extra_index)]; + const sub_range_start: Ast.ExtraIndex = @fromBackingInt(@backingInt(extra_index) + 1); + const sub_range_end: Ast.ExtraIndex = @fromBackingInt(@backingInt(sub_range_start) + variable_count); return .{ .sub_range = .{ .items = .{ .start = sub_range_start, .end = sub_range_end }, .suffix = .{ value_expr.toOptional(), .none }, @@ -1362,10 +1362,10 @@ pub const Iterator = union(enum) { .@"for", => { const extra_index, const extra = tree.nodeData(node).@"for"; - const then_expr: Node.Index = @enumFromInt(tree.extra_data[@intFromEnum(extra_index) + extra.inputs]); - const else_expr: Node.OptionalIndex = if (extra.has_else) @enumFromInt(tree.extra_data[@intFromEnum(extra_index) + extra.inputs + 1]) else .none; + const then_expr: Node.Index = @fromBackingInt(tree.extra_data[@backingInt(extra_index) + extra.inputs]); + const else_expr: Node.OptionalIndex = if (extra.has_else) @fromBackingInt(tree.extra_data[@backingInt(extra_index) + extra.inputs + 1]) else .none; return .{ .sub_range = .{ - .items = .{ .start = extra_index, .end = @enumFromInt(@intFromEnum(extra_index) + extra.inputs) }, + .items = .{ .start = extra_index, .end = @fromBackingInt(@backingInt(extra_index) + extra.inputs) }, .suffix = .{ then_expr.toOptional(), else_expr }, } }; }, @@ -1443,7 +1443,7 @@ pub const Iterator = union(enum) { } const items = tree.extraDataSlice(sub_range.items, Ast.Node.Index); if (items.len > 0) { - defer sub_range.items.start = @enumFromInt(@intFromEnum(sub_range.items.start) + 1); + defer sub_range.items.start = @fromBackingInt(@backingInt(sub_range.items.start) + 1); return items[0]; } const first = sub_range.suffix[0].unwrap() orelse return null; @@ -1486,8 +1486,8 @@ pub const Iterator = union(enum) { } const items = tree.extraDataSlice(asm_state.items, Ast.Node.Index); - var i: usize = 0; - defer asm_state.items.start = @enumFromInt(@intFromEnum(asm_state.items.start) + i); + var i: u32 = 0; + defer asm_state.items.start = @fromBackingInt(@backingInt(asm_state.items.start) + i); while (i < items.len) { defer i += 1; switch (tree.nodeTag(items[i])) { @@ -1647,7 +1647,7 @@ pub fn nodesOverlappingIndexIncludingParseErrors(allocator: std.mem.Allocator, t var node_locs: std.ArrayList(NodeLoc) = .empty; defer node_locs.deinit(allocator); for (0..tree.nodes.len) |i| { - const node: Ast.Node.Index = @enumFromInt(i); + const node: Ast.Node.Index = @fromBackingInt(@intCast(i)); const loc = offsets.nodeToLoc(tree, node); if (loc.start <= source_index and source_index <= loc.end) { try node_locs.append(allocator, .{ .node = node, .loc = loc }); diff --git a/src/features/code_actions.zig b/src/features/code_actions.zig index 5a6a1c4f71..50d96c77e3 100644 --- a/src/features/code_actions.zig +++ b/src/features/code_actions.zig @@ -736,7 +736,7 @@ pub const ImportDecl = struct { pub fn lessThan(context: *const Ast, lhs: ImportDecl, rhs: ImportDecl) bool { const lhs_kind = lhs.getKind(); const rhs_kind = rhs.getKind(); - if (lhs_kind != rhs_kind) return @intFromEnum(lhs_kind) < @intFromEnum(rhs_kind); + if (lhs_kind != rhs_kind) return @backingInt(lhs_kind) < @backingInt(rhs_kind); if (sort_public_decls_first) { const node_tokens = context.nodes.items(.main_token); @@ -801,9 +801,9 @@ pub const ImportDecl = struct { /// returns true if there should be an empty line between these two imports /// assumes `lessThan(void, lhs, rhs) == true` pub fn addSeperator(lhs: ImportDecl, rhs: ImportDecl) bool { - const lhs_kind = @intFromEnum(lhs.getKind()); - const rhs_kind = @intFromEnum(rhs.getKind()); - if (rhs_kind <= @intFromEnum(Kind.build_options)) return false; + const lhs_kind = @backingInt(lhs.getKind()); + const rhs_kind = @backingInt(rhs.getKind()); + if (rhs_kind <= @backingInt(Kind.build_options)) return false; return lhs_kind != rhs_kind; } @@ -903,7 +903,7 @@ pub fn getImportsDecls(builder: *Builder, allocator: std.mem.Allocator) error{Ou .kind = .other, }).unwrap() orelse continue :next_decl; - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); if (decl != .ast_node) continue :next_decl; const decl_found = decl.ast_node; diff --git a/src/features/completions.zig b/src/features/completions.zig index ef1291f090..ae15852f75 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -1490,7 +1490,7 @@ fn collectContainerFields( const scope_decls = document_scope.getScopeDeclarationsConst(scope_handle.scope); for (scope_decls) |decl_index| { - const decl = document_scope.declarations.get(@intFromEnum(decl_index)); + const decl = document_scope.declarations.get(@backingInt(decl_index)); if (decl != .ast_node) continue; const decl_handle: Analyser.DeclWithHandle = .{ .decl = decl, .handle = scope_handle.handle, .container_type = container }; const maybe_resolved_ty = type_maybe orelse try decl_handle.resolveType(builder.analyser); diff --git a/src/features/diagnostics.zig b/src/features/diagnostics.zig index 1c259010ad..ef64f6c853 100644 --- a/src/features/diagnostics.zig +++ b/src/features/diagnostics.zig @@ -160,7 +160,7 @@ fn collectWarnStyleDiagnostics( defer tracy_zone.end(); for (0..tree.nodes.len) |i| { - const node: Ast.Node.Index = @enumFromInt(i); + const node: Ast.Node.Index = @fromBackingInt(@intCast(i)); if (ast.isBuiltinCall(tree, node)) { const builtin_token = tree.nodeMainToken(node); const call_name = tree.tokenSlice(builtin_token); diff --git a/src/features/folding_range.zig b/src/features/folding_range.zig index e16681ab69..3fa8c306ef 100644 --- a/src/features/folding_range.zig +++ b/src/features/folding_range.zig @@ -227,7 +227,7 @@ pub fn generateFoldingRanges(allocator: std.mem.Allocator, tree: *const Ast, enc } for (0..tree.nodes.len) |i| { - const node: Ast.Node.Index = @enumFromInt(i); + const node: Ast.Node.Index = @fromBackingInt(@intCast(i)); switch (tree.nodeTag(node)) { .root => continue, diff --git a/src/features/semantic_tokens.zig b/src/features/semantic_tokens.zig index b371a3319f..c4dcf617e5 100644 --- a/src/features/semantic_tokens.zig +++ b/src/features/semantic_tokens.zig @@ -201,7 +201,7 @@ const Builder = struct { delta.line, delta.character, @intCast(length), - @intFromEnum(token_type), + @backingInt(token_type), @as(u16, @bitCast(token_modifiers)), }); self.previous_source_index = loc.start; diff --git a/src/features/workspace_symbols.zig b/src/features/workspace_symbols.zig index c3bed53b6a..327e7a48ce 100644 --- a/src/features/workspace_symbols.zig +++ b/src/features/workspace_symbols.zig @@ -36,7 +36,7 @@ pub fn handler(server: *Server, arena: std.mem.Allocator, request: types.workspa const SortContext = struct { names: []const std.zig.Ast.TokenIndex, fn lessThan(ctx: @This(), lhs: TrigramStore.Declaration.Index, rhs: TrigramStore.Declaration.Index) bool { - return ctx.names[@intFromEnum(lhs)] < ctx.names[@intFromEnum(rhs)]; + return ctx.names[@backingInt(lhs)] < ctx.names[@backingInt(rhs)]; } }; @@ -56,8 +56,8 @@ pub fn handler(server: *Server, arena: std.mem.Allocator, request: types.workspa try symbols.ensureUnusedCapacity(arena, declaration_buffer.items.len); for (declaration_buffer.items) |declaration| { - const name_token = names[@intFromEnum(declaration)]; - const kind = kinds[@intFromEnum(declaration)]; + const name_token = names[@backingInt(declaration)]; + const kind = kinds[@backingInt(declaration)]; const loc = offsets.tokenToLoc(&handle.tree, name_token); const name = @import("document_symbol.zig").tokenNameMaybeQuotes(&handle.tree, name_token); diff --git a/src/main.zig b/src/main.zig index 27582cb63b..67ee9cea8f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -75,7 +75,7 @@ fn logFn( }; } - if (@intFromEnum(level) > @intFromEnum(log_level)) return; + if (@backingInt(level) > @backingInt(log_level)) return; if (!log_stderr and log_file == null) return; const level_txt: []const u8 = switch (level) { diff --git a/src/testing.zig b/src/testing.zig index d5bf58677c..04f7c357e1 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -35,7 +35,7 @@ pub fn printDocumentScope(doc_scope: DocumentScope) void { if (builtin.mode != .Debug) @compileError("this function should only be used in debug mode!"); for (0..doc_scope.scopes.len) |index| { - const scope_index: DocumentScope.Scope.Index = @enumFromInt(index); + const scope_index: DocumentScope.Scope.Index = @fromBackingInt(@intCast(index)); const scope = doc_scope.scopes.get(index); if (index != 0) std.debug.print("\n\n", .{}); std.debug.print( @@ -57,8 +57,8 @@ pub fn printDocumentScope(doc_scope: DocumentScope) void { for (doc_scope.getScopeDeclarationsConst(scope_index)) |decl| { std.debug.print(" - {s:<8} {}\n", .{ - doc_scope.declaration_lookup_map.keys()[@intFromEnum(decl)].name, - doc_scope.declarations.get(@intFromEnum(decl)), + doc_scope.declaration_lookup_map.keys()[@backingInt(decl)].name, + doc_scope.declarations.get(@backingInt(decl)), }); } } diff --git a/tests/analysis/switch.zig b/tests/analysis/switch.zig index c718f2ed72..89c72fe52b 100644 --- a/tests/analysis/switch.zig +++ b/tests/analysis/switch.zig @@ -29,7 +29,7 @@ const NonExhaustiveEnum = enum(u8) { const some_u8: u8 = 'e'; const some_enum: Enum = .baz; const some_tagged_union: TaggedUnion = .{ .baz = 3.14 }; -const some_non_exhaustive_enum: NonExhaustiveEnum = @enumFromInt(42); +const some_non_exhaustive_enum: NonExhaustiveEnum = @fromBackingInt(42); const switch_u8 = switch (some_u8) { 'x' => |a| a, diff --git a/tests/lsp_features/semantic_tokens.zig b/tests/lsp_features/semantic_tokens.zig index d936440f7e..d8abdd3515 100644 --- a/tests/lsp_features/semantic_tokens.zig +++ b/tests/lsp_features/semantic_tokens.zig @@ -2135,7 +2135,7 @@ const TokenIterator = struct { const delta_line = token_data[0]; const delta_start = token_data[1]; const length = token_data[2]; - const token_type: zls.semantic_tokens.TokenType = @enumFromInt(token_data[3]); + const token_type: zls.semantic_tokens.TokenType = @fromBackingInt(token_data[3]); const token_modifiers: zls.semantic_tokens.TokenModifiers = @bitCast(@as(u16, @intCast(token_data[4]))); self.position.line += delta_line; From 1980ecb23bbe65d1bb54f0bb8ee203dc52ff9c3f Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 16 Jul 2026 07:46:12 +0200 Subject: [PATCH 11/14] do not force lld as linker when using -Duse-llvm --- build.zig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.zig b/build.zig index 07c75fb379..0ee3760562 100644 --- a/build.zig +++ b/build.zig @@ -175,7 +175,6 @@ pub fn build(b: *Build) !void { .root_module = exe_module, .max_rss = if (optimize == .debug and target_query.os_tag == .wasi) 2_600_000_000 else 2_000_000_000, .use_llvm = use_llvm, - .use_lld = use_llvm, }); } @@ -217,7 +216,6 @@ pub fn build(b: *Build) !void { .name = "zls", .root_module = exe_module, .use_llvm = use_llvm, - .use_lld = use_llvm, }); b.installArtifact(exe); } @@ -246,7 +244,6 @@ pub fn build(b: *Build) !void { }), .filters = test_filters, .use_llvm = use_llvm, - .use_lld = use_llvm, }); const src_tests = b.addTest(.{ @@ -254,7 +251,6 @@ pub fn build(b: *Build) !void { .root_module = zls_module, .filters = test_filters, .use_llvm = use_llvm, - .use_lld = use_llvm, }); blk: { // zig build test, zig build test-analysis From 23e4a748f64e77f5dd638921dc5533ce5cdb2800 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Wed, 15 Jul 2026 07:59:46 +0200 Subject: [PATCH 12/14] do not run git describe to resolve version when being a dependency --- build.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.zig b/build.zig index 0ee3760562..fa37a2977c 100644 --- a/build.zig +++ b/build.zig @@ -321,6 +321,14 @@ fn getVersion(b: *Build) std.SemanticVersion { } if (zls_version.pre == null) return zls_version; + if (!b.isRoot()) { + return .{ + .major = zls_version.major, + .minor = zls_version.minor, + .patch = zls_version.patch, + .pre = "dev", + }; + } // Ensure git version changes get picked up. git: { From bd98108fb2ca1e231af16d19dc1dee028f4e40c0 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 3 Sep 2026 23:13:44 +0200 Subject: [PATCH 13/14] CI: disable release artifact deployment --- .github/workflows/artifacts.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index af2773a082..030f738371 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -11,7 +11,9 @@ on: jobs: deploy: - if: github.repository_owner == 'zigtools' + # https://github.com/zigtools/zls/issues/3208 + if: false + # if: github.repository_owner == 'zigtools' runs-on: ubuntu-22.04 timeout-minutes: 30 steps: From 89b3bbdfde1072217f7bc0412105d9f6cd99c7fd Mon Sep 17 00:00:00 2001 From: Techatrix Date: Fri, 4 Sep 2026 16:46:01 +0200 Subject: [PATCH 14/14] add warning about missing build system integration to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9dd2c5a86f..13b83c2356 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ The complete installation guide is available on the [Zigtools website](https://z ### Build From Source +> [!WARNING] +> ZLS currently lacks critical build system integration with Zig nightly/master. See [#3208](https://github.com/zigtools/zls/issues/3208) for more information. +> It is recommended to use Zig and ZLS 0.16.0 in the meantime. + The default branch of ZLS targets [Zig master](https://ziglang.org/download/). ```bash