Skip to content

Commit 53ebeef

Browse files
authored
Merge branch 'main' into platform-fixes
2 parents f1a5676 + 576001c commit 53ebeef

File tree

20 files changed

+632
-40
lines changed

20 files changed

+632
-40
lines changed

.github/workflows/ci_zig.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,4 @@ jobs:
268268

269269
# Test cross-compilation with Roc's cross-compilation system (musl + glibc)
270270
roc-cross-compile:
271-
uses: ./.github/workflows/ci_cross_compile.yml
271+
uses: ./.github/workflows/ci_cross_compile.yml

build.zig

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ fn configureBackend(step: *Step.Compile, target: ResolvedTarget) void {
2020
}
2121
}
2222

23-
fn isNativeOrMusl(target: ResolvedTarget) bool {
24-
return target.query.isNativeCpu() and target.query.isNativeOs() and
23+
fn isNativeishOrMusl(target: ResolvedTarget) bool {
24+
return target.result.cpu.arch == builtin.target.cpu.arch and
25+
target.query.isNativeOs() and
2526
(target.query.isNativeAbi() or target.result.abi.isMusl());
2627
}
2728

@@ -1081,13 +1082,25 @@ pub fn build(b: *std.Build) void {
10811082
const is_windows = target.result.os.tag == .windows;
10821083

10831084
// fx platform effectful functions test - only run when not cross-compiling
1084-
if (isNativeOrMusl(target)) {
1085+
if (isNativeishOrMusl(target)) {
1086+
// Determine the appropriate target for the fx platform host library.
1087+
// On Linux, we need to use musl explicitly because the CLI's findHostLibrary
1088+
// looks for targets/x64musl/libhost.a first, and musl produces proper static binaries.
1089+
const fx_host_target, const fx_host_target_dir: ?[]const u8 = switch (target.result.os.tag) {
1090+
.linux => switch (target.result.cpu.arch) {
1091+
.x86_64 => .{ b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }), "x64musl" },
1092+
.aarch64 => .{ b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl }), "arm64musl" },
1093+
else => .{ target, null },
1094+
},
1095+
else => .{ target, null },
1096+
};
1097+
10851098
// Create fx test platform host static library
10861099
const test_platform_fx_host_lib = createTestPlatformHostLib(
10871100
b,
10881101
"test_platform_fx_host",
10891102
"test/fx/platform/host.zig",
1090-
target,
1103+
fx_host_target,
10911104
optimize,
10921105
roc_modules,
10931106
);
@@ -1098,6 +1111,14 @@ pub fn build(b: *std.Build) void {
10981111
copy_test_fx_host.addCopyFileToSource(test_platform_fx_host_lib.getEmittedBin(), b.pathJoin(&.{ "test/fx/platform", test_fx_host_filename }));
10991112
b.getInstallStep().dependOn(&copy_test_fx_host.step);
11001113

1114+
// On Linux, also copy to the target-specific directory so findHostLibrary finds it
1115+
if (fx_host_target_dir) |target_dir| {
1116+
copy_test_fx_host.addCopyFileToSource(
1117+
test_platform_fx_host_lib.getEmittedBin(),
1118+
b.pathJoin(&.{ "test/fx/platform/targets", target_dir, "libhost.a" }),
1119+
);
1120+
}
1121+
11011122
const fx_platform_test = b.addTest(.{
11021123
.name = "fx_platform_test",
11031124
.root_module = b.createModule(.{
@@ -1118,7 +1139,7 @@ pub fn build(b: *std.Build) void {
11181139
}
11191140

11201141
var build_afl = false;
1121-
if (!isNativeOrMusl(target)) {
1142+
if (!isNativeishOrMusl(target)) {
11221143
std.log.warn("Cross compilation does not support fuzzing (Only building repro executables)", .{});
11231144
} else if (is_windows) {
11241145
// Windows does not support fuzzing - only build repro executables

src/canonicalize/Can.zig

Lines changed: 122 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4894,13 +4894,128 @@ pub fn canonicalizeExpr(
48944894
.free_vars = null,
48954895
};
48964896
},
4897-
.local_dispatch => |_| {
4898-
const feature = try self.env.insertString("canonicalize local_dispatch expression");
4899-
const expr_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{ .not_implemented = .{
4900-
.feature = feature,
4901-
.region = Region.zero(),
4902-
} });
4903-
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = null };
4897+
.local_dispatch => |local_dispatch| {
4898+
// Desugar `arg1->fn(arg2, arg3)` to `fn(arg1, arg2, arg3)`
4899+
// and `arg1->fn` to `fn(arg1)`
4900+
const region = self.parse_ir.tokenizedRegionToRegion(local_dispatch.region);
4901+
const free_vars_start = self.scratch_free_vars.top();
4902+
4903+
// Canonicalize the left expression (first argument)
4904+
const can_first_arg = try self.canonicalizeExpr(local_dispatch.left) orelse return null;
4905+
4906+
// Get the right expression to determine the function and additional args
4907+
const right_expr = self.parse_ir.store.getExpr(local_dispatch.right);
4908+
4909+
switch (right_expr) {
4910+
.apply => |apply| {
4911+
// Case: `arg1->fn(arg2, arg3)` - function call with additional args
4912+
// Check if this is a tag application
4913+
const ast_fn = self.parse_ir.store.getExpr(apply.@"fn");
4914+
if (ast_fn == .tag) {
4915+
// Tag application: `arg1->Tag(arg2)` becomes `Tag(arg1, arg2)`
4916+
const tag_expr = ast_fn.tag;
4917+
const tag_name = self.parse_ir.tokens.resolveIdentifier(tag_expr.token) orelse @panic("tag token is not an ident");
4918+
4919+
// Build args: first_arg followed by apply.args
4920+
const scratch_top = self.env.store.scratchExprTop();
4921+
try self.env.store.addScratchExpr(can_first_arg.idx);
4922+
4923+
const additional_args = self.parse_ir.store.exprSlice(apply.args);
4924+
for (additional_args) |arg| {
4925+
if (try self.canonicalizeExpr(arg)) |can_arg| {
4926+
try self.env.store.addScratchExpr(can_arg.idx);
4927+
}
4928+
}
4929+
4930+
const args_span = try self.env.store.exprSpanFrom(scratch_top);
4931+
4932+
const expr_idx = try self.env.addExpr(CIR.Expr{
4933+
.e_tag = .{
4934+
.name = tag_name,
4935+
.args = args_span,
4936+
},
4937+
}, region);
4938+
4939+
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
4940+
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
4941+
}
4942+
4943+
// Normal function call
4944+
const can_fn_expr = try self.canonicalizeExpr(apply.@"fn") orelse return null;
4945+
4946+
// Build args: first_arg followed by apply.args
4947+
const scratch_top = self.env.store.scratchExprTop();
4948+
try self.env.store.addScratchExpr(can_first_arg.idx);
4949+
4950+
const additional_args = self.parse_ir.store.exprSlice(apply.args);
4951+
for (additional_args) |arg| {
4952+
if (try self.canonicalizeExpr(arg)) |can_arg| {
4953+
try self.env.store.addScratchExpr(can_arg.idx);
4954+
}
4955+
}
4956+
4957+
const args_span = try self.env.store.exprSpanFrom(scratch_top);
4958+
4959+
const expr_idx = try self.env.addExpr(CIR.Expr{
4960+
.e_call = .{
4961+
.func = can_fn_expr.idx,
4962+
.args = args_span,
4963+
.called_via = CalledVia.apply,
4964+
},
4965+
}, region);
4966+
4967+
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
4968+
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
4969+
},
4970+
.ident, .tag => {
4971+
// Case: `arg1->fn` or `arg1->Tag` - simple function/tag call with single arg
4972+
if (right_expr == .tag) {
4973+
const tag_expr = right_expr.tag;
4974+
const tag_name = self.parse_ir.tokens.resolveIdentifier(tag_expr.token) orelse @panic("tag token is not an ident");
4975+
4976+
const scratch_top = self.env.store.scratchExprTop();
4977+
try self.env.store.addScratchExpr(can_first_arg.idx);
4978+
const args_span = try self.env.store.exprSpanFrom(scratch_top);
4979+
4980+
const expr_idx = try self.env.addExpr(CIR.Expr{
4981+
.e_tag = .{
4982+
.name = tag_name,
4983+
.args = args_span,
4984+
},
4985+
}, region);
4986+
4987+
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
4988+
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
4989+
}
4990+
4991+
// It's an ident
4992+
const can_fn_expr = try self.canonicalizeExpr(local_dispatch.right) orelse return null;
4993+
4994+
const scratch_top = self.env.store.scratchExprTop();
4995+
try self.env.store.addScratchExpr(can_first_arg.idx);
4996+
const args_span = try self.env.store.exprSpanFrom(scratch_top);
4997+
4998+
const expr_idx = try self.env.addExpr(CIR.Expr{
4999+
.e_call = .{
5000+
.func = can_fn_expr.idx,
5001+
.args = args_span,
5002+
.called_via = CalledVia.apply,
5003+
},
5004+
}, region);
5005+
5006+
const free_vars_span = self.scratch_free_vars.spanFrom(free_vars_start);
5007+
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = if (free_vars_span.len > 0) free_vars_span else null };
5008+
},
5009+
else => {
5010+
// Unexpected expression type on right side of arrow
5011+
const feature = try self.env.insertString("arrow with complex expression");
5012+
const expr_idx = try self.env.pushMalformed(Expr.Idx, Diagnostic{ .not_implemented = .{
5013+
.feature = feature,
5014+
.region = region,
5015+
} });
5016+
return CanonicalizedExpr{ .idx = expr_idx, .free_vars = null };
5017+
},
5018+
}
49045019
},
49055020
.bin_op => |e| {
49065021
const region = self.parse_ir.tokenizedRegionToRegion(e.region);

src/lsp/README.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,48 @@ written in Zig as part of the Rust to Zig rewrite.
44

55
## Current state
66
The experimental LSP currently only holds the scaffolding for the incoming implementation.
7-
It doesn't implement any LSP capabilities yet except `initialized` and `exit` which allows it
8-
to be connected to an editor and verify it's actually running.
7+
It doesn't provide any features yet, but it does connect to your editor, detect file change
8+
and store the buffer in memory.
9+
The following request have been handled :
10+
- `initialize`
11+
- `shutdown`
12+
The following notifications have been handled :
13+
- `initialized`
14+
- `exit`
15+
- `didOpen` (stores the buffer into a `StringHashMap`, but doesn't do any action on it)
16+
- `didChange` (same as `didOpen`, but also supports incremental changes)
17+
918

1019
## How to implement new LSP capabilities
1120
The core functionalities of the LSP have been implemented in a way so that `transport.zig` and
1221
`protocol.zig` shouldn't have to be modified as more capabilities are added. When handling a new
1322
LSP method, like `textDocument/completion` for example, the handler should be added in the `handlers`
14-
directory and its call should be added in `server.zig` like this :
23+
directory and its call should be added either in `request` (if it expects a response) or `notification`
24+
(if it doesn't expect a response). `textDocument/completion` for example would go here :
1525
```zig
1626
const request_handlers = std.StaticStringMap(HandlerPtr).initComptime(.{
1727
.{ "initialize", &InitializeHandler.call },
1828
.{ "shutdown", &ShutdownHandler.call },
1929
.{ "textDocument/completion", &CompletionHandler.call },
2030
});
2131
```
22-
The `Server` holds the state so it will be responsible of knowing the project and how different parts
23-
interact. This is then accessible by every handler.
32+
When adding a new capability, if the server is ready to support it, you need to add the capabilities to
33+
the `capabilities.zig` file for the `initialize` response to tell the client the capabilities is available :
34+
```zig
35+
pub fn buildCapabilities() ServerCapabilities {
36+
return .{
37+
.textDocumentSync = .{
38+
.openClose = true,
39+
.change = @intFromEnum(ServerCapabilities.TextDocumentSyncKind.incremental),
40+
},
41+
};
42+
}
43+
```
44+
Here we tell the client that `textDocumentSync` is available in accordance to the LSP specifications data
45+
structure. The `Server` struct holds the state, meaning in has the knowledge of the project files, the
46+
documentation, the type inference, the syntax, etc. Every handler has access to it. These points of knowledge
47+
are ideally separated in different fields of the server. For example, the opened buffer and other desired files
48+
are stored in a `DocumentStore` which is a struct containing a `StringHashMap`, accessible through the `Server`.
2449

2550
## Starting the server
2651
Build the Roc toolchain and run:
@@ -37,7 +62,7 @@ roc experimental-lsp --debug-transport
3762

3863
Passing the `--debug-transport` flag will create a log file in your OS tmp folder (`/tmp` on Unix
3964
systems). A mirror of the raw JSON-RPC traffic will be appended to the log file. Watching the file
40-
will allow an user to see incoming and outgoing message between the server and the editor
65+
will allow a user to see incoming and outgoing message between the server and the editor
4166
```bash
4267
tail -f /tmp/roc-lsp-debug.log
4368
---

src/lsp/capabilities.zig

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const std = @import("std");
2+
3+
/// Aggregates all server capabilities supported by the Roc LSP.
4+
pub const ServerCapabilities = struct {
5+
positionEncoding: []const u8 = "utf-16",
6+
textDocumentSync: ?TextDocumentSyncOptions = null,
7+
8+
pub const TextDocumentSyncOptions = struct {
9+
openClose: bool = false,
10+
change: u32 = @intFromEnum(TextDocumentSyncKind.none),
11+
};
12+
13+
pub const TextDocumentSyncKind = enum(u32) {
14+
none = 0,
15+
full = 1,
16+
incremental = 2,
17+
};
18+
};
19+
20+
/// Returns the server capabilities currently implemented.
21+
pub fn buildCapabilities() ServerCapabilities {
22+
return .{
23+
.textDocumentSync = .{
24+
.openClose = true,
25+
.change = @intFromEnum(ServerCapabilities.TextDocumentSyncKind.incremental),
26+
},
27+
};
28+
}

0 commit comments

Comments
 (0)