commit 056fbe1069c2cb8b811dccd89e6778d5fe2b2b65 (tree)
parent 216b706834ef0a27f43fbe1ea2057184a16e912a
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Thu, 26 Feb 2026 21:19:54 +0000
sema: comptime DECL_REF/FIELD_PTR resolution and CG builtin nav lookup
Infrastructure for comptime evaluation of declaration references:
- DECL_REF/DECL_VAL: in comptime context, resolve import values
(returns root struct type for imports, CG builtin type for
@import("builtin")) and non-import declarations (returns
already-resolved nav values). Non-comptime path loads import
side effects only.
- zirFieldPtr: in comptime, when operand is a struct type (its
type is `type`), look up field in type's namespace. Only returns
already-resolved nav values to avoid creating IP entries out of
order.
- findNavInNamespace: handle CG builtin module (no ZIR) by using
IP string_bytes for nav name comparison instead of ZIR
string_bytes.
- Use resolveImportPath helper in non-comptime DECL_REF path
instead of duplicating path resolution logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
| M | stage0/sema.c | | | 123 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- |
1 file changed, 91 insertions(+), 32 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -2435,19 +2435,26 @@ static InternPoolIndex internNavPtr(
static uint32_t findNavInNamespace(uint32_t ns_idx, const char* name) {
const SemaNamespace* ns = &s_namespaces[ns_idx];
- const Zir* zir = &s_loaded_modules[ns->file_idx].zir;
- if (!s_loaded_modules[ns->file_idx].has_zir)
+ // Nav names come from ZIR string_bytes (normal modules) or IP
+ // string_bytes (compiler-generated builtin module without ZIR).
+ const uint8_t* string_bytes;
+ if (s_loaded_modules[ns->file_idx].has_zir) {
+ string_bytes = s_loaded_modules[ns->file_idx].zir.string_bytes;
+ } else if (s_module_ip) {
+ string_bytes = s_module_ip->string_bytes;
+ } else {
return UINT32_MAX;
+ }
for (uint32_t i = 0; i < ns->pub_nav_count; i++) {
const Nav* nav = ipGetNav(ns->pub_navs[i]);
- const char* nav_name = (const char*)&zir->string_bytes[nav->name];
+ const char* nav_name = (const char*)&string_bytes[nav->name];
if (strcmp(nav_name, name) == 0)
return ns->pub_navs[i];
}
for (uint32_t i = 0; i < ns->priv_nav_count; i++) {
const Nav* nav = ipGetNav(ns->priv_navs[i]);
- const char* nav_name = (const char*)&zir->string_bytes[nav->name];
+ const char* nav_name = (const char*)&string_bytes[nav->name];
if (strcmp(nav_name, name) == 0)
return ns->priv_navs[i];
}
@@ -8999,7 +9006,7 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
uint32_t c_nav
= findNavInNamespace(cc_ns, cc_name);
if (c_nav != UINT32_MAX) {
- ensureNavValUpToDate(c_nav);
+ (void)ensureNavValUpToDate(c_nav);
}
}
}
@@ -9858,6 +9865,28 @@ static AirInstRef zirFieldPtr(Sema* sema, SemaBlock* block, uint32_t inst) {
AirInstRef obj = resolveInst(sema, obj_ref);
+ // Comptime: if the operand IS a struct type (its type is `type`),
+ // look up the field in the type's namespace. This handles patterns
+ // like @import("builtin").target where the import resolves to a
+ // struct type and we need namespace member access.
+ // Only returns already-resolved nav values — does NOT trigger
+ // analyzeNavValC which would create IP entries out of order.
+ if (AIR_REF_IS_IP(obj) && block->is_comptime) {
+ InternPoolIndex obj_ip = AIR_REF_TO_IP(obj);
+ InternPoolIndex obj_type = ipTypeOf(sema->ip, obj_ip);
+ if (obj_type == IP_INDEX_TYPE_TYPE) {
+ uint32_t ns = findNamespaceForType(obj_ip);
+ if (ns != UINT32_MAX) {
+ uint32_t nav = findNavInNamespace(ns, field_name);
+ if (nav != UINT32_MAX) {
+ const Nav* n = ipGetNav(nav);
+ if (n->resolved_type != IP_INDEX_NONE)
+ return AIR_REF_FROM_IP(n->resolved_type);
+ }
+ }
+ }
+ }
+
// Determine the struct type from the pointer operand's type.
// The operand is a pointer-to-struct, so get the pointee type.
TypeIndex ptr_ty = semaTypeOf(sema, obj);
@@ -11532,43 +11561,73 @@ static bool analyzeBodyInner(
continue;
// decl_val / decl_ref: reference to a module-level declaration.
- // Returns the Nav's resolved value when available, void otherwise.
- // When the declaration is an @import, lazily create a struct
- // type for the imported module and recursively process its
- // imports (matching Zig's ensureFileAnalyzed / scanNamespace).
+ // In comptime context, resolves the declaration's value:
+ // for imports, returns the imported module's root struct type;
+ // for other declarations, calls ensureNavValUpToDate.
+ // In non-comptime context, loads imports (side effect) and
+ // returns void (the resolved value is not used).
+ // Ported from Sema.zig zirDeclRef / zirDeclVal.
case ZIR_INST_DECL_VAL:
case ZIR_INST_DECL_REF: {
uint32_t decl_name_idx = sema->code.inst_datas[inst].str_tok.start;
- if (sema->source_dir && decl_name_idx != 0) {
+ InternPoolIndex resolved = IP_INDEX_VOID_VALUE;
+
+ if (decl_name_idx != 0 && sema->file_idx != UINT32_MAX
+ && block->is_comptime) {
+ // Comptime: resolve the declaration value.
+ const char* decl_name
+ = (const char*)&sema->code.string_bytes[decl_name_idx];
+ const char* import_path
+ = findDeclImportPath(sema, decl_name_idx);
+
+ if (import_path) {
+ if (strcmp(import_path, "builtin") == 0) {
+ // @import("builtin") — compiler-generated
+ // builtin module. Look up the nav in the
+ // current file's namespace.
+ uint32_t ns_idx = s_file_namespace[sema->file_idx];
+ uint32_t nav = findNavInNamespace(ns_idx, decl_name);
+ if (nav != UINT32_MAX) {
+ const Nav* n = ipGetNav(nav);
+ if (n->resolved_type != IP_INDEX_NONE)
+ resolved = n->resolved_type;
+ }
+ } else if (sema->source_dir) {
+ char import_full[1024];
+ if (resolveImportPath(sema->source_dir, import_path,
+ import_full, sizeof(import_full))) {
+ uint32_t fid = ensureFileAnalyzedC(import_full);
+ if (fid != UINT32_MAX)
+ resolved = s_file_root_type[fid];
+ }
+ }
+ } else {
+ // Not an import — look up by name in the
+ // file's namespace and return already-resolved
+ // value. Don't call ensureNavValUpToDate to avoid
+ // creating IP entries out of order.
+ uint32_t ns_idx = s_file_namespace[sema->file_idx];
+ uint32_t nav = findNavInNamespace(ns_idx, decl_name);
+ if (nav != UINT32_MAX) {
+ const Nav* n = ipGetNav(nav);
+ if (n->resolved_type != IP_INDEX_NONE)
+ resolved = n->resolved_type;
+ }
+ }
+ } else if (sema->source_dir && decl_name_idx != 0) {
+ // Non-comptime: just load import side effects.
const char* import_path
= findDeclImportPath(sema, decl_name_idx);
if (import_path && strcmp(import_path, "builtin") != 0) {
- // Resolve import path.
char import_full[1024];
- const char* rel = import_path;
- if (rel[0] == '.' && rel[1] == '/')
- rel += 2;
- snprintf(import_full, sizeof(import_full), "%s/%s",
- sema->source_dir, rel);
- {
- FILE* check = fopen(import_full, "r");
- if (!check && sema->module_root
- && import_path[0] != '.'
- && import_path[0] != '/') {
- snprintf(import_full, sizeof(import_full),
- "%s/lib/%s/%s.zig", sema->module_root,
- import_path, import_path);
- }
- if (check)
- fclose(check);
+ if (resolveImportPath(sema->source_dir, import_path,
+ import_full, sizeof(import_full))) {
+ (void)ensureFileAnalyzedC(import_full);
}
- // Use ensureFileAnalyzedC for proper namespace
- // scanning (creates type_struct + Navs).
- (void)ensureFileAnalyzedC(import_full);
}
}
- instMapPut(
- &sema->inst_map, inst, AIR_REF_FROM_IP(IP_INDEX_VOID_VALUE));
+
+ instMapPut(&sema->inst_map, inst, AIR_REF_FROM_IP(resolved));
i++;
continue;
}