diff options
| -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 |
