From f52a24a0fbcceb5053903c31633175c2c59b8869 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 12:56:28 +0300 Subject: [PATCH] refactor(sema): seal sema.zig as editor indexing only (A8.1) Remove the last compiler dependency on sema as semantic truth and stop publishing as-you-type sema diagnostics from the LSP. - core.zig: drop dead `Compilation.analyze()`, the `sema_result` field, and the sema->diagnostics merge; drop the now-orphaned sema import. The CLI pipeline (parse -> resolveImports -> generateCode) never called analyze(), so this removes only dead code. - lsp/server.zig: rename `analyzeAndPublish` -> `refreshEditorIndex` and delete its sema-diagnostic publish (and the now-unused `semaToLspDiags`). The editor index (doc.sema) is still refreshed for nav/refs/completion/ tokens. On-save/on-open diagnostics still come solely from the canonical compiler pipeline in `runProjectCheck` (unchanged). - Document sema as an editor-indexing API (doc.sema field comment). Intended behavior change: as-you-type sema diagnostics no longer publish; on-save canonical diagnostics are the sole source. CLI compile output and the 361-example suite are unchanged (361/0, zero snapshot churn). --- src/core.zig | 14 -------------- src/lsp/document.zig | 4 +++- src/lsp/server.zig | 43 +++++++++---------------------------------- 3 files changed, 12 insertions(+), 49 deletions(-) diff --git a/src/core.zig b/src/core.zig index 5c88d98..fdc9cd1 100644 --- a/src/core.zig +++ b/src/core.zig @@ -2,7 +2,6 @@ const std = @import("std"); const ast = @import("ast.zig"); const parser = @import("parser.zig"); const imports = @import("imports.zig"); -const sema = @import("sema.zig"); const errors = @import("errors.zig"); const c_import = @import("c_import.zig"); const ir = @import("ir/ir.zig"); @@ -27,7 +26,6 @@ pub const Compilation = struct { import_sources: std.StringHashMap([:0]const u8), module_scopes: std.StringHashMap(std.StringHashMap(void)), import_graph: std.StringHashMap(std.StringHashMap(void)), - sema_result: ?sema.SemaResult = null, ir_emitter: ?ir.LLVMEmitter = null, /// Lowered IR module, kept alive past `generateCode` so post-link /// callbacks can re-enter the interpreter to invoke sx functions @@ -128,18 +126,6 @@ pub const Compilation = struct { self.resolved_root = new_root; } - pub fn analyze(self: *Compilation) !void { - const root = self.resolved_root orelse self.root orelse return error.CompileError; - var analyzer = sema.Analyzer.init(self.allocator); - self.sema_result = analyzer.analyze(root) catch return error.CompileError; - // Merge sema diagnostics into our list - if (self.sema_result) |sr| { - for (sr.diagnostics) |d| { - self.diagnostics.add(d.level, d.message, d.span); - } - } - } - /// Generate code via the IR pipeline: lower AST → IR → LLVM. pub fn generateCode(self: *Compilation) !void { // Heap-allocate the IR module so its address is stable during emit diff --git a/src/lsp/document.zig b/src/lsp/document.zig index f013c12..6f5e461 100644 --- a/src/lsp/document.zig +++ b/src/lsp/document.zig @@ -23,7 +23,9 @@ pub const Document = struct { version: i64, /// AST root for this file only (not merged). root: ?*sx.ast.Node, - /// Sema results for this file (references are relative to this source). + /// Editor index for this file — symbols/references/types for navigation, + /// completion, and hover (references are relative to this source). Not a + /// diagnostic source; see `sema.zig` module doc. sema: ?sx.sema.SemaResult, /// Last successful sema (preserved across parse failures for completions). last_good_sema: ?sx.sema.SemaResult = null, diff --git a/src/lsp/server.zig b/src/lsp/server.zig index 47ee5dd..201c46e 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -158,7 +158,7 @@ pub const Server = struct { const text = jsonStr(jsonGet(td, "text") orelse return) orelse return; const version = jsonInt(jsonGet(td, "version") orelse return) orelse return; - try self.analyzeAndPublish(uri, text, version); + try self.refreshEditorIndex(uri, text, version); self.runProjectCheck(); } @@ -173,7 +173,7 @@ pub const Server = struct { const last = changes_arr[changes_arr.len - 1]; const text = jsonStr(jsonGet(last, "text") orelse return) orelse return; - try self.analyzeAndPublish(uri, text, version); + try self.refreshEditorIndex(uri, text, version); } fn handleDidClose(_: *Server, params: std.json.Value) void { @@ -1919,41 +1919,16 @@ pub const Server = struct { // ---- Core analysis pipeline ---- - fn analyzeAndPublish(self: *Server, uri: []const u8, text: []const u8, version: i64) !void { + /// Refresh the editor index for a document (symbols/references/types that + /// power navigation, completion, hover, and token classification). Publishes + /// no diagnostics — authoritative diagnostics come only from the canonical + /// compiler pipeline in `runProjectCheck`. + fn refreshEditorIndex(self: *Server, uri: []const u8, text: []const u8, version: i64) !void { const file_path = uriToFilePath(uri) orelse ""; const source = try self.allocator.dupeZ(u8, text); const doc = try self.documents.openOrUpdate(file_path, source, version); self.documents.analyzeDocument(doc) catch {}; - - // Publish diagnostics from sema - if (doc.sema) |sema| { - try self.sendDiagnostics(uri, semaToLspDiags(self.allocator, doc.source, sema.diagnostics)); - } else { - try self.sendDiagnostics(uri, &.{}); - } - } - - fn semaToLspDiags(allocator: std.mem.Allocator, source: [:0]const u8, diags: []const sx.errors.Diagnostic) []const lsp.Diagnostic { - var result = std.ArrayList(lsp.Diagnostic).empty; - for (diags) |d| { - const range = if (d.span) |span| spanToRange(source, span) else lsp.Range{ - .start = .{ .line = 0, .character = 0 }, - .end = .{ .line = 0, .character = 1 }, - }; - const severity: u32 = switch (d.level) { - .err => 1, - .warn => 2, - .note => 3, - .help => 4, - }; - result.append(allocator, .{ - .range = range, - .severity = severity, - .message = d.message, - }) catch continue; - } - return result.items; } fn sendDiagnostics(self: *Server, uri: []const u8, diagnostics: []const lsp.Diagnostic) !void { @@ -2012,8 +1987,8 @@ pub const Server = struct { } /// Drive the whole-program check from the workspace entry point and publish - /// the real compiler's diagnostics per file (runs on save; the sema layer - /// keeps live per-keystroke feedback). + /// the real compiler's diagnostics per file. Runs on open and save; this is + /// the sole source of LSP diagnostics (the editor index publishes none). fn runProjectCheck(self: *Server) void { if (self.root_path.len == 0) return; const entry_path = std.fmt.allocPrint(self.allocator, "{s}/main.sx", .{self.root_path}) catch return;