aboutsummaryrefslogtreecommitdiff
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.c213
-rw-r--r--boot/transpile.c4
-rw-r--r--boot/visit/ir.c2
4 files changed, 134 insertions, 97 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);
}
+ // 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);
+ }
- // 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);
+ 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