diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-08-24 14:18:22 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-08-24 14:18:22 -0400 |
| commit | be534f5e511a1d3836254b827cdaa2b8c309f5c4 (patch) | |
| tree | 478fc9ede3a1c61220d3b02a2524fb32d53e7300 | |
| parent | 788922fa2437c5b680b57d8adaaac9012fd5aba4 (diff) | |
Constructors and functions with underscore arguments should be allowed to be called, but
only if the underscore arguments are not provided but are implicit from
the default values. Same for constructing structs with private fields.
| -rw-r--r-- | examples/learnxiny.tm | 12 | ||||
| -rw-r--r-- | src/compile.c | 50 | ||||
| -rw-r--r-- | src/environment.c | 28 | ||||
| -rw-r--r-- | src/typecheck.c | 53 | ||||
| -rw-r--r-- | src/typecheck.h | 8 |
5 files changed, 86 insertions, 65 deletions
diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm index 9b9f1017..7b1c74ca 100644 --- a/examples/learnxiny.tm +++ b/examples/learnxiny.tm @@ -182,13 +182,13 @@ func main() # different values using the "@" operator (think: "(a)llocate"). my_arr := @[10, 20, 30] my_arr[1] = 999 - >> my_arr - = @[999, 20, 30] + >> my_arr[] + = [999, 20, 30] # To call a method, you must use ":" and the name of the method: my_arr.sort() - >> my_arr - = @[20, 30, 999] + >> my_arr[] + = [20, 30, 999] # To access the immutable value that resides inside the memory area, you # can use the "[]" operator: @@ -200,8 +200,8 @@ func main() # remain unchanged. snapshot := my_arr[] my_arr.insert(1000) - >> my_arr - = @[20, 30, 999, 1000] + >> my_arr[] + = [20, 30, 999, 1000] >> snapshot = [20, 30, 999] # Internally, this is implemented using copy-on-write, so it's quite diff --git a/src/compile.c b/src/compile.c index 6bb528a0..262608c5 100644 --- a/src/compile.c +++ b/src/compile.c @@ -579,7 +579,7 @@ static Text_t compile_binary_op(env_t *env, ast_t *ast) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, rhs_t)) { arg_ast_t *args = new (arg_ast_t, .value = binop.rhs, .next = new (arg_ast_t, .value = binop.lhs)); - if (is_valid_call(env, fn->args, args, true)) + if (is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true})) return Texts(b->code, "(", compile_arguments(env, ast, fn->args, args), ")"); } } @@ -589,7 +589,7 @@ static Text_t compile_binary_op(env_t *env, ast_t *ast) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, lhs_t)) { arg_ast_t *args = new (arg_ast_t, .value = binop.lhs, .next = new (arg_ast_t, .value = binop.rhs)); - if (is_valid_call(env, fn->args, args, true)) + if (is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true})) return Texts(b->code, "(", compile_arguments(env, ast, fn->args, args), ")"); } } @@ -599,7 +599,7 @@ static Text_t compile_binary_op(env_t *env, ast_t *ast) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, lhs_t)) { arg_ast_t *args = new (arg_ast_t, .value = binop.lhs, .next = new (arg_ast_t, .value = binop.rhs)); - if (is_valid_call(env, fn->args, args, true)) + if (is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true})) return Texts(b->code, "(", compile_arguments(env, ast, fn->args, args), ")"); } } @@ -609,7 +609,7 @@ static Text_t compile_binary_op(env_t *env, ast_t *ast) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, lhs_t)) { arg_ast_t *args = new (arg_ast_t, .value = binop.lhs, .next = new (arg_ast_t, .value = binop.rhs)); - if (is_valid_call(env, fn->args, args, true)) + if (is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true})) return Texts(b->code, "(", compile_arguments(env, ast, fn->args, args), ")"); } } @@ -3466,6 +3466,24 @@ Text_t compile(env_t *env, ast_t *ast) { type_t *fn_t = get_type(env, call->fn); if (fn_t->tag == FunctionType) { Text_t fn = compile(env, call->fn); + if (!is_valid_call(env, Match(fn_t, FunctionType)->args, call->args, (call_opts_t){.promotion = true})) { + if (is_valid_call(env, Match(fn_t, FunctionType)->args, call->args, + (call_opts_t){.promotion = true, .underscores = true})) { + code_err(ast, "You can't pass underscore arguments to this function (those are private)"); + } else { + arg_t *args = NULL; + for (arg_ast_t *a = call->args; a; a = a->next) + args = new (arg_t, .name = a->name, .type = get_type(env, a->value), .next = args); + REVERSE_LIST(args); + code_err(ast, + "This function's public signature doesn't match this call site.\n" + "The signature is: ", + type_to_text(fn_t), + "\n" + "But it's being called with: ", + type_to_text(Type(FunctionType, .args = args))); + } + } return Texts(fn, "(", compile_arguments(env, ast, Match(fn_t, FunctionType)->args, call->args), ")"); } else if (fn_t->tag == TypeInfoType) { type_t *t = Match(fn_t, TypeInfoType)->type; @@ -3509,21 +3527,19 @@ Text_t compile(env_t *env, ast_t *ast) { ")"); } else if (t->tag == StructType) { DeclareMatch(struct_, t, StructType); - if (!struct_->opaque && is_valid_call(env, struct_->fields, call->args, true)) { - if (env->current_type == NULL || !type_eq(env->current_type, t)) { - for (arg_t *field = struct_->fields; field; field = field->next) { - if (field->name[0] == '_') - code_err(ast, "This struct can't be " - "initialized directly because it " - "has private fields (starting " - "with underscore).\n" - "Use a `convert` or `func` to " - "instantiate the type " - "instead."); - } - } + if (struct_->opaque) code_err(ast, "This struct is opaque, so I don't know what's inside it!"); + + call_opts_t constructor_opts = { + .promotion = true, + .underscores = (env->current_type != NULL && type_eq(env->current_type, t)), + }; + if (is_valid_call(env, struct_->fields, call->args, constructor_opts)) { return Texts("((", compile_type(t), "){", compile_arguments(env, ast, struct_->fields, call->args), "})"); + } else if (!constructor_opts.underscores + && is_valid_call(env, struct_->fields, call->args, + (call_opts_t){.promotion = true, .underscores = true})) { + code_err(ast, "This constructor uses private fields that are not exposed."); } } code_err(ast, diff --git a/src/environment.c b/src/environment.c index 848e1269..f7956054 100644 --- a/src/environment.c +++ b/src/environment.c @@ -718,28 +718,18 @@ PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args, bool if (!type_env) return NULL; List_t constructors = type_env->namespace->constructors; // Prioritize exact matches: + call_opts_t options = {.promotion = false, .underscores = allow_underscores}; for (int64_t i = constructors.length - 1; i >= 0; i--) { - binding_t *b = constructors.data + i * constructors.stride; - DeclareMatch(fn, b->type, FunctionType); - if (!allow_underscores) { - for (arg_t *arg = fn->args; arg; arg = arg->next) - if (arg->name[0] == '_') goto next_constructor; - } - if (type_eq(fn->ret, t) && is_valid_call(env, fn->args, args, false)) return b; - next_constructor: - continue; + binding_t *constructor = constructors.data + i * constructors.stride; + DeclareMatch(fn, constructor->type, FunctionType); + if (type_eq(fn->ret, t) && is_valid_call(env, fn->args, args, options)) return constructor; } // Fall back to promotion: + options.promotion = true; for (int64_t i = constructors.length - 1; i >= 0; i--) { - binding_t *b = constructors.data + i * constructors.stride; - DeclareMatch(fn, b->type, FunctionType); - if (!allow_underscores) { - for (arg_t *arg = fn->args; arg; arg = arg->next) - if (arg->name[0] == '_') goto next_constructor2; - } - if (type_eq(fn->ret, t) && is_valid_call(env, fn->args, args, true)) return b; - next_constructor2: - continue; + binding_t *constructor = constructors.data + i * constructors.stride; + DeclareMatch(fn, constructor->type, FunctionType); + if (type_eq(fn->ret, t) && is_valid_call(env, fn->args, args, options)) return constructor; } return NULL; } @@ -752,7 +742,7 @@ PUREFUNC binding_t *get_metamethod_binding(env_t *env, ast_e tag, ast_t *lhs, as DeclareMatch(fn, b->type, FunctionType); if (!type_eq(fn->ret, ret)) return NULL; arg_ast_t *args = new (arg_ast_t, .value = lhs, .next = new (arg_ast_t, .value = rhs)); - return is_valid_call(env, fn->args, args, true) ? b : NULL; + return is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true}) ? b : NULL; } void set_binding(env_t *env, const char *name, type_t *type, Text_t code) { diff --git a/src/typecheck.c b/src/typecheck.c index 928f4b14..db55ebac 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1300,7 +1300,7 @@ type_t *get_type(env_t *env, ast_t *ast) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, rhs_t)) { arg_ast_t *args = new (arg_ast_t, .value = binop.rhs, .next = new (arg_ast_t, .value = binop.lhs)); - if (is_valid_call(env, fn->args, args, true)) return rhs_t; + if (is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true})) return rhs_t; } } } else if (ast->tag == Multiply && is_numeric_type(rhs_t)) { @@ -1309,7 +1309,7 @@ type_t *get_type(env_t *env, ast_t *ast) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, lhs_t)) { arg_ast_t *args = new (arg_ast_t, .value = binop.lhs, .next = new (arg_ast_t, .value = binop.rhs)); - if (is_valid_call(env, fn->args, args, true)) return lhs_t; + if (is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true})) return lhs_t; } } } else if ((ast->tag == Divide || ast->tag == Mod || ast->tag == Mod1) && is_numeric_type(rhs_t)) { @@ -1318,7 +1318,7 @@ type_t *get_type(env_t *env, ast_t *ast) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, lhs_t)) { arg_ast_t *args = new (arg_ast_t, .value = binop.lhs, .next = new (arg_ast_t, .value = binop.rhs)); - if (is_valid_call(env, fn->args, args, true)) return lhs_t; + if (is_valid_call(env, fn->args, args, (call_opts_t){.promotion = true})) return lhs_t; } } } @@ -1606,19 +1606,22 @@ type_t *get_arg_type(env_t *env, arg_t *arg) { return get_type(env, arg->default_val); } -bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bool promotion_allowed) { +bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, call_opts_t options) { Table_t used_args = {}; // Populate keyword args: for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) { if (!call_arg->name) continue; + if (!options.underscores && call_arg->name[0] == '_') return false; - type_t *call_type = get_arg_ast_type(env, call_arg); for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) { + if (!options.underscores && spec_arg->name[0] == '_') continue; if (!streq(call_arg->name, spec_arg->name)) continue; type_t *spec_type = get_arg_type(env, spec_arg); - if (promotion_allowed) { - if (!can_compile_to_type(env, call_arg->value, spec_type)) + env_t *arg_scope = with_enum_scope(env, spec_type); + type_t *call_type = get_arg_ast_type(arg_scope, call_arg); + if (options.promotion) { + if (!can_compile_to_type(arg_scope, call_arg->value, spec_type)) return false; // Positional arg trying to fill in } else { type_t *complete_call_type = @@ -1638,22 +1641,25 @@ bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bool prom arg_ast_t *keyworded = Table$str_get(used_args, spec_arg->name); if (keyworded) continue; - type_t *spec_type = get_arg_type(env, spec_arg); - for (; unused_args; unused_args = unused_args->next) { - if (unused_args->name) continue; // Already handled the keyword args - if (promotion_allowed) { - if (!can_compile_to_type(env, unused_args->value, spec_type)) - return false; // Positional arg trying to fill in - } else { - type_t *call_type = get_arg_ast_type(env, unused_args); - type_t *complete_call_type = - is_incomplete_type(call_type) ? most_complete_type(call_type, spec_type) : call_type; - if (!complete_call_type) return false; - if (!type_eq(complete_call_type, spec_type)) return false; // Positional arg trying to fill in + if (spec_arg->name[0] != '_' || options.underscores) { + type_t *spec_type = get_arg_type(env, spec_arg); + env_t *arg_scope = with_enum_scope(env, spec_type); + for (; unused_args; unused_args = unused_args->next) { + if (unused_args->name) continue; // Already handled the keyword args + if (options.promotion) { + if (!can_compile_to_type(arg_scope, unused_args->value, spec_type)) + return false; // Positional arg trying to fill in + } else { + type_t *call_type = get_arg_ast_type(arg_scope, unused_args); + type_t *complete_call_type = + is_incomplete_type(call_type) ? most_complete_type(call_type, spec_type) : call_type; + if (!complete_call_type) return false; + if (!type_eq(complete_call_type, spec_type)) return false; // Positional arg trying to fill in + } + Table$str_set(&used_args, spec_arg->name, unused_args); + unused_args = unused_args->next; + goto found_it; } - Table$str_set(&used_args, spec_arg->name, unused_args); - unused_args = unused_args->next; - goto found_it; } if (spec_arg->default_val) goto found_it; @@ -1753,6 +1759,9 @@ PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed) { type_t *actual = get_type(env, ast); if (actual->tag == OptionalType && needed->tag == OptionalType) return can_promote(actual, needed); + if (is_numeric_type(needed) && ast->tag == Int) return true; + if (needed->tag == NumType && ast->tag == Num) return true; + needed = non_optional(needed); if (needed->tag == ListType && ast->tag == List) { type_t *item_type = Match(needed, ListType)->item_type; diff --git a/src/typecheck.h b/src/typecheck.h index b2be77bf..34fc33a1 100644 --- a/src/typecheck.h +++ b/src/typecheck.h @@ -4,6 +4,7 @@ #include <gc.h> #include <stdarg.h> +#include <stdbool.h> #include "ast.h" #include "environment.h" @@ -25,7 +26,12 @@ PUREFUNC bool can_be_mutated(env_t *env, ast_t *ast); type_t *parse_type_string(env_t *env, const char *str); type_t *get_method_type(env_t *env, ast_t *self, const char *name); PUREFUNC bool is_constant(env_t *env, ast_t *ast); -bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, bool promotion_allowed); PUREFUNC bool can_compile_to_type(env_t *env, ast_t *ast, type_t *needed); +typedef struct { + bool promotion : 1, underscores : 1; +} call_opts_t; + +bool is_valid_call(env_t *env, arg_t *spec_args, arg_ast_t *call_args, call_opts_t options); + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 |
