This document covers the internal architecture of mx for contributors.
Source (.mod/.def)
-> TargetInfo (src/target.rs) detect/parse target triple, populate layout info
-> Lexer (src/lexer.rs) tokenize into TokenKind stream
-> Parser (src/parser.rs) recursive-descent -> AST
-> Sema (src/sema.rs) type checking, scope resolution, symbol table
-> HIR (src/hir_build/) build_module: prebuilt HirModule with structural
decls, lowered statements, procedure bodies
-> CFG (src/cfg/) build_cfg: control flow graph from HIR bodies
-> CodeGen:
C backend (src/codegen_c/) CFG blocks -> C source (goto-based)
LLVM backend (src/codegen_llvm/) CFG blocks -> LLVM IR basic blocks
-> Driver (src/driver.rs) invoke cc/clang, link
The compiler has two backends selected at compile time:
- C backend (default): emits goto-based C source with
#linedirectives for debugging. Invokes the system C compiler. - LLVM backend (
--llvm): emits LLVM IR text (.ll), compiled by clang. Produces native DWARF debug info, LLVM-native exception handling, and RTTI for TYPECASE/REF/OBJECT types.
Lexer::new(source, filename) produces a Vec<Token>. Keywords are always matched case-insensitively (the input is uppercased before keyword lookup). Identifiers preserve original case by default; with case_sensitive: false, they are uppercased.
The lexer handles feature-gate pragmas ((*$IF name*) / (*$ELSE*) / (*$END*)) at the token level -- disabled blocks are skipped entirely, so the parser never sees them.
Parser::new(tokens) implements a recursive-descent parser producing CompilationUnit (the top-level AST node). The AST types are defined in src/ast.rs.
The parser handles:
- Program modules (
MODULE ... END name.) - Definition modules (
DEFINITION MODULE ...) - Implementation modules (
IMPLEMENTATION MODULE ...) - Foreign C modules (
DEFINITION MODULE FOR "C" ...) - All PIM4 statements and expressions
- Modula-2+ extensions when enabled (TRY/EXCEPT, REF, OBJECT, etc.)
SemanticAnalyzer (in src/sema.rs) is embedded inside each backend's CodeGen struct as self.sema. It performs:
- Symbol table construction with nested scope tracking
- Type checking and coercion
- Import resolution against registered definition modules
- Open array parameter tracking
The symbol table (src/symtab.rs) uses a scope stack (Vec<usize>) for nested scope management. Each scope has a parent, enabling walk-up lookup.
src/hir.rs defines a typed intermediate representation for designators, expressions, statements, and module structure. src/hir_build/ provides HirBuilder for statement/expression lowering and build_module() for constructing a complete HirModule as a prebuilt compilation phase.
Prebuilt HIR (build_module): Runs after sema, before backend codegen. Constructs an HirModule containing:
- Structural declarations: type_decls, const_decls, global_decls, exception_decls, proc_decls
- Embedded modules:
HirEmbeddedModulefor each imported implementation module, with its own types/consts/globals/procs/init body - Module body: init_body, except_handler, finally_handler (all pre-lowered to
Vec<HirStmt>) - Procedure declarations:
HirProcDeclper procedure with full signature, lowered body, locals, nested procs, closure captures, and except handler. Bodies are populated at arbitrary nesting depth via recursivebuild_nested_recursive.
Both backends iterate structural declarations from HirModule for:
- C backend: record forward decls, type decls, const decls, exception decls, global var decls, proc forward decls, except/finally handlers
- LLVM backend: type decls, const decls, exception decls, global var decls, proc decls (via
gen_hir_proc_decl), module body/except/finally
TypeId → C name resolver: A typeid_c_names map (populated from HirModule type_decls, def-module registration, and gen_type_decl emission) allows type_id_to_c() to resolve TypeIds to their C typedef names. Only non-structural types (records, enums, arrays, aliases) are registered to avoid cross-module conflicts with pointer/set types.
Key statement/expression lowering responsibilities:
- Statement lowering: Converts all AST statements (PIM4 and M2+) to
HirStmt. WITH is desugared to field projections. FOR direction is pre-computed. - Expression lowering: Converts AST expressions to
HirExprwith resolved types. Constants are unwrapped to literals (except when projections are present). Single-char strings carryTY_CHARwith promotion toTY_STRINGfor open array contexts. - Designator resolution: Maps AST designators to
Place(base + projections), using sema's scope chain as the single source of truth for locality and type identity. - Call argument expansion: Inserts
_highcompanions for open array parameters, wraps VAR arguments asAddrOf. SkipsAddrOffor args that are already pointers (open arrays, VAR params, fixed arrays). - WITH scope tracking: Resolves bare field names to qualified record field accesses.
- TYPECASE binding: Registers branch variables with the correct REF type before lowering the body.
- Scope-aware lookup: Uses
current_scopefrom sema, withvar_types_ownedchecked before context-provided var_types (so dynamically registered variables like TYPECASE bindings are visible).
src/cfg/ constructs a verified control flow graph from HIR statement bodies. The CFG is the single source of truth for control flow in both backends — no structured IF/WHILE/CASE/LOOP constructs survive past this stage.
Construction: build_cfg(body: &[HirStmt]) -> Cfg lowers all HIR control flow statements into basic blocks with terminators. The driver calls this for every procedure body, module init body, embedded module init, and local module init after HIR construction (Phase 4b).
Data model:
BasicBlock: sequential statements (Vec<HirStmt>— only Assign/ProcCall/Empty) + oneTerminatorTerminator:Goto(BlockId),Branch { cond, on_true, on_false },Switch { expr, arms, default },Return(Option<HirExpr>),Raise(Option<HirExpr>)SwitchArm: labels (Vec<CaseLabel>) + target block.CaseLabelisSingle(expr),Range(lo, hi), orType(SymbolId, Option<binding_var>)handler: Option<BlockId>per block — exception handler region annotation for TRY/EXCEPT
Lowering:
- IF/ELSIF/ELSE → Branch chains with merge block
- WHILE → Branch + Goto back-edge
- REPEAT → Goto body + Branch back-edge (condition reversed)
- FOR → init assign + cond Branch + body + latch (Add step) + Goto back
- LOOP/EXIT → Goto body + Goto back / Goto exit block
- CASE → Switch terminator with Single/Range labels
- TYPECASE → Switch terminator with Type labels + binding variable assignment
- TRY/EXCEPT/FINALLY → handler-annotated body blocks + catch dispatch (Branch chain) + finally blocks + Raise(None) for reraise
- LOCK → desugared to ProcCall(Lock) + body + ProcCall(Unlock)
- AND/OR → short-circuit Branch chains (real control flow, not eager evaluation)
Proc-level EXCEPT: The driver wraps procedure bodies that have except_handler in a synthetic HirStmtKind::Try before building the CFG, so exception dispatch is part of the CFG rather than a backend concern.
Module-level FINALLY: Folded into the module init body CFG via synthetic TRY wrapping. Both backends execute finally inline (no atexit).
Invariants (verified by Cfg::verify()):
- Entry block is always block 0
- All block IDs are sequential
- Every block has exactly one terminator
- All terminator targets are valid block IDs
- Handler targets are valid block IDs
Post-construction passes:
compute_preds()— materializes predecessor lists from terminatorscleanup()—remove_unreachable()(BFS from entry + handler roots) +collapse_trivial_gotos()(merge single-predecessor blocks)
src/codegen_c/ emits C code from CFG and HIR, split across 10 modules:
| File | Purpose |
|---|---|
mod.rs |
Core struct, state management, output buffer |
cfg_emit.rs |
CFG-driven body emission: block labels, terminators, SJLJ handler regions |
modules.rs |
Module-level codegen, imports, embedded impl modules |
decls.rs |
Procedure and variable declarations |
stmts.rs |
Statement dispatch (all statements route through HIR) |
hir_emit.rs |
HIR → C emission for statements (Assign/ProcCall only) and expressions |
exprs.rs |
Legacy AST expression helpers (escape functions, etc.) |
designators.rs |
HIR Place → C designator strings, name mangling |
types.rs |
TypeNode → C type strings, type name resolution |
m2plus.rs |
M2+ type/declaration codegen (REF, OBJECT, EXCEPTION) |
Key design decisions:
- Module-prefixed names: Imported symbols get
Module_Nameprefixes (e.g.,Stack_Push). - Embedded implementations: Imported module
.modfiles are inlined into the output C file, topologically sorted by dependencies. - Foreign modules:
DEFINITION MODULE FOR "C"modules emitexterndeclarations with bare C names. - EXPORTC pragma:
(*$EXPORTC "c_name"*)maps M2 procedure names to specific C names. - Record types: Emitted as
structwithtypedef struct X X;forward declarations. - Open arrays: Passed as pointer + high-bound pair.
- VAR parameters: Passed as pointers.
- CFG-driven control flow: All control flow (IF/WHILE/FOR/CASE/etc.) is emitted from CFG terminators as
goto L<n>;,if (cond) goto L<t>; else goto L<f>;, andreturn. No structured C control flow constructs are generated. - Exception handling: setjmp/longjmp via raw
m2_ExcFramefields. Handler regions are detected from CFGblock.handlerannotations. The backend sets up setjmp at handler region entry and pops the frame at region exit. Exception dispatch usesframe.exception_id == M2_EXC_Namecomparisons.
src/codegen_llvm/ emits LLVM IR as text (.ll files). Key modules:
| File | Purpose |
|---|---|
mod.rs |
Core struct, Val representation, semantic type queries, registration APIs |
cfg_emit.rs |
CFG-driven body emission: LLVM basic blocks, terminators, SJLJ handler regions |
modules.rs |
Module-level codegen: gen_program_module, gen_implementation_module, gen_embedded_impl_module_by_name, topo sort, import maps — all HIR-driven |
decls.rs |
HIR-based declaration emission: gen_hir_type_decls_from, gen_hir_var_decls_global_from, gen_hir_const_decls_from, gen_hir_exception_decls_from, gen_hir_proc_decl |
stmts.rs |
HIR statement emission (Assign/ProcCall only), builtin procedure expansion |
exprs.rs |
HIR expression emission, function calls, short-circuit AND/OR, COMPLEX builtins (CMPLX/RE/IM), NEW/DISPOSE |
designators.rs |
HIR Place → LLVM IR address/load, variant field offset computation, named array param handling |
types.rs |
TypeId resolution, LLVM type strings, float coercion, type_lowering delegation |
type_lowering.rs |
M2 types → LLVM IR types (variant record flattening, pseudo-field skip) |
llvm_types.rs |
LLVM type representation and IR emission |
stdlib_sigs.rs |
Standard library call signatures |
debug_info.rs |
DWARF metadata (DICompileUnit, DISubprogram, DILocalVariable) |
Key design decisions:
- Val carries TypeId: Every codegen result is
{name, ty, type_id}wheretype_idtracks the semantic identity from sema, enabling correct aggregate handling and cross-module type disambiguation. - Aggregate invariant: Records and arrays stay as addresses (pointers) in
gen_designator_load. Callers that need values (return, struct-by-value) load explicitly. This prevents invalid SSA loads of aggregate types. - Sema-driven types: All semantic questions (is this a pointer? what are the record fields?) are answered from the sema TypeRegistry, not LLVM type strings.
- Zero AST dependencies: All codegen reads from prebuilt HIR (
HirModule,HirProcDecl,HirEmbeddedModule). The only AST import (use crate::ast::*) is for shared types (BinaryOp,UnaryOp,ExprKind) used by HIR expressions, plusCompilationUnitfor entry-point dispatch. - Closure captures:
HirProcDecl.closure_capturesis populated by hir_build with transitive propagation — grandchild captures are forwarded through parent env structs. The LLVM backend promotes captured variables to globals. - CFG-driven control flow: All control flow is emitted from CFG terminators as
br label %B<n>,br i1 %cond, label %B<t>, label %B<f>, andret. Each CFG block maps to an LLVM basic block. No structured control flow reconstruction occurs. - Short-circuit evaluation: AND/OR are lowered to Branch chains in the CFG (real control flow, not eager LLVM
and/orinstructions). - Floored DIV/MOD: Calls
m2_div/m2_modruntime helpers for signed integer division (PIM4 requires floored, not truncated). - COMPLEX arithmetic: Delegates to
m2_complex_add/sub/mul/div/neg/eqruntime helpers. CMPLX/RE/IM builtins use LLVMinsertvalue/extractvalue. - M2+ exception handling: setjmp/longjmp-based
m2_ExcFramestack. The CFG annotates blocks withhandler: Option<BlockId>for exception regions; the backend emits SJLJ frame setup/teardown inline based on handler transitions. TRY/EXCEPT/FINALLY and procedure-level EXCEPT are all lowered in the CFG; the backend handles only the SJLJ mechanism. - RTTI:
M2_TypeDescglobals for REF/OBJECT types,M2_RefHeaderprepended to allocations,M2_ISAfor TYPECASE runtime type checking. - DWARF debug info:
DW_LANG_C99(for lldb compatibility),#dbg_declarerecords (LLVM 19+ format), full DILocalVariable/DIGlobalVariable metadata.
src/target.rs defines TargetInfo, which formalizes platform semantics consumed by both backends, the driver, and the runtime. It is constructed once at the start of driver::compile() — either from the host platform or from an explicit --target triple — and passed by reference to both backends.
struct TargetInfo {
triple: String, // e.g. "x86_64-unknown-linux-gnu"
arch: Arch, // X86_64 | Aarch64
os: Os, // Linux | Darwin
pointer_bits: u32, // 64 for all supported targets
endian: Endianness, // Little (all supported targets)
c_abi: CAbi, // SysV | Darwin
int_layout: IntLayout, // sizes of INTEGER, LONGINT, REAL, etc.
alignments: AlignmentInfo, // struct + primitive alignment rules
supports_setjmp: bool, // true for all POSIX targets
}Supported targets: x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin. All use LP64 data model.
Key APIs:
TargetInfo::from_host()— detect fromstd::env::consts::{ARCH, OS}TargetInfo::from_triple(s)— parse short (x86_64-linux), full (x86_64-unknown-linux-gnu), or LLVM canonical (arm64-apple-macosx14.0.0) triplesllvm_triple()/llvm_datalayout()— LLVM backend stringstype_size(tid, types)/type_align(tid, types)— size/alignment for any TypeId (primitives, records, arrays, variants)compute_record_layout(tid, types, target)— field offsets with C ABI paddingemit_c_layout_guards(target)—_Static_assertguards emitted into generated C for compile-time validation
The C backend emits layout guards after the runtime header to catch target mismatches at C compile time (e.g., cross-compiling with the wrong --cc). The LLVM backend uses llvm_triple() and llvm_datalayout() for the .ll header instead of detecting the host at codegen time.
The driver uses target.is_darwin() instead of cfg!(target_os) for linker flag selection, so --target correctly selects linker flags for the target platform rather than the host.
driver::compile() orchestrates the full pipeline in five phases:
- Phase 1 — Parse
.deffiles: Starting from the main module's imports, transitively discover and parse all.deffiles into aparsed_defsmap. - Phase 2 — Topological sort and register
.deffiles: Topologically sort the parsed.deffiles by their import dependencies, then register each with sema in order. Pre-registers type names first (as opaques) so cross-module type references resolve during full registration. - Phase 3 — Load
.modfiles: For each implementation module, find and parse its.modfile. Recursively discover and register any.deffiles referenced by the.modthat weren't found in Phase 1 (ensures transitive dependencies likeURI.defforHTTPClient.modare registered in correct order before the module that needs them). Register implementation module types with sema. - Phase 4 — Analyze, build HIR, build CFGs: Run sema on the main compilation unit. Build prebuilt
HirModuleviabuild_module(). Then build CFGs for all bodies: procedure bodies (with proc-level EXCEPT folded into synthetic TRY), module init body (with EXCEPT/FINALLY folded in), embedded module inits, and local module inits. CFGs are stored onHirProcDecl.cfg,HirModule.init_cfg, andHirEmbeddedModule.init_cfg. - Phase 5 — Backend emission: Pass the
HirModule(with pre-built CFGs) to the selected backend. Both backends iterate structural declarations from HIR for types/consts/globals/protos, then emit procedure and init bodies by walking CFG blocks — emitting labels, statements, and terminators. Write.cor.ll, invoke the system compiler, link.
The driver handles include path resolution, finding .def/.mod files, and constructing the compiler command line. The LLVM backend is selected with --llvm (full compilation) or --emit-llvm (emit .ll text only).
Debug mode (-g): The driver uses a two-step compile (.c -> .o -> executable) so the .o file stays on disk for DWARF debug info. On macOS, dsymutil creates a .dSYM bundle after linking. The codegen emits #line directives and setvbuf(stdout, NULL, _IONBF, 0) for unbuffered I/O. The C compiler receives -g -O0 -fno-omit-frame-pointer -fno-inline -gno-column-info.
Release mode: Single-step compile+link; the .c file is cleaned up after compilation.
src/analyze.rs provides analyze_source(), which runs lex -> parse -> sema without C codegen. This is the path used by the LSP server.
pub struct AnalysisResult {
pub ast: Option<CompilationUnit>,
pub symtab: SymbolTable,
pub types: TypeRegistry,
pub scope_map: ScopeMap,
pub ref_index: ReferenceIndex,
pub call_graph: HashMap<String, Vec<CallEdge>>,
pub diagnostics: Vec<CompileError>,
}ScopeMap maps source positions (line, col) to scope IDs. Used by completion to show only in-scope symbols.
ReferenceIndex tracks all symbol references in a file (definition sites and usage sites). Used by rename, highlight, and goto-def.
CallEdge records call sites with callee name, optional module qualifier, and source position (including end column for exact token spans).
The sema.into_results() method extracts the symbol table, type registry, scope map, and errors from the semantic analyzer without requiring codegen.
Cross-file features use identity-based keys to track symbols across the workspace.
Each symbol has:
file: canonical path of the defining filescope_id: scope number (0 = module level)module: module namename: symbol namekind: procedure, type, variable, constant, or module
- Cross-file key:
"Module::Name::kind"(e.g.,"Stack::Push::proc"). Used for cross-file rename and references. - Local key:
"file::scope_id::Name::kind". Used for intra-module disambiguation. - Nested procedures:
"Module::name@parent::proc"(e.g.,"M::helper@Outer::proc"). The@parentsuffix disambiguates same-named nested procedures.
src/lsp/index.rs maintains the workspace-wide index.
| Structure | Type | Purpose |
|---|---|---|
files |
HashMap<PathBuf, IndexedFile> |
Per-file index data, keyed by canonical path |
symbols |
Vec<WorkspaceSymbol> |
Flat symbol list for workspace/symbol search |
symbols_by_name |
HashMap<String, Vec<usize>> |
Name -> symbol indices (case-insensitive) |
refs_by_identity |
HashMap<IdentityKey, Vec<IdentityRef>> |
Cross-file references by identity |
defs_by_identity |
HashMap<IdentityKey, IdentityLocation> |
Definition locations by identity |
refs_by_name |
HashMap<String, Vec<IndexedRef>> |
Name-based fallback references |
calls_out |
HashMap<IdentityKey, Vec<WsCallEdge>> |
Outgoing call edges per procedure |
calls_in |
HashMap<IdentityKey, Vec<WsCallEdge>> |
Incoming call edges per procedure |
file_call_edges |
HashMap<PathBuf, Vec<(key, key)>> |
Per-file call edge tracking for incremental updates |
Files are stamped with (mtime, size, content_hash) using FNV-1a hashing. A file is re-indexed only when its stamp changes. Open documents are indexed from in-memory text, not disk.
rebuild_if_dirty() runs when the dirty flag is set:
- Clear all inverted indexes
- Iterate indexed files; extract symbols, refs, call edges
- Build
symbols_by_nameindex - Build
refs_by_identityanddefs_by_identity - Build
calls_outandcalls_infrom extracted call edges - Clear
dirtyflag
src/lsp/server.rs implements the main event loop.
stdin_reader_thread -> ServerEvent::Message(Json)
timer_thread -> ServerEvent::Tick (every MX_LSP_TICK_MS, default 50ms)
main loop:
on Tick: flush_pending_diagnostics()
flush_pending_index_updates()
on Message: dispatch request/notification
On textDocument/didChange:
- Update
DocumentStoreimmediately (interactive requests see new text) - Invalidate analysis cache for that URI
- Record URI in
pending_diagnosticswith current timestamp - Record URI in
pending_index_updateswith current timestamp
On Tick events, entries older than the debounce threshold are flushed:
- Diagnostics: re-analyze and publish
- Index: re-index from in-memory text and rebuild workspace index
textDocument/didSave bypasses debounce (immediate analysis + index update).
$/cancelRequest adds the request ID to a HashSet<i64>. At handler entry, cancelled requests get a -32800 (RequestCancelled) error response. Call hierarchy handlers also check cancellation after index rebuilds.
A match on the JSON-RPC method string routes to handler functions. Unknown methods with an id get a -32601 error. Unknown notifications are silently dropped.
src/project_resolver.rs is a crate-root module shared between the LSP server and the build system.
It provides:
Manifest::parse(content)-- INI-style parsing with[section]supportLockfile::parse(content)-- with[dep.NAME]sectionsLockfile::content_hash(content)-- FNV-1a hash for cache keysfind_project_root(path)-- walk-up directory search form2.tomlProjectContext::load(root, cli_paths)-- reads manifest + lockfile, resolves all include paths
The LSP's src/lsp/workspace.rs re-exports these types.
src/build.rs implements the mx build/run/test/clean subcommands.
The build system stamps all source files (mtime + size + FNV-1a hash) and stores the combined hash in .mx/build_state.json. If no stamps changed and the artifact exists, compilation is skipped.
- Resolve entry point from manifest
- Collect all
.mod/.deffiles from include paths - Stamp all source files + manifest + lockfile
- Compare combined hash against cached state
- If changed: build
CompileOptionsfrom manifest, calldriver::compile() - Save new build state
230+ tests covering:
- Lexer: tokenization, keywords, case sensitivity, feature pragmas
- Parser: AST construction for various constructs
- CodeGen:
#linedirective emission, debug/non-debug mode - LSP handlers: completion, hover, call hierarchy, signature help, highlighting
- Workspace index: call graph, incremental reindex, multi-root
- Analysis: scope map, reference index, symbol table
Categorized .mod files in examples/ compiled and executed. Each test has an expected output comment at the top. The test runner compiles with mx, runs the binary, and compares stdout against expected output.
1150+ tests across multiple compiler configurations (PIM4, M2+, optimized, with/without sanitizers, C/LLVM backends). Tests are defined in tests/adversarial/tests.json and run via run_adversarial.py. Use --backend all to test both C and LLVM backends.
22 tests validating the compiler's external interface:
--version-jsonoutput format--print-targetsoutput--emit-coutputcompile --planexecution--diagnostics-jsonformat- Capability advertisement
cargo test # 230+ unit tests
bash tests/run_all.sh # integration tests
bash tests/conformance.sh # conformance tests
python3 tests/adversarial/run_adversarial.py --mode ci # adversarial tests (C backend)
python3 tests/adversarial/run_adversarial.py --backend all # adversarial tests (C + LLVM)| File | Purpose |
|---|---|
src/main.rs |
CLI entry point, subcommand routing |
src/lexer.rs |
Tokenizer |
src/parser.rs |
Recursive-descent parser |
src/ast.rs |
AST node types |
src/sema.rs |
Semantic analysis |
src/hir.rs |
HIR types (Place, Projection, HirExpr, HirStmt) |
src/hir_build/ |
HIR builder: mod.rs (build_module, struct defs, tests) + lower.rs (HirBuilder impl) |
src/cfg/ |
CFG: mod.rs (data model, verify, cleanup, DOT) + build.rs (CfgBuilder, lowering) |
src/codegen_c/ |
C code generation (10 modules, CFG-driven control flow) |
src/codegen_llvm/ |
LLVM IR code generation (12 modules, CFG-driven control flow, zero AST dependencies) |
src/target.rs |
Target abstraction (TargetInfo, layout, ABI) |
src/driver.rs |
Compilation orchestration |
src/build.rs |
Project build system |
src/analyze.rs |
Analysis-only path for LSP |
src/project_resolver.rs |
Manifest/lockfile parsing |
src/stdlib.rs |
Standard library definitions + C runtime |
src/symtab.rs |
Symbol table |
src/types.rs |
Type registry |
src/errors.rs |
Error types |
src/json.rs |
JSON parser |
src/lsp/server.rs |
LSP event loop and dispatch |
src/lsp/index.rs |
Workspace index |
src/lsp/analysis.rs |
Analysis caching, def cache |
src/lsp/documents.rs |
Open document store |
src/lsp/transport.rs |
JSON-RPC framing |
src/lsp/completion.rs |
Code completion |
src/lsp/hover.rs |
Hover information |
src/lsp/goto_def.rs |
Go to definition |
src/lsp/call_hierarchy.rs |
Call hierarchy |
src/lsp/highlight.rs |
Document highlight |
src/lsp/signature_help.rs |
Signature help |
src/lsp/symbols.rs |
Document symbols |
src/lsp/semantic_tokens.rs |
Semantic token highlighting |
src/lsp/code_actions.rs |
Code actions |
src/lsp/workspace.rs |
Re-exports from project_resolver |