about summary refs log tree commit diff
path: root/boot/lower.c
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-26 00:50:09 +0200
committerMel <mel@rnrd.eu>2026-05-26 00:50:09 +0200
commit900b412b052a0f745f0853d6703e4bafbb0a3ce5 (patch)
treee04b65b395d4761e5b88fa1e9a792c3996ff3dc1 /boot/lower.c
parente8400cc704eeb74f3c464f7a0822c556cb52e3fd (diff)
downloadcatskill-900b412b052a0f745f0853d6703e4bafbb0a3ce5.tar.zst
catskill-900b412b052a0f745f0853d6703e4bafbb0a3ce5.zip
Lambda function emission, thin/fat calling conventions for (future) closed-over state
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/lower.c')
-rw-r--r--boot/lower.c97
1 files changed, 85 insertions, 12 deletions
diff --git a/boot/lower.c b/boot/lower.c
index 696f3e4..ba83b39 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -644,10 +644,30 @@ lower_fill_function_signature(
     }
     fn->variadic = variadic;
     fn->main_takes_args = fn->is_main && array_length(&fn->params) > 0;
+    fn->ast_header = &fn_expr->value.function.header;
     fn->ast_body = &fn_expr->value.function.body;
     fn->body = nil;
 }
 
+// synthesize the function's signature as a structural closure type
+// the first time the function is referenced in value context, or reuse already
+// synthesized type.
+Type_Id
+lower_function_closure_type(struct Lower_Context* ctx, struct Function* fn)
+{
+    if (fn->closure_type_id != 0) return fn->closure_type_id;
+
+    struct Tree_Type wrapper = {
+        .type = TREE_TYPE_FUNCTION,
+        .value = { .function = { .header = *fn->ast_header } },
+        .span = fn->ast_header->span,
+        .location = fn->ast_header->location,
+    };
+    struct Type_Ref ref = lower_intern_type_ref(ctx, &wrapper);
+    fn->closure_type_id = ref.type_id;
+    return ref.type_id;
+}
+
 void
 lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_Type* tree_type)
 {
@@ -837,6 +857,7 @@ 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 Expression*
 lower_expression_integer_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
@@ -869,7 +890,15 @@ lower_expression_boolean_literal(struct Lower_Context* ctx, struct Tree_Expressi
 struct Expression*
 lower_expression_name(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
 {
-    (void)ctx;
+    // names that resolve to a catskill function become a function reference.
+    Function_Id fn_id;
+    if (lower_function_lookup_by_name(ctx->unit, tree_expr->value.name.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);
+    }
     return ir_make_name(tree_expr->value.name.name, tree_expr->span);
 }
 
@@ -1092,13 +1121,24 @@ 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) {
-        Function_Id fn_id;
-        if (lower_function_lookup_by_name(ctx->unit, subject_tree->value.name.name, &fn_id))
-            callee = *array_at(struct Function*, &ctx->unit->functions.entries, fn_id);
-    }
-
-    struct Expression* subject = lower_expression(ctx, subject_tree);
+        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;
+    } 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.
+        is_indirect = true;
+    }
+
+    // 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)
+               : lower_expression(ctx, subject_tree);
     Array(struct Call_Argument) arguments;
 
     if (callee) {
@@ -1126,7 +1166,7 @@ lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_ex
         }
     }
 
-    return ir_make_call(subject, arguments, tree_expr->span);
+    return ir_make_call(subject, arguments, is_indirect, tree_expr->span);
 }
 
 struct Expression*
@@ -1181,9 +1221,8 @@ lower_expression_type(struct Lower_Context* ctx, struct Tree_Expression* tree_ex
 }
 
 // anonymous function: lift it up as a synthetic function table entry,
-// replace the expression with a name reference.
-// TODO: should we have a better kind of reference here than a name?
-// maybe a function reference somehow?
+// and emit a function reference so the use site can wrap it as a fat
+// closure value.
 struct Expression*
 lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
 {
@@ -1200,7 +1239,10 @@ lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tre
     fn->synthetic = true;
     lower_fill_function_signature(ctx, fn, tree_expr);
 
-    return ir_make_name(name, tree_expr->span);
+    // a function literal is always in value context, we can synthesize
+    // the closure type eagerly right here.
+    Type_Id closure_type_id = lower_function_closure_type(ctx, fn);
+    return ir_make_function_ref(id, closure_type_id, tree_expr->span);
 }
 
 struct Expression*
@@ -1326,6 +1368,37 @@ lower_is_shadowing(struct Lower_Context* ctx, struct String name)
     return false;
 }
 
+// look up a local by name across all active scopes, returning its type.
+// returns false if no local with that name exists in scope.
+bool
+lower_local_lookup(struct Lower_Context* ctx, struct String name, struct Type_Ref* out_type)
+{
+    for (uint i = ctx->scope_stack.length; i > 0; --i) {
+        struct Scope* scope = array_at(struct Scope, &ctx->scope_stack, i - 1);
+
+        FOR_EACH_ARRAY (struct Local_Variable, var, &scope->variables) {
+            if (string_equals(var->name, name)) {
+                if (out_type) *out_type = var->type;
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+// 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)
+{
+    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 Statement*
 lower_statement_return(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
 {