summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-20 12:51:23 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-20 12:51:23 +0000
commitb106fe19b55e9fe3348b3a5c9992c21dac27b02c (patch)
tree32663c9a791b7f45ce9bc61c5a87351d5dc2c782
parente5d8c4219cfe5629a3ce4dbff01406a1817a788f (diff)
Working a bit on the LSP + fixed some bugs
-rw-r--r--Makefile1
-rw-r--r--src/codegen/codegen_main.c110
-rw-r--r--src/lsp/json_rpc.c234
-rw-r--r--src/lsp/lsp_analysis.c566
-rw-r--r--src/lsp/lsp_index.c6
-rw-r--r--src/lsp/lsp_project.c299
-rw-r--r--src/lsp/lsp_project.h59
-rw-r--r--src/parser/parser_stmt.c10
-rw-r--r--tests/features/test_match_ref.zc25
9 files changed, 1118 insertions, 192 deletions
diff --git a/Makefile b/Makefile
index 5eb4040..3c70e61 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@ SRCS = src/main.c \
src/lsp/lsp_main.c \
src/lsp/lsp_analysis.c \
src/lsp/lsp_index.c \
+ src/lsp/lsp_project.c \
src/zen/zen_facts.c \
src/repl/repl.c \
src/plugins/plugin_manager.c
diff --git a/src/codegen/codegen_main.c b/src/codegen/codegen_main.c
index 7382827..97abfc7 100644
--- a/src/codegen/codegen_main.c
+++ b/src/codegen/codegen_main.c
@@ -14,33 +14,101 @@ static int struct_depends_on(ASTNode *s1, const char *target_name)
return 0;
}
- // Only structs have dependencies that matter for ordering.
- if (s1->type != NODE_STRUCT)
+ // Check structs
+ if (s1->type == NODE_STRUCT)
{
- return 0;
- }
+ ASTNode *field = s1->strct.fields;
+ while (field)
+ {
+ if (field->type == NODE_FIELD && field->field.type)
+ {
+ char *type_str = field->field.type;
- ASTNode *field = s1->strct.fields;
- while (field)
+ // Skip pointers - they don't create ordering dependency.
+ if (strchr(type_str, '*'))
+ {
+ field = field->next;
+ continue;
+ }
+
+ // Clean type string (remove struct/enum prefixes)
+ const char *clean = type_str;
+ if (strncmp(clean, "struct ", 7) == 0)
+ {
+ clean += 7;
+ }
+ else if (strncmp(clean, "enum ", 5) == 0)
+ {
+ clean += 5;
+ }
+ else if (strncmp(clean, "union ", 6) == 0)
+ {
+ clean += 6;
+ }
+
+ // Check for match
+ size_t len = strlen(target_name);
+ if (strncmp(clean, target_name, len) == 0)
+ {
+ char next = clean[len];
+ if (next == 0 || next == '[' || isspace(next))
+ {
+ return 1;
+ }
+ }
+ }
+ field = field->next;
+ }
+ }
+ // Check enums (ADTs)
+ else if (s1->type == NODE_ENUM)
{
- if (field->type == NODE_FIELD && field->field.type)
+ ASTNode *variant = s1->enm.variants;
+ while (variant)
{
- char *type_str = field->field.type;
- // Skip pointers - they don't create ordering dependency.
- if (strchr(type_str, '*'))
+ if (variant->type == NODE_ENUM_VARIANT && variant->variant.payload)
{
- field = field->next;
- continue;
- }
+ char *type_str = type_to_string(variant->variant.payload);
+ if (type_str)
+ {
+ if (strchr(type_str, '*'))
+ {
+ free(type_str);
+ variant = variant->next;
+ continue;
+ }
- // Check if this field's type matches target (struct or enum).
- if (strcmp(type_str, target_name) == 0)
- {
- return 1;
+ const char *clean = type_str;
+ if (strncmp(clean, "struct ", 7) == 0)
+ {
+ clean += 7;
+ }
+ else if (strncmp(clean, "enum ", 5) == 0)
+ {
+ clean += 5;
+ }
+ else if (strncmp(clean, "union ", 6) == 0)
+ {
+ clean += 6;
+ }
+
+ size_t len = strlen(target_name);
+ if (strncmp(clean, target_name, len) == 0)
+ {
+ char next = clean[len];
+ if (next == 0 || next == '[' || isspace(next))
+ {
+ free(type_str);
+ return 1;
+ }
+ }
+ free(type_str);
+ }
}
+ variant = variant->next;
}
- field = field->next;
}
+
return 0;
}
@@ -102,8 +170,8 @@ static ASTNode *topo_sort_structs(ASTNode *head)
continue;
}
- // Enums and traits have no dependencies, emit first.
- if (nodes[i]->type == NODE_ENUM || nodes[i]->type == NODE_TRAIT)
+ // Traits have no dependencies, emit first.
+ if (nodes[i]->type == NODE_TRAIT)
{
order[order_idx++] = i;
emitted[i] = 1;
@@ -111,7 +179,7 @@ static ASTNode *topo_sort_structs(ASTNode *head)
continue;
}
- // For structs, check if all dependencies are emitted.
+ // For structs/enums, check if all dependencies are emitted.
int can_emit = 1;
for (int j = 0; j < count; j++)
{
diff --git a/src/lsp/json_rpc.c b/src/lsp/json_rpc.c
index 903da71..c8c9a3b 100644
--- a/src/lsp/json_rpc.c
+++ b/src/lsp/json_rpc.c
@@ -3,108 +3,148 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
-// Basic JSON parsing helpers
-char *get_json_string(const char *json, const char *key)
+// Helper to skip whitespace
+const char *skip_ws(const char *p)
{
- char search[256];
- sprintf(search, "\"%s\":\"", key);
- char *p = strstr(json, search);
- if (!p)
+ while (*p && isspace(*p))
{
- return NULL;
+ p++;
}
- p += strlen(search);
- char *end = strchr(p, '"');
- if (!end)
+ return p;
+}
+
+// Robust JSON string extractor
+// Finds "key" ... : ... "value"
+char *get_json_string(const char *json, const char *key)
+{
+ char key_pattern[256];
+ sprintf(key_pattern, "\"%s\"", key);
+
+ char *p = strstr(json, key_pattern);
+ while (p)
{
- return NULL;
+ const char *cursor = p + strlen(key_pattern);
+ cursor = skip_ws(cursor);
+ if (*cursor == ':')
+ {
+ cursor++; // skip :
+ cursor = skip_ws(cursor);
+ if (*cursor == '"')
+ {
+ // Found start of value
+ cursor++;
+ const char *start = cursor;
+ // Find end " (handling escapes?)
+ // MVP: just find next " that is not escaped
+ while (*cursor)
+ {
+ if (*cursor == '"' && *(cursor - 1) != '\\')
+ {
+ break;
+ }
+ cursor++;
+ }
+
+ int len = cursor - start;
+ char *res = malloc(len + 1);
+ strncpy(res, start, len);
+ res[len] = 0;
+ return res;
+ }
+ }
+ // False positive? Find next
+ p = strstr(p + 1, key_pattern);
}
- int len = end - p;
- char *res = malloc(len + 1);
- strncpy(res, p, len);
- res[len] = 0;
- return res;
+ return NULL;
}
-// Extract nested "text" from params/contentChanges/0/text or
-// params/textDocument/text This is very hacky for MVP. proper JSON library
-// needed.
-
+// Extract nested "text" from params...
+// Since "text" might be huge and contain anything, we need to be careful.
char *get_text_content(const char *json)
{
- char *p = strstr(json, "\"text\":\"");
- if (!p)
+ // Search for "text" key
+ char *res = get_json_string(json, "text");
+ if (!res)
{
return NULL;
}
- p += 8;
-
- size_t cap = strlen(p);
- char *res = malloc(cap + 1);
- char *dst = res;
- while (*p)
+ size_t len = strlen(res);
+ char *unescaped = malloc(len + 1);
+ char *dst = unescaped;
+ char *src = res;
+ while (*src)
{
- if (*p == '\\')
+ if (*src == '\\')
{
- p++;
- if (*p == 'n')
+ src++;
+ if (*src == 'n')
{
*dst++ = '\n';
}
- else if (*p == 'r')
+ else if (*src == 'r')
{
*dst++ = '\r';
}
- else if (*p == 't')
+ else if (*src == 't')
{
*dst++ = '\t';
}
- else if (*p == '"')
+ else if (*src == '"')
{
*dst++ = '"';
}
- else if (*p == '\\')
+ else if (*src == '\\')
{
*dst++ = '\\';
}
else
{
- *dst++ = *p; // preserve others
+ *dst++ = *src;
}
- p++;
- }
- else if (*p == '"')
- {
- break; // End of string.
}
else
{
- *dst++ = *p++;
+ *dst++ = *src;
}
+ src++;
}
*dst = 0;
- return res;
+ free(res);
+ return unescaped;
}
-// Helper to get line/char
void get_json_position(const char *json, int *line, int *col)
{
- char *pos = strstr(json, "\"position\":");
- if (!pos)
- {
- return;
- }
- char *l = strstr(pos, "\"line\":");
- if (l)
+ // Search for "line" and "character"
+ // Note: they are integers
+
+ char *p = strstr(json, "\"line\"");
+ if (p)
{
- *line = atoi(l + 7);
+ p += 6; // skip "line"
+ p = (char *)skip_ws(p);
+ if (*p == ':')
+ {
+ p++;
+ p = (char *)skip_ws(p);
+ *line = atoi(p);
+ }
}
- char *c = strstr(pos, "\"character\":");
- if (c)
+
+ p = strstr(json, "\"character\"");
+ if (p)
{
- *col = atoi(c + 12);
+ p += 11;
+ p = (char *)skip_ws(p);
+ if (*p == ':')
+ {
+ p++;
+ p = (char *)skip_ws(p);
+ *col = atoi(p);
+ }
}
}
@@ -112,11 +152,42 @@ void lsp_check_file(const char *uri, const char *src);
void lsp_goto_definition(const char *uri, int line, int col);
void lsp_hover(const char *uri, int line, int col);
void lsp_completion(const char *uri, int line, int col);
+void lsp_document_symbol(const char *uri);
+void lsp_references(const char *uri, int line, int col);
+void lsp_signature_help(const char *uri, int line, int col);
+
+#include "lsp_project.h"
void handle_request(const char *json_str)
{
- if (strstr(json_str, "\"method\":\"initialize\""))
+ // Looser method detection
+ if (strstr(json_str, "initialize"))
{
+ // Extract rootPath or rootUri
+ char *root = get_json_string(json_str, "rootPath");
+ if (!root)
+ {
+ root = get_json_string(json_str, "rootUri");
+ }
+
+ // Clean up URI if needed
+ if (root && strncmp(root, "file://", 7) == 0)
+ {
+ char *clean = strdup(root + 7);
+ free(root);
+ root = clean;
+ }
+
+ if (root)
+ {
+ lsp_project_init(root);
+ free(root);
+ }
+ else
+ {
+ lsp_project_init(".");
+ }
+
const char *response = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{"
"\"capabilities\":{\"textDocumentSync\":1,"
"\"definitionProvider\":true,\"hoverProvider\":true,"
@@ -127,16 +198,14 @@ void handle_request(const char *json_str)
return;
}
- if (strstr(json_str, "\"method\":\"textDocument/didOpen\"") ||
- strstr(json_str, "\"method\":\"textDocument/didChange\""))
+ if (strstr(json_str, "didOpen") || strstr(json_str, "didChange"))
{
-
char *uri = get_json_string(json_str, "uri");
char *text = get_text_content(json_str);
if (uri && text)
{
- fprintf(stderr, "zls: Checking %s\n", uri);
+ // fprintf(stderr, "zls: Checking %s\n", uri);
lsp_check_file(uri, text);
}
@@ -150,7 +219,7 @@ void handle_request(const char *json_str)
}
}
- if (strstr(json_str, "\"method\":\"textDocument/definition\""))
+ if (strstr(json_str, "textDocument/definition"))
{
char *uri = get_json_string(json_str, "uri");
int line = 0, col = 0;
@@ -158,13 +227,12 @@ void handle_request(const char *json_str)
if (uri)
{
- fprintf(stderr, "zls: Definition request at %d:%d\n", line, col);
lsp_goto_definition(uri, line, col);
free(uri);
}
}
- if (strstr(json_str, "\"method\":\"textDocument/hover\""))
+ if (strstr(json_str, "textDocument/hover"))
{
char *uri = get_json_string(json_str, "uri");
int line = 0, col = 0;
@@ -172,13 +240,12 @@ void handle_request(const char *json_str)
if (uri)
{
- fprintf(stderr, "zls: Hover request at %d:%d\n", line, col);
lsp_hover(uri, line, col);
free(uri);
}
}
- if (strstr(json_str, "\"method\":\"textDocument/completion\""))
+ if (strstr(json_str, "textDocument/completion"))
{
char *uri = get_json_string(json_str, "uri");
int line = 0, col = 0;
@@ -186,9 +253,44 @@ void handle_request(const char *json_str)
if (uri)
{
- fprintf(stderr, "zls: Completion request at %d:%d\n", line, col);
lsp_completion(uri, line, col);
free(uri);
}
}
+
+ if (strstr(json_str, "textDocument/documentSymbol"))
+ {
+ char *uri = get_json_string(json_str, "uri");
+ if (uri)
+ {
+ lsp_document_symbol(uri);
+ free(uri);
+ }
+ }
+
+ if (strstr(json_str, "textDocument/references"))
+ {
+ char *uri = get_json_string(json_str, "uri");
+ int line = 0, col = 0;
+ get_json_position(json_str, &line, &col);
+
+ if (uri)
+ {
+ lsp_references(uri, line, col);
+ free(uri);
+ }
+ }
+
+ if (strstr(json_str, "textDocument/signatureHelp"))
+ {
+ char *uri = get_json_string(json_str, "uri");
+ int line = 0, col = 0;
+ get_json_position(json_str, &line, &col);
+
+ if (uri)
+ {
+ lsp_signature_help(uri, line, col);
+ free(uri);
+ }
+ }
}
diff --git a/src/lsp/lsp_analysis.c b/src/lsp/lsp_analysis.c
index d455894..b8b9189 100644
--- a/src/lsp/lsp_analysis.c
+++ b/src/lsp/lsp_analysis.c
@@ -1,13 +1,10 @@
-
#include "json_rpc.h"
-#include "lsp_index.h"
-#include "parser.h"
+#include "lsp_project.h" // Includes lsp_index.h, parser.h
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-
-static LSPIndex *g_index = NULL;
+#include <unistd.h>
typedef struct Diagnostic
{
@@ -23,17 +20,15 @@ typedef struct
Diagnostic *tail;
} DiagnosticList;
-static ParserContext *g_ctx = NULL;
-static char *g_last_src = NULL;
-
// Callback for parser errors.
void lsp_on_error(void *data, Token t, const char *msg)
{
DiagnosticList *list = (DiagnosticList *)data;
- Diagnostic *d = xmalloc(sizeof(Diagnostic));
+ // Simple allocation for MVP
+ Diagnostic *d = calloc(1, sizeof(Diagnostic));
d->line = t.line > 0 ? t.line - 1 : 0;
d->col = t.col > 0 ? t.col - 1 : 0;
- d->message = xstrdup(msg);
+ d->message = strdup(msg);
d->next = NULL;
if (!list->head)
@@ -50,44 +45,39 @@ void lsp_on_error(void *data, Token t, const char *msg)
void lsp_check_file(const char *uri, const char *json_src)
{
- // Prepare ParserContext (persistent).
- if (g_ctx)
+ if (!g_project)
{
-
- free(g_ctx);
+ // Fallback or lazy init? current dir
+ char cwd[1024];
+ if (getcwd(cwd, sizeof(cwd)))
+ {
+ lsp_project_init(cwd);
+ }
+ else
+ {
+ lsp_project_init(".");
+ }
}
- g_ctx = calloc(1, sizeof(ParserContext));
- g_ctx->is_fault_tolerant = 1;
+ // Setup error capture on the global project context
DiagnosticList diagnostics = {0};
- g_ctx->error_callback_data = &diagnostics;
- g_ctx->on_error = lsp_on_error;
-
- // Prepare Lexer.
- // Cache source.
- if (g_last_src)
- {
- free(g_last_src);
- }
- g_last_src = strdup(json_src);
- Lexer l;
- lexer_init(&l, json_src);
+ // We attach the callback to 'g_project->ctx'.
+ // NOTE: If we use lsp_project_update_file, it uses g_project->ctx.
+ void *old_data = g_project->ctx->error_callback_data;
+ void (*old_cb)(void *, Token, const char *) = g_project->ctx->on_error;
- ASTNode *root = parse_program(g_ctx, &l);
+ g_project->ctx->error_callback_data = &diagnostics;
+ g_project->ctx->on_error = lsp_on_error;
- if (g_index)
- {
- lsp_index_free(g_index);
- }
- g_index = lsp_index_new();
- if (root)
- {
- lsp_build_index(g_index, root);
- }
+ // Update and Parse
+ lsp_project_update_file(uri, json_src);
- // Construct JSON Response (notification)
+ // Restore
+ g_project->ctx->on_error = old_cb;
+ g_project->ctx->error_callback_data = old_data;
+ // Construct JSON Response (publishDiagnostics)
char *notification = malloc(128 * 1024);
char *p = notification;
p += sprintf(p,
@@ -98,7 +88,6 @@ void lsp_check_file(const char *uri, const char *json_src)
Diagnostic *d = diagnostics.head;
while (d)
{
-
p += sprintf(p,
"{\"range\":{\"start\":{\"line\":%d,\"character\":%d},\"end\":"
"{\"line\":%d,"
@@ -109,20 +98,17 @@ void lsp_check_file(const char *uri, const char *json_src)
{
p += sprintf(p, ",");
}
-
d = d->next;
}
p += sprintf(p, "]}}");
- // Send notification.
long len = strlen(notification);
fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", len, notification);
fflush(stdout);
free(notification);
- // Cleanup.
Diagnostic *cur = diagnostics.head;
while (cur)
{
@@ -135,58 +121,139 @@ void lsp_check_file(const char *uri, const char *json_src)
void lsp_goto_definition(const char *uri, int line, int col)
{
- if (!g_index)
+ ProjectFile *pf = lsp_project_get_file(uri);
+ LSPIndex *idx = pf ? pf->index : NULL;
+
+ if (!idx)
{
return;
}
- LSPRange *r = lsp_find_at(g_index, line, col);
- if (r && r->type == RANGE_REFERENCE)
- {
- // Found reference, return definition
- char resp[1024];
- sprintf(resp,
- "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\","
- "\"range\":{\"start\":{"
- "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%"
- "d}}}}",
- uri, r->def_line, r->def_col, r->def_line, r->def_col);
+ LSPRange *r = lsp_find_at(idx, line, col);
- fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp);
- fflush(stdout);
- }
- else if (r && r->type == RANGE_DEFINITION)
+ // 1. Check Local Index
+ if (r)
{
- // Already at definition? Return itself.
- char resp[1024];
- sprintf(resp,
- "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\","
- "\"range\":{\"start\":{"
- "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%"
- "d}}}}",
- uri, r->start_line, r->start_col, r->end_line, r->end_col);
+ if (r->type == RANGE_DEFINITION)
+ {
+ // Already at definition
+ char resp[1024];
+ sprintf(resp,
+ "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\","
+ "\"range\":{\"start\":{"
+ "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%d}}}}",
+ uri, r->start_line, r->start_col, r->end_line, r->end_col);
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp);
+ fflush(stdout);
+ return;
+ }
+ else if (r->type == RANGE_REFERENCE && r->def_line >= 0)
+ {
+ LSPRange *def = lsp_find_at(idx, r->def_line, r->def_col);
+ int is_local = 0;
+ if (def && def->type == RANGE_DEFINITION)
+ {
+ // Check name congruence
+ char *ref_name = NULL;
+ char *def_name = NULL;
- fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp);
- fflush(stdout);
+ if (r->node->type == NODE_EXPR_VAR)
+ {
+ ref_name = r->node->var_ref.name;
+ }
+ else if (r->node->type == NODE_EXPR_CALL &&
+ r->node->call.callee->type == NODE_EXPR_VAR)
+ {
+ ref_name = r->node->call.callee->var_ref.name;
+ }
+
+ if (def->node->type == NODE_FUNCTION)
+ {
+ def_name = def->node->func.name;
+ }
+ else if (def->node->type == NODE_VAR_DECL)
+ {
+ def_name = def->node->var_decl.name;
+ }
+ else if (def->node->type == NODE_CONST)
+ {
+ def_name = def->node->var_decl.name;
+ }
+ else if (def->node->type == NODE_STRUCT)
+ {
+ def_name = def->node->strct.name;
+ }
+
+ if (ref_name && def_name && strcmp(ref_name, def_name) == 0)
+ {
+ is_local = 1;
+ }
+ }
+
+ if (is_local)
+ {
+ char resp[1024];
+ sprintf(resp,
+ "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\","
+ "\"range\":{\"start\":{"
+ "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%d}}}}",
+ uri, r->def_line, r->def_col, r->def_line, r->def_col);
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp);
+ fflush(stdout);
+ return;
+ }
+ }
}
- else
+
+ // 2. Global Definition (if local failed)
+ if (r && r->node)
{
- // Null result
- const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}";
- fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp);
- fflush(stdout);
+ char *name = NULL;
+ if (r->node->type == NODE_EXPR_VAR)
+ {
+ name = r->node->var_ref.name;
+ }
+ else if (r->node->type == NODE_EXPR_CALL && r->node->call.callee->type == NODE_EXPR_VAR)
+ {
+ name = r->node->call.callee->var_ref.name;
+ }
+
+ if (name)
+ {
+ DefinitionResult def = lsp_project_find_definition(name);
+ if (def.uri && def.range)
+ {
+ char resp[1024];
+ sprintf(resp,
+ "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\","
+ "\"range\":{\"start\":{"
+ "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%d}}}}",
+ def.uri, def.range->start_line, def.range->start_col, def.range->end_line,
+ def.range->end_col);
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp);
+ fflush(stdout);
+ return;
+ }
+ }
}
+
+ const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}";
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp);
+ fflush(stdout);
}
void lsp_hover(const char *uri, int line, int col)
{
(void)uri;
- if (!g_index)
+ ProjectFile *pf = lsp_project_get_file(uri);
+ LSPIndex *idx = pf ? pf->index : NULL;
+
+ if (!idx)
{
return;
}
- LSPRange *r = lsp_find_at(g_index, line, col);
+ LSPRange *r = lsp_find_at(idx, line, col);
char *text = NULL;
if (r)
@@ -197,7 +264,7 @@ void lsp_hover(const char *uri, int line, int col)
}
else if (r->type == RANGE_REFERENCE)
{
- LSPRange *def = lsp_find_at(g_index, r->def_line, r->def_col);
+ LSPRange *def = lsp_find_at(idx, r->def_line, r->def_col);
if (def && def->type == RANGE_DEFINITION)
{
text = def->hover_text;
@@ -208,7 +275,6 @@ void lsp_hover(const char *uri, int line, int col)
if (text)
{
char *json = malloc(16384);
- // content: { kind: markdown, value: text }
sprintf(json,
"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"contents\":{\"kind\":"
"\"markdown\","
@@ -229,20 +295,18 @@ void lsp_hover(const char *uri, int line, int col)
void lsp_completion(const char *uri, int line, int col)
{
- (void)uri;
- if (!g_ctx)
+ ProjectFile *pf = lsp_project_get_file(uri);
+ // Need global project context
+ if (!g_project || !g_project->ctx || !pf)
{
return;
}
- // Context-aware completion (Dot access)
- if (g_last_src)
+ // 1. Context-aware completion (Dot access)
+ if (pf->source)
{
- // Simple line access
int cur_line = 0;
-
- char *ptr = g_last_src;
- // Fast forward to line
+ char *ptr = pf->source;
while (*ptr && cur_line < line)
{
if (*ptr == '\n')
@@ -254,14 +318,12 @@ void lsp_completion(const char *uri, int line, int col)
if (col > 0 && ptr[col - 1] == '.')
{
- // Found dot!
- // Scan backwards for identifier: [whitespace] [ident] .
+ // Found dot! Scan backwards for identifier
int i = col - 2;
while (i >= 0 && (ptr[i] == ' ' || ptr[i] == '\t'))
{
i--;
}
-
if (i >= 0)
{
int end_ident = i;
@@ -279,7 +341,7 @@ void lsp_completion(const char *uri, int line, int col)
var_name[len] = 0;
char *type_name = NULL;
- Symbol *sym = find_symbol_in_all(g_ctx, var_name);
+ Symbol *sym = find_symbol_in_all(g_project->ctx, var_name);
if (sym)
{
@@ -297,7 +359,7 @@ void lsp_completion(const char *uri, int line, int col)
{
char clean_name[256];
char *src = type_name;
- if (0 == strncmp(src, "struct ", 7))
+ if (strncmp(src, "struct ", 7) == 0)
{
src += 7;
}
@@ -308,12 +370,13 @@ void lsp_completion(const char *uri, int line, int col)
}
*dst = 0;
- // Lookup struct.
- StructDef *sd = g_ctx->struct_defs;
+ // Lookup struct in GLOBAL registry
+ StructDef *sd = g_project->ctx->struct_defs;
while (sd)
{
- if (0 == strcmp(sd->name, clean_name))
+ if (strcmp(sd->name, clean_name) == 0)
{
+ // Found struct!
char *json_fields = malloc(1024 * 1024);
char *pj = json_fields;
pj += sprintf(pj, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[");
@@ -331,65 +394,358 @@ void lsp_completion(const char *uri, int line, int col)
pj += sprintf(
pj,
"{\"label\":\"%s\",\"kind\":5,\"detail\":\"field %s\"}",
- field->field.name, field->field.type); // Kind 5 = Field
+ field->field.name, field->field.type);
ffirst = 0;
field = field->next;
}
}
-
pj += sprintf(pj, "]}");
fprintf(stdout, "Content-Length: %ld\r\n\r\n%s",
strlen(json_fields), json_fields);
fflush(stdout);
free(json_fields);
- free(json);
- return; // Done, yippee.
+ free(type_name); // type_to_string arg
+ return;
}
sd = sd->next;
}
+ if (sym && sym->type_info)
+ {
+ free(type_name);
+ }
}
}
}
}
}
- char *json = xmalloc(1024 * 1024); // 1MB buffer.
+ // 2. Global Completion (Functions & Structs)
+ char *json = malloc(1024 * 1024);
char *p = json;
p += sprintf(p, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[");
int first = 1;
// Functions
- FuncSig *f = g_ctx->func_registry;
+ FuncSig *f = g_project->ctx->func_registry;
while (f)
{
if (!first)
{
p += sprintf(p, ",");
}
- p += sprintf(p, "{\"label\":\"%s\",\"kind\":3,\"detail\":\"fn %s(...)\"}", f->name,
- f->name); // Kind 3 = Function
+ p += sprintf(p, "{\"label\":\"%s\",\"kind\":3,\"detail\":\"fn %s\"}", f->name, f->name);
first = 0;
f = f->next;
}
// Structs
- StructDef *s = g_ctx->struct_defs;
+ StructDef *s = g_project->ctx->struct_defs;
while (s)
{
if (!first)
{
p += sprintf(p, ",");
}
- p += sprintf(p, "{\"label\":\"%s\",\"kind\":22,\"detail\":\"struct %s\"}", s->name,
- s->name); // Kind 22 = Struct
+ p +=
+ sprintf(p, "{\"label\":\"%s\",\"kind\":22,\"detail\":\"struct %s\"}", s->name, s->name);
first = 0;
s = s->next;
}
p += sprintf(p, "]}");
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json);
+ fflush(stdout);
+ free(json);
+}
+void lsp_document_symbol(const char *uri)
+{
+ ProjectFile *pf = lsp_project_get_file(uri);
+ if (!pf || !pf->index)
+ {
+ const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}";
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp);
+ fflush(stdout);
+ return;
+ }
+
+ char *json = malloc(1024 * 1024);
+ char *p = json;
+ p += sprintf(p, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[");
+
+ int first = 1;
+ LSPRange *r = pf->index->head;
+ while (r)
+ {
+ if (r->type == RANGE_DEFINITION && r->node)
+ {
+ char *name = NULL;
+ int kind = 0; // 0 = default
+
+ if (r->node->type == NODE_FUNCTION)
+ {
+ name = r->node->func.name;
+ kind = 12; // Function
+ }
+ else if (r->node->type == NODE_STRUCT)
+ {
+ name = r->node->strct.name;
+ kind = 23; // Struct
+ }
+ else if (r->node->type == NODE_VAR_DECL)
+ {
+ name = r->node->var_decl.name;
+ kind = 13; // Variable
+ }
+ else if (r->node->type == NODE_CONST)
+ {
+ name = r->node->var_decl.name;
+ kind = 14; // Constant
+ }
+
+ if (name)
+ {
+ if (!first)
+ {
+ p += sprintf(p, ",");
+ }
+ p += sprintf(p,
+ "{\"name\":\"%s\",\"kind\":%d,\"location\":{\"uri\":\"%s\",\"range\":{"
+ "\"start\":{\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,"
+ "\"character\":%d}}}}",
+ name, kind, uri, r->start_line, r->start_col, r->end_line, r->end_col);
+ first = 0;
+ }
+ }
+ r = r->next;
+ }
+
+ p += sprintf(p, "]}");
fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json);
fflush(stdout);
free(json);
}
+
+void lsp_references(const char *uri, int line, int col)
+{
+ ProjectFile *pf = lsp_project_get_file(uri);
+ if (!pf || !pf->index)
+ {
+ return;
+ }
+
+ LSPRange *r = lsp_find_at(pf->index, line, col);
+ if (!r || !r->node)
+ {
+ // No symbol at cursor?
+ const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}";
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp);
+ fflush(stdout);
+ return;
+ }
+
+ char *name = NULL;
+ if (r->node->type == NODE_FUNCTION)
+ {
+ name = r->node->func.name;
+ }
+ else if (r->node->type == NODE_VAR_DECL)
+ {
+ name = r->node->var_decl.name;
+ }
+ else if (r->node->type == NODE_CONST)
+ {
+ name = r->node->var_decl.name;
+ }
+ else if (r->node->type == NODE_STRUCT)
+ {
+ name = r->node->strct.name;
+ }
+ else if (r->node->type == NODE_EXPR_VAR)
+ {
+ name = r->node->var_ref.name;
+ }
+ else if (r->node->type == NODE_EXPR_CALL && r->node->call.callee->type == NODE_EXPR_VAR)
+ {
+ name = r->node->call.callee->var_ref.name;
+ }
+
+ if (!name)
+ {
+ const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}";
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp);
+ fflush(stdout);
+ return;
+ }
+
+ ReferenceResult *refs = lsp_project_find_references(name);
+
+ char *json = malloc(1024 * 1024); // Large buffer for references
+ char *p = json;
+ p += sprintf(p, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[");
+
+ int first = 1;
+ ReferenceResult *curr = refs;
+ while (curr)
+ {
+ if (!first)
+ {
+ p += sprintf(p, ",");
+ }
+ p += sprintf(p,
+ "{\"uri\":\"%s\",\"range\":{\"start\":{\"line\":%d,\"character\":%d},\"end\":{"
+ "\"line\":%d,\"character\":%d}}}",
+ curr->uri, curr->range->start_line, curr->range->start_col,
+ curr->range->end_line, curr->range->end_col);
+ first = 0;
+
+ ReferenceResult *next = curr->next;
+ free(curr); // Free linked list node as we go
+ curr = next;
+ }
+
+ p += sprintf(p, "]}");
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json);
+ fflush(stdout);
+ free(json);
+}
+
+void lsp_signature_help(const char *uri, int line, int col)
+{
+ ProjectFile *pf = lsp_project_get_file(uri);
+ if (!g_project || !g_project->ctx || !pf || !pf->source)
+ {
+ return;
+ }
+
+ // Scan backwards from cursor for '('
+ char *ptr = pf->source;
+ int cur_line = 0;
+ while (*ptr && cur_line < line)
+ {
+ if (*ptr == '\n')
+ {
+ cur_line++;
+ }
+ ptr++;
+ }
+
+ // We are at start of line. Advance to col.
+ if (ptr && col > 0)
+ {
+ ptr += col;
+ }
+
+ // Safety check
+ if (ptr > pf->source + strlen(pf->source))
+ {
+ return;
+ }
+
+ // Scan backwards
+ char *p = ptr - 1;
+ while (p >= pf->source)
+ {
+ if (*p == ')')
+ {
+ // Nested call or closed. Bail for simple implementation.
+ // Or skip balanced parens (TODO for better robustness)
+ return;
+ }
+ if (*p == '(')
+ {
+ // Found open paren!
+ // Look for identifier before it.
+ char *ident_end = p - 1;
+ while (ident_end >= pf->source && isspace(*ident_end))
+ {
+ ident_end--;
+ }
+
+ if (ident_end < pf->source)
+ {
+ return;
+ }
+
+ char *ident_start = ident_end;
+ while (ident_start >= pf->source && (isalnum(*ident_start) || *ident_start == '_'))
+ {
+ ident_start--;
+ }
+ ident_start++;
+
+ // Extract name
+ int len = ident_end - ident_start + 1;
+ if (len <= 0 || len > 255)
+ {
+ return;
+ }
+
+ char func_name[256];
+ strncpy(func_name, ident_start, len);
+ func_name[len] = 0;
+
+ // Lookup Function
+ FuncSig *fn = g_project->ctx->func_registry;
+ while (fn)
+ {
+ if (strcmp(fn->name, func_name) == 0)
+ {
+ char *json = malloc(4096);
+ char label[2048];
+ // Reconstruct signature label
+ char params[1024] = "";
+ int first = 1;
+
+ // Use total_args and arg_types
+ for (int i = 0; i < fn->total_args; i++)
+ {
+ if (!first)
+ {
+ strcat(params, ", ");
+ }
+
+ // Convert Type* to string
+ char *tstr = type_to_string(fn->arg_types[i]);
+ if (tstr)
+ {
+ strcat(params, tstr);
+ free(tstr);
+ }
+ else
+ {
+ strcat(params, "unknown");
+ }
+
+ first = 0;
+ }
+
+ char *ret_str = type_to_string(fn->ret_type);
+ sprintf(label, "fn %s(%s) -> %s", fn->name, params, ret_str ? ret_str : "void");
+ if (ret_str)
+ {
+ free(ret_str);
+ }
+
+ sprintf(json,
+ "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"signatures\":[{\"label\":"
+ "\"%s\",\"parameters\":[]}]}}",
+ label);
+
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json);
+ fflush(stdout);
+ free(json);
+ return;
+ }
+ fn = fn->next;
+ }
+ break; // Found paren but no func match
+ }
+ p--;
+ }
+
+ const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}";
+ fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp);
+ fflush(stdout);
+}
diff --git a/src/lsp/lsp_index.c b/src/lsp/lsp_index.c
index d952b77..285dec3 100644
--- a/src/lsp/lsp_index.c
+++ b/src/lsp/lsp_index.c
@@ -148,6 +148,12 @@ void lsp_walk_node(LSPIndex *idx, ASTNode *node)
lsp_walk_node(idx, node->var_decl.init_expr);
}
+ else if (node->type == NODE_STRUCT)
+ {
+ char hover[256];
+ sprintf(hover, "struct %s", node->strct.name);
+ lsp_index_add_def(idx, node->token, hover, node);
+ }
// Reference logic.
if (node->definition_token.line > 0 && node->definition_token.line != node->token.line)
diff --git a/src/lsp/lsp_project.c b/src/lsp/lsp_project.c
new file mode 100644
index 0000000..0af3acc
--- /dev/null
+++ b/src/lsp/lsp_project.c
@@ -0,0 +1,299 @@
+#include "lsp_project.h"
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+LSPProject *g_project = NULL;
+
+static void scan_dir(const char *dir_path);
+
+void lsp_project_init(const char *root_path)
+{
+ if (g_project)
+ {
+ return;
+ }
+
+ fprintf(stderr, "zls: Initializing project at %s\n", root_path);
+
+ g_project = xcalloc(1, sizeof(LSPProject));
+ g_project->root_path = xstrdup(root_path);
+
+ // Create a persistent global context
+ g_project->ctx = xcalloc(1, sizeof(ParserContext));
+ g_project->ctx->is_fault_tolerant = 1;
+
+ // Scan workspace
+ scan_dir(root_path);
+}
+
+static void scan_file(const char *path)
+{
+ // Skip if not .zc
+ const char *ext = strrchr(path, '.');
+ if (!ext || strcmp(ext, ".zc") != 0)
+ {
+ return;
+ }
+
+ fprintf(stderr, "zls: Indexing %s\n", path);
+
+ char *src = load_file(path);
+ if (!src)
+ {
+ return;
+ }
+
+ char uri[2048];
+ sprintf(uri, "file://%s", path);
+
+ lsp_project_update_file(uri, src);
+ free(src);
+}
+
+static void scan_dir(const char *dir_path)
+{
+ DIR *d = opendir(dir_path);
+ if (!d)
+ {
+ return;
+ }
+
+ struct dirent *dir;
+ while ((dir = readdir(d)) != NULL)
+ {
+ if (dir->d_name[0] == '.')
+ {
+ continue;
+ }
+
+ char path[1024];
+ snprintf(path, sizeof(path), "%s/%s", dir_path, dir->d_name);
+
+ struct stat st;
+ if (stat(path, &st) == 0)
+ {
+ if (S_ISDIR(st.st_mode))
+ {
+ scan_dir(path);
+ }
+ else if (S_ISREG(st.st_mode))
+ {
+ scan_file(path);
+ }
+ }
+ }
+ closedir(d);
+}
+
+ProjectFile *lsp_project_get_file(const char *uri)
+{
+ if (!g_project)
+ {
+ return NULL;
+ }
+ ProjectFile *curr = g_project->files;
+ while (curr)
+ {
+ if (strcmp(curr->uri, uri) == 0)
+ {
+ return curr;
+ }
+ curr = curr->next;
+ }
+ return NULL;
+}
+
+static ProjectFile *add_project_file(const char *uri)
+{
+ ProjectFile *f = xcalloc(1, sizeof(ProjectFile));
+ f->uri = xstrdup(uri);
+ // Simple path extraction from URI (file://...)
+ if (strncmp(uri, "file://", 7) == 0)
+ {
+ f->path = xstrdup(uri + 7);
+ }
+ else
+ {
+ f->path = xstrdup(uri);
+ }
+
+ f->next = g_project->files;
+ g_project->files = f;
+ return f;
+}
+
+void lsp_project_update_file(const char *uri, const char *src)
+{
+ if (!g_project)
+ {
+ return;
+ }
+
+ ProjectFile *pf = lsp_project_get_file(uri);
+ if (!pf)
+ {
+ pf = add_project_file(uri);
+ }
+
+ // Clear old index
+ if (pf->index)
+ {
+ lsp_index_free(pf->index);
+ pf->index = NULL;
+ }
+
+ // Update source
+ if (pf->source)
+ {
+ free(pf->source);
+ }
+ pf->source = xstrdup(src);
+
+ // Parse
+ Lexer l;
+ lexer_init(&l, src);
+
+ ASTNode *root = parse_program(g_project->ctx, &l);
+
+ // Build Index
+ pf->index = lsp_index_new();
+ if (root)
+ {
+ lsp_build_index(pf->index, root);
+ }
+}
+
+DefinitionResult lsp_project_find_definition(const char *name)
+{
+ // ... existing implementation ...
+ DefinitionResult res = {0};
+ if (!g_project)
+ {
+ return res;
+ }
+
+ ProjectFile *pf = g_project->files;
+ while (pf)
+ {
+ if (pf->index)
+ {
+ LSPRange *r = pf->index->head;
+ while (r)
+ {
+ if (r->type == RANGE_DEFINITION && r->node)
+ {
+ // Check name match
+ char *found_name = NULL;
+ if (r->node->type == NODE_FUNCTION)
+ {
+ found_name = r->node->func.name;
+ }
+ else if (r->node->type == NODE_VAR_DECL)
+ {
+ found_name = r->node->var_decl.name;
+ }
+ else if (r->node->type == NODE_CONST)
+ {
+ found_name = r->node->var_decl.name;
+ }
+ else if (r->node->type == NODE_STRUCT)
+ {
+ found_name = r->node->strct.name;
+ }
+
+ if (found_name && strcmp(found_name, name) == 0)
+ {
+ res.uri = pf->uri;
+ res.range = r;
+ return res;
+ }
+ }
+ r = r->next;
+ }
+ }
+ pf = pf->next;
+ }
+
+ return res;
+}
+
+// Find all references to a symbol name project-wide
+
+ReferenceResult *lsp_project_find_references(const char *name)
+{
+ if (!g_project)
+ {
+ return NULL;
+ }
+ ReferenceResult *head = NULL;
+ ReferenceResult *tail = NULL;
+
+ ProjectFile *pf = g_project->files;
+ while (pf)
+ {
+ if (pf->index)
+ {
+ LSPRange *r = pf->index->head;
+ while (r)
+ {
+ // We want REFERENCES that match the name
+ // Or DEFINITIONS that match the name (include decl)
+ char *scan_name = NULL;
+
+ if (r->node)
+ {
+ if (r->node->type == NODE_FUNCTION)
+ {
+ scan_name = r->node->func.name;
+ }
+ else if (r->node->type == NODE_VAR_DECL)
+ {
+ scan_name = r->node->var_decl.name;
+ }
+ else if (r->node->type == NODE_CONST)
+ {
+ scan_name = r->node->var_decl.name;
+ }
+ else if (r->node->type == NODE_STRUCT)
+ {
+ scan_name = r->node->strct.name;
+ }
+ else if (r->node->type == NODE_EXPR_VAR)
+ {
+ scan_name = r->node->var_ref.name;
+ }
+ else if (r->node->type == NODE_EXPR_CALL &&
+ r->node->call.callee->type == NODE_EXPR_VAR)
+ {
+ scan_name = r->node->call.callee->var_ref.name;
+ }
+ }
+
+ if (scan_name && strcmp(scan_name, name) == 0)
+ {
+ ReferenceResult *new_res = calloc(1, sizeof(ReferenceResult));
+ new_res->uri = pf->uri;
+ new_res->range = r;
+
+ if (!head)
+ {
+ head = new_res;
+ tail = new_res;
+ }
+ else
+ {
+ tail->next = new_res;
+ tail = new_res;
+ }
+ }
+
+ r = r->next;
+ }
+ }
+ pf = pf->next;
+ }
+ return head;
+}
diff --git a/src/lsp/lsp_project.h b/src/lsp/lsp_project.h
new file mode 100644
index 0000000..9b4fe42
--- /dev/null
+++ b/src/lsp/lsp_project.h
@@ -0,0 +1,59 @@
+#ifndef LSP_PROJECT_H
+#define LSP_PROJECT_H
+
+#include "parser.h"
+#include "lsp_index.h"
+
+typedef struct ProjectFile
+{
+ char *path; // Absolute path
+ char *uri; // file:// URI
+ char *source; // Cached source content
+ LSPIndex *index; // File-specific index (local vars, refs)
+ struct ProjectFile *next;
+} ProjectFile;
+
+typedef struct
+{
+ // Global symbol table (Structs, Functions, Globals)
+ // We reuse ParserContext for this, as it already supports registries.
+ ParserContext *ctx;
+
+ // List of tracked files
+ ProjectFile *files;
+
+ // Root directory
+ char *root_path;
+} LSPProject;
+
+// Global project instance
+extern LSPProject *g_project;
+
+// Initialize the project with a root directory
+void lsp_project_init(const char *root_path);
+
+// Find a file in the project
+ProjectFile *lsp_project_get_file(const char *uri);
+
+// Update a file (re-parse and re-index)
+void lsp_project_update_file(const char *uri, const char *src);
+
+// Find definition globally
+typedef struct
+{
+ char *uri;
+ LSPRange *range;
+} DefinitionResult;
+
+DefinitionResult lsp_project_find_definition(const char *name);
+
+typedef struct ReferenceResult
+{
+ char *uri;
+ LSPRange *range;
+ struct ReferenceResult *next;
+} ReferenceResult;
+
+ReferenceResult *lsp_project_find_references(const char *name);
+
+#endif
diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c
index 7433525..c5efe8a 100644
--- a/src/parser/parser_stmt.c
+++ b/src/parser/parser_stmt.c
@@ -335,6 +335,14 @@ ASTNode *parse_match(ParserContext *ctx, Lexer *l)
zpanic_at(lexer_peek(l), "Expected =>");
}
+ // Create scope for the case to hold the binding
+ enter_scope(ctx);
+ if (binding)
+ {
+ // If ref binding, mark as pointer type so auto-deref (->) works
+ add_symbol(ctx, binding, is_ref ? "void*" : "unknown", NULL);
+ }
+
ASTNode *body;
Token pk = lexer_peek(l);
if (pk.type == TOK_LBRACE)
@@ -355,6 +363,8 @@ ASTNode *parse_match(ParserContext *ctx, Lexer *l)
body = parse_expression(ctx, l);
}
+ exit_scope(ctx);
+
ASTNode *c = ast_create(NODE_MATCH_CASE);
c->match_case.pattern = pattern;
c->match_case.binding_name = binding;
diff --git a/tests/features/test_match_ref.zc b/tests/features/test_match_ref.zc
index 9734ffe..4e04b89 100644
--- a/tests/features/test_match_ref.zc
+++ b/tests/features/test_match_ref.zc
@@ -15,3 +15,28 @@ test "match_ref_int" {
MyOption::None => assert(false, "fail")
}
}
+// Mover struct to test move semantics vs ref
+struct Mover {
+ val: int;
+}
+
+test "match_ref_mover" {
+ var m = Mover { val: 100 };
+ // MyOption<Mover> instantiation
+ var opt = MyOption<Mover>::Some(m);
+
+ match opt {
+ MyOption::Some(ref x) => {
+ // x should be Mover*
+ assert(x.val == 100, "Mover ref access failed");
+ x.val = 200;
+ },
+ MyOption::None => assert(false, "Should be Some")
+ }
+
+ // Check if modification persisted
+ match opt {
+ MyOption::Some(v) => assert(v.val == 200, "Mutation via ref failed"),
+ MyOption::None => {}
+ }
+}