about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-30 23:04:48 +0200
committerMel <mel@rnrd.eu>2026-05-30 23:04:48 +0200
commitdd9f8fccf405af709c1d655dbec4be1561e990c0 (patch)
tree5f05eff13d01df0e1bd365e0c31a9b4b69284bdc
parent4ec376b67d605910cb7757dc2e15a28a942f59ca (diff)
downloadcatskill-dd9f8fccf405af709c1d655dbec4be1561e990c0.tar.zst
catskill-dd9f8fccf405af709c1d655dbec4be1561e990c0.zip
Define name resolution behavior, clean-up langauge object resolution
Signed-off-by: Mel <mel@rnrd.eu>
-rw-r--r--boot/ir.c12
-rw-r--r--boot/lower.c215
-rw-r--r--boot/transpile.c4
-rw-r--r--boot/visit/ir.c2
4 files changed, 135 insertions, 98 deletions
diff --git a/boot/ir.c b/boot/ir.c
index af631f8..f1ae951 100644
--- a/boot/ir.c
+++ b/boot/ir.c
@@ -589,6 +589,18 @@ struct Unit
     Array(struct Lower_Error) lower_errors;
 };
 
+struct Type*
+unit_get_type(struct Unit* unit, Type_Id id)
+{
+    return *array_at(struct Type*, &unit->types.entries, id);
+}
+
+struct Function*
+unit_get_function(struct Unit* unit, Function_Id id)
+{
+    return *array_at(struct Function*, &unit->functions.entries, id);
+}
+
 REGION(struct Type, type)
 REGION(struct Function, function)
 REGION(struct Statement, statement)
diff --git a/boot/lower.c b/boot/lower.c
index 4d00c5d..df5fd45 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -45,6 +45,24 @@ struct Lower_Local_Lookup_Capture
     struct Function* owner;
 };
 
+enum Name_Resolution_Kind
+{
+    NAME_UNRESOLVED,
+    NAME_LOCAL,
+    NAME_CAPTURE,
+    NAME_FUNCTION,
+};
+
+// the result of a name lookup within a scope.
+struct Name_Resolution
+{
+    enum Name_Resolution_Kind kind;
+    struct Type_Ref type;
+    // depending on type, either the resolved function, or when
+    // resolved to a capture, the function the captured local belongs.
+    struct Function* function;
+};
+
 // what kind of scope is this?
 // declared on every lexical scope so we can make sure that the scopes are balanced.
 enum Scope_Type
@@ -120,8 +138,8 @@ lower_format_cycle_chain(
     for (uint k = 0; k < n; ++k) {
         Type_Id current_id = *array_at(Type_Id, chain, k);
         Type_Id next_id = *array_at(Type_Id, chain, (k + 1) % n);
-        struct Type* current = *array_at(struct Type*, &unit->types.entries, current_id);
-        struct Type* next = *array_at(struct Type*, &unit->types.entries, next_id);
+        struct Type* current = unit_get_type(unit, current_id);
+        struct Type* next = unit_get_type(unit, next_id);
         uint line, column;
         source_position_from_span(source.source, current->span, &line, &column);
 
@@ -203,28 +221,22 @@ lower_error_to_diagnostic(struct Lower_Error* err, struct Unit* unit, struct Sou
     }
 }
 
-bool
-lower_type_lookup_by_name(struct Unit* unit, struct String name, Type_Id* out_id)
+struct Type*
+lower_type_lookup_by_name(struct Unit* unit, struct String name)
 {
     FOR_EACH_ARRAY (struct Type_Name_To_Id, mapping, &unit->types.by_name) {
-        if (string_equals(mapping->name, name)) {
-            *out_id = mapping->id;
-            return true;
-        }
+        if (string_equals(mapping->name, name)) return unit_get_type(unit, mapping->id);
     }
-    return false;
+    return nil;
 }
 
-bool
-lower_function_lookup_by_name(struct Unit* unit, struct String name, Function_Id* out_id)
+struct Function*
+lower_function_lookup_by_name(struct Unit* unit, struct String name)
 {
     FOR_EACH_ARRAY (struct Function_Name_To_Id, mapping, &unit->functions.by_name) {
-        if (string_equals(mapping->name, name)) {
-            *out_id = mapping->id;
-            return true;
-        }
+        if (string_equals(mapping->name, name)) return unit_get_function(unit, mapping->id);
     }
-    return false;
+    return nil;
 }
 
 // registers a basic type object for a primitive, so other types
@@ -512,9 +524,9 @@ lower_intern_type_ref(struct Lower_Context* ctx, struct Tree_Type* tree_type)
 
     switch (current->type) {
     case TREE_TYPE_NAME: {
-        Type_Id id;
-        if (lower_type_lookup_by_name(ctx->unit, current->value.name.name, &id)) {
-            ref.type_id = id;
+        struct Type* type = lower_type_lookup_by_name(ctx->unit, current->value.name.name);
+        if (type) {
+            ref.type_id = type->id;
         } else {
             lower_push_error(
                 ctx->unit,
@@ -600,16 +612,16 @@ lower_match_type_decl(
 }
 
 // register a function shell object, only listing a name and assigning a unique identifier.
-bool
+// returns the freshly created function, or nil if a same-named entry already exists.
+struct Function*
 lower_register_function_shell(struct Unit* unit, struct String name, struct Span span)
 {
-    Function_Id existing;
-    if (lower_function_lookup_by_name(unit, name, &existing)) {
+    if (lower_function_lookup_by_name(unit, name)) {
         lower_push_error(
             unit,
             (struct Lower_Error){
                 .kind = LOWER_ERROR_DUPLICATE_FUNCTION, .span = span, .name = name });
-        return false;
+        return nil;
     }
 
     Function_Id id = array_length(&unit->functions.entries);
@@ -618,7 +630,7 @@ lower_register_function_shell(struct Unit* unit, struct String name, struct Span
 
     struct Function_Name_To_Id mapping = { .name = name, .id = id };
     array_push(&unit->functions.by_name, &mapping);
-    return true;
+    return fn;
 }
 
 // register a type shell object, only listing a name and assigning a unique identifier.
@@ -626,8 +638,7 @@ lower_register_function_shell(struct Unit* unit, struct String name, struct Span
 bool
 lower_register_type_shell(struct Unit* unit, struct String name, struct Span span)
 {
-    Type_Id existing;
-    if (lower_type_lookup_by_name(unit, name, &existing)) {
+    if (lower_type_lookup_by_name(unit, name)) {
         lower_push_error(
             unit,
             (struct Lower_Error){ .kind = LOWER_ERROR_DUPLICATE_TYPE, .span = span, .name = name });
@@ -734,11 +745,11 @@ lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_T
     switch (tree_type->type) {
     case TREE_TYPE_NAME: {
         type->kind = TYPE_ALIAS;
-        Type_Id target_id;
-        if (lower_type_lookup_by_name(ctx->unit, tree_type->value.name.name, &target_id)) {
-            type->value.alias.target_id = target_id;
+        struct Type* target = lower_type_lookup_by_name(ctx->unit, tree_type->value.name.name);
+        if (target) {
+            type->value.alias.target_id = target->id;
             // aliases always need their target's full definition.
-            array_push(&type->depends_on, &target_id);
+            array_push(&type->depends_on, &target->id);
         } else {
             lower_push_error(
                 ctx->unit,
@@ -856,22 +867,15 @@ lower_pass_1_fill_bodies(struct Lower_Context* ctx, struct Tree* tree)
 
         struct Tree_Expression* fn_expr;
         if (lower_match_function_decl(stmt, &name, &fn_expr)) {
-            Function_Id id;
-            if (lower_function_lookup_by_name(ctx->unit, name, &id)) {
-                struct Function* fn =
-                    *array_at(struct Function*, &ctx->unit->functions.entries, id);
-                if (!fn->ast_body) lower_fill_function_signature(ctx, fn, fn_expr);
-            }
+            struct Function* fn = lower_function_lookup_by_name(ctx->unit, name);
+            if (fn && !fn->ast_body) lower_fill_function_signature(ctx, fn, fn_expr);
             continue;
         }
 
         struct Tree_Type* tree_type;
         if (lower_match_type_decl(stmt, &name, &tree_type)) {
-            Type_Id id;
-            if (lower_type_lookup_by_name(ctx->unit, name, &id)) {
-                struct Type* type = *array_at(struct Type*, &ctx->unit->types.entries, id);
-                if (type->kind == TYPE_NONE) lower_fill_type_body(ctx, type, tree_type);
-            }
+            struct Type* type = lower_type_lookup_by_name(ctx->unit, name);
+            if (type && type->kind == TYPE_NONE) lower_fill_type_body(ctx, type, tree_type);
             continue;
         }
 
@@ -917,7 +921,6 @@ lower_pass_1(struct Lower_Context* ctx, struct Tree* tree)
 struct Block* lower_block(struct Lower_Context* ctx, struct Tree_Block* tree_block);
 struct Statement* lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt);
 struct Expression* lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr);
-bool lower_name_is_function_typed_local(struct Lower_Context* ctx, struct String name);
 struct Lower_Local_Lookup_Capture
 lower_local_lookup_capturing(struct Lower_Context* ctx, struct String name);
 void lower_record_capture(struct Function* fn, struct String name, struct Type_Ref type);
@@ -925,6 +928,7 @@ void lower_push_scope_function(struct Lower_Context* ctx, struct Function* owner
 void lower_pop_scope(struct Lower_Context* ctx, enum Scope_Type expected);
 void lower_declare_local(struct Lower_Context* ctx, struct String name, struct Type_Ref type);
 struct Function* lower_current_function(struct Lower_Context* ctx);
+struct Name_Resolution lower_resolve_name(struct Lower_Context* ctx, struct String name);
 
 struct Expression*
 lower_expression_integer_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
@@ -959,34 +963,37 @@ lower_expression_name(struct Lower_Context* ctx, struct Tree_Expression* tree_ex
 {
     struct String name = tree_expr->value.name.name;
 
-    // names that resolve to a catskill function become a function reference.
-    Function_Id fn_id;
-    if (lower_function_lookup_by_name(ctx->unit, name, &fn_id)) {
-        // since this path is only taken outside direct call-positions,
-        // the function will need a closure type to wrap it as a value.
-        struct Function* fn = *array_at(struct Function*, &ctx->unit->functions.entries, fn_id);
-        Type_Id closure_type_id = lower_function_closure_type(ctx, fn);
-        return ir_make_function_ref(fn_id, closure_type_id, tree_expr->span);
-    }
-
+    struct Name_Resolution resolved = lower_resolve_name(ctx, name);
+    switch (resolved.kind) {
     // locals that were defined in an upper function scope are captured.
-    struct Lower_Local_Lookup_Capture lookup = lower_local_lookup_capturing(ctx, name);
-    if (lookup.captured) {
+    case NAME_CAPTURE: {
         struct Function* fn = lower_current_function(ctx);
         // we walk the enclosing function chain to record every in-between capture.
-        while (fn && fn != lookup.owner) {
-            lower_record_capture(fn, name, lookup.type);
+        while (fn && fn != resolved.function) {
+            lower_record_capture(fn, name, resolved.type);
             fn = fn->parent_function;
         }
         return ir_make_capture_ref(name, tree_expr->span);
     }
-
-    // unresolved names will continue being bare names.
-    // this is required to account for all non-language objects that might
-    // come from c, or the runtime, or some c-header include we can't see.
-    // the backend will note any name reference which is still unresolved
-    // after taking every source into account.
-    return ir_make_name(name, tree_expr->span);
+    // names that resolve to a catskill function become a function reference.
+    case NAME_FUNCTION: {
+        // since this path is only taken outside direct call-positions,
+        // the function will need a closure type to wrap it as a value.
+        Type_Id closure_type_id = lower_function_closure_type(ctx, resolved.function);
+        return ir_make_function_ref(resolved.function->id, closure_type_id, tree_expr->span);
+    }
+
+    case NAME_LOCAL:
+    case NAME_UNRESOLVED:
+        // unresolved names will continue being bare names.
+        // this is required to account for all non-language objects that might
+        // come from c, or the runtime, or some c-header include we can't see.
+        // the backend will note any name reference which is still unresolved
+        // after taking every source into account.
+        return ir_make_name(name, tree_expr->span);
+    default:
+        failure("unexpected name resolution kind in `lower_expression_name`");
+    }
 }
 
 // any groups are discarded in the lowered representation, their presence
@@ -1208,13 +1215,23 @@ lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_ex
     // only catskill-side functions are in the function table, calls to libc
     // and the like go through as positional with no named args allowed.
     struct Function* callee = nil;
-    Function_Id callee_id = 0;
     bool is_indirect = false;
     if (subject_tree && subject_tree->kind == TREE_EXPRESSION_NAME) {
-        if (lower_function_lookup_by_name(ctx->unit, subject_tree->value.name.name, &callee_id))
-            callee = *array_at(struct Function*, &ctx->unit->functions.entries, callee_id);
-        else if (lower_name_is_function_typed_local(ctx, subject_tree->value.name.name))
-            is_indirect = true;
+        struct Name_Resolution resolved = lower_resolve_name(ctx, subject_tree->value.name.name);
+        switch (resolved.kind) {
+        case NAME_LOCAL:
+        case NAME_CAPTURE: {
+            struct Type* t = unit_get_type(ctx->unit, resolved.type.type_id);
+            if (array_length(&resolved.type.mods) == 0 && t->kind == TYPE_FUNCTION)
+                is_indirect = true;
+            break;
+        }
+        case NAME_FUNCTION:
+            callee = resolved.function;
+            break;
+        case NAME_UNRESOLVED:
+            break;
+        }
     } else if (subject_tree) {
         // any subject that isn't a bare name (member, subscript, call result, closure, anything
         // that's not literally just a name) yields a fat closure value at the call site by default.
@@ -1224,7 +1241,7 @@ lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_ex
     // for direct calls to a known catskill function, build the reference
     // subject directly so we don't drag the function into value-context synthesis!
     struct Expression* subject =
-        callee ? ir_make_function_ref(callee_id, 0, subject_tree->span)
+        callee ? ir_make_function_ref(callee->id, 0, subject_tree->span)
                : lower_expression(ctx, subject_tree);
     Array(struct Call_Argument) arguments;
 
@@ -1269,8 +1286,8 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
             });
         return nil;
     }
-    Type_Id type_id;
-    if (!lower_type_lookup_by_name(ctx->unit, subject->value.name.name, &type_id)) {
+    struct Type* type = lower_type_lookup_by_name(ctx->unit, subject->value.name.name);
+    if (!type) {
         lower_push_error(
             ctx->unit,
             (struct Lower_Error){
@@ -1280,8 +1297,6 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
             });
         return nil;
     }
-
-    struct Type* type = *array_at(struct Type*, &ctx->unit->types.entries, type_id);
     Array(struct String) field_names = array_new(struct String, 16);
     if (type->kind == TYPE_STRUCTURE) {
         FOR_EACH_ARRAY (struct Field, f, &type->value.structure.fields)
@@ -1292,7 +1307,7 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
     lower_resolve_construct_fields(
         ctx, tree_expr->span, &tree_expr->value.construct.argument_group, field_names, &fields);
 
-    return ir_make_construct(type_id, fields, tree_expr->span);
+    return ir_make_construct(type->id, fields, tree_expr->span);
 }
 
 // type-as-expression is only valid as the right-side of a top-level binding,
@@ -1317,12 +1332,9 @@ lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tre
 
     // NOTE: we are allowed to register functions whenever we want,
     // even while iterating over them, the pass will eventually get to them.
-    if (!lower_register_function_shell(ctx->unit, name, tree_expr->span)) return nil;
-
-    Function_Id id;
-    lower_function_lookup_by_name(ctx->unit, name, &id);
+    struct Function* fn = lower_register_function_shell(ctx->unit, name, tree_expr->span);
+    if (!fn) return nil;
 
-    struct Function* fn = *array_at(struct Function*, &ctx->unit->functions.entries, id);
     fn->synthetic = true;
     fn->parent_function = lower_current_function(ctx);
     lower_fill_function_signature(ctx, fn, tree_expr);
@@ -1343,7 +1355,7 @@ lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tre
     // if the body captured anything, also synthesize the state type.
     lower_function_capture_state_type(ctx, fn);
 
-    return ir_make_function_ref(id, closure_type_id, tree_expr->span);
+    return ir_make_function_ref(fn->id, closure_type_id, tree_expr->span);
 }
 
 struct Expression*
@@ -1357,7 +1369,9 @@ lower_expression_increment_decrement(struct Lower_Context* ctx, struct Tree_Expr
             (struct Lower_Error){
                 .kind = LOWER_ERROR_UNIMPLEMENTED,
                 .span = tree_expr->span,
-                .detail = string_from_static_c_string("increment/decrement on non-trivial lvalue"),
+                .detail = string_from_static_c_string(
+                    "increment/decrement on non-trivial "
+                    "lvalue"),
             });
         return nil;
     }
@@ -1551,16 +1565,25 @@ lower_record_capture(struct Function* fn, struct String name, struct Type_Ref ty
     array_push(&fn->captures, &cap);
 }
 
-// is the named local a function-typed value?
-// (used to decide direct vs. indirect call dispatch when the call subject is a bare name.)
-bool
-lower_name_is_function_typed_local(struct Lower_Context* ctx, struct String name)
+// resolve a name from the current scope context.
+struct Name_Resolution
+lower_resolve_name(struct Lower_Context* ctx, struct String name)
 {
-    struct Type_Ref ref;
-    if (!lower_local_lookup(ctx, name, &ref)) return false;
-    if (array_length(&ref.mods) > 0) return false; // references are not callable!
-    struct Type* type = *array_at(struct Type*, &ctx->unit->types.entries, ref.type_id);
-    return type->kind == TYPE_FUNCTION;
+    struct Lower_Local_Lookup_Capture lookup = lower_local_lookup_capturing(ctx, name);
+    if (lookup.owner) {
+        return (struct Name_Resolution){
+            .kind = lookup.captured ? NAME_CAPTURE : NAME_LOCAL,
+            .type = lookup.type,
+            .function = lookup.owner,
+        };
+    }
+
+    // by default functions can be shadowed by locals, hence why
+    // this function lookup is done after the local one.
+    struct Function* fn = lower_function_lookup_by_name(ctx->unit, name);
+    if (fn) return (struct Name_Resolution){ .kind = NAME_FUNCTION, .function = fn };
+
+    return (struct Name_Resolution){ .kind = NAME_UNRESOLVED };
 }
 
 struct Statement*
@@ -1744,7 +1767,9 @@ lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt
                 (struct Lower_Error){
                     .kind = LOWER_ERROR_UNIMPLEMENTED,
                     .span = tree_stmt->span,
-                    .detail = string_from_static_c_string("for-loop with multi-name declaration"),
+                    .detail = string_from_static_c_string(
+                        "for-loop with multi-name "
+                        "declaration"),
                 });
             return nil;
         }
@@ -1941,7 +1966,7 @@ lower_report_type_cycle(struct Unit* unit, struct _Array* chain, uint cycle_star
     }
 
     Type_Id origin_id = *array_at(Type_Id, chain, cycle_start);
-    struct Type* origin = *array_at(struct Type*, &unit->types.entries, origin_id);
+    struct Type* origin = unit_get_type(unit, origin_id);
     lower_push_error(
         unit,
         (struct Lower_Error){
@@ -1968,7 +1993,7 @@ lower_topological_sort_dependency_graph(struct Unit* unit)
     // for i, holds in-degree for type i.
     Array(uint) in_degree = array_new(uint, n);
     for (uint i = 0; i < n; ++i) {
-        struct Type* type = *array_at(struct Type*, &unit->types.entries, i);
+        struct Type* type = unit_get_type(unit, i);
         uint deg = array_length(&type->depends_on);
         array_push(&in_degree, &deg);
     }
@@ -1980,7 +2005,7 @@ lower_topological_sort_dependency_graph(struct Unit* unit)
         array_push(&reverse_deps, &slot);
     }
     for (uint i = 0; i < n; ++i) {
-        struct Type* type = *array_at(struct Type*, &unit->types.entries, i);
+        struct Type* type = unit_get_type(unit, i);
         Type_Id me = (Type_Id)i;
         FOR_EACH_ARRAY (Type_Id, dep, &type->depends_on) {
             struct _Array* slot = array_at(struct _Array, &reverse_deps, *dep);
@@ -2029,7 +2054,7 @@ lower_topological_sort_dependency_graph(struct Unit* unit)
     Type_Id current = start;
 
     while (true) {
-        struct Type* type = *array_at(struct Type*, &unit->types.entries, current);
+        struct Type* type = unit_get_type(unit, current);
 
         Type_Id next = (Type_Id)-1;
         FOR_EACH_ARRAY (Type_Id, dep, &type->depends_on) {
diff --git a/boot/transpile.c b/boot/transpile.c
index e0af3bf..af601f0 100644
--- a/boot/transpile.c
+++ b/boot/transpile.c
@@ -115,7 +115,7 @@ struct Type*
 transpile_type_at(struct Transpiler* transpiler, Type_Id id)
 {
     if (id >= array_length(&transpiler->unit->types.entries)) return nil;
-    return *array_at(struct Type*, &transpiler->unit->types.entries, id);
+    return unit_get_type(transpiler->unit, id);
 }
 
 // the c-side form of a primitive catskill type.
@@ -946,7 +946,7 @@ transpile_unit(struct Transpiler* transpiler, struct Unit* unit)
     // pass 2: type definitions
     visit->table = &transpile_definition_visit_functions;
     FOR_EACH_ARRAY (Type_Id, ordered_id, &unit->type_emission_order) {
-        struct Type* type = *array_at(struct Type*, &unit->types.entries, *ordered_id);
+        struct Type* type = unit_get_type(unit, *ordered_id);
         VISIT(visit_type, type);
     }
 
diff --git a/boot/visit/ir.c b/boot/visit/ir.c
index ca32a1e..8a0297b 100644
--- a/boot/visit/ir.c
+++ b/boot/visit/ir.c
@@ -467,7 +467,7 @@ struct Type*
 printer_type_at(struct Printer* p, Type_Id id)
 {
     if (id >= array_length(&p->unit->types.entries)) return nil;
-    return *array_at(struct Type*, &p->unit->types.entries, id);
+    return unit_get_type(p->unit, id);
 }
 
 void