diff --git a/README.md b/README.md index 094fd4ee5..f02043a65 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ The Odin programming language is fast, concise, readable, pragmatic and open sou * built for modern systems * joy of programming * metaprogramming -* designed for good programmers Website: [https://odin.handmade.network/](https://odin.handmade.network/) @@ -20,8 +19,7 @@ Website: [https://odin.handmade.network/](https://odin.handmade.network/) * [Composition & Refactorability](https://www.youtube.com/watch?v=n1wemZfcbXM) * [Introspection, Modules, and Record Layout](https://www.youtube.com/watch?v=UFq8rhWhx4s) * [push_allocator & Minimal Dependency Building](https://www.youtube.com/watch?v=f_LGVOAMb78) -* [when, for, & procedure overloading](https://www.youtube.com/watch?v=OzeOekzyZK8) -* [when, for, & procedure overloading](https://www.youtube.com/watch?v=OzeOekzyZK8) +* [when, for & procedure overloading](https://www.youtube.com/watch?v=OzeOekzyZK8) * [Context Types, Unexported Entities, Labelled Branches](https://www.youtube.com/watch?v=CkHVwT1Qk-g) * [Bit Fields, i128 & u128, Syntax Changes](https://www.youtube.com/watch?v=NlTutcLyF64) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8f21d257a..b0f8e88cf 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4967,8 +4967,99 @@ bool check_unpack_arguments(Checker *c, isize lhs_count, Array *operand return optional_ok; } +// NOTE(bill): Returns `NULL` on failure +Entity *find_or_generate_polymorphic_procedure(Checker *c, Entity *base_entity, Array *operands, ProcedureInfo *proc_info_) { + if (base_entity == NULL) { + return NULL; + } + + if (!is_type_proc(base_entity->type)) { + return NULL; + } + + TypeProc *pt = &base_type(base_entity->type)->Proc; + if (!pt->is_generic || pt->is_generic_specialized) { + return NULL; + } + + if (pt->param_count != operands->count) { + return NULL; + } + + DeclInfo *old_decl = decl_info_of_entity(&c->info, base_entity); + GB_ASSERT(old_decl != NULL); + + gbAllocator a = heap_allocator(); + + CheckerContext prev_context = c->context; + defer (c->context = prev_context); + + Scope *scope = make_scope(base_entity->scope, a); + scope->is_proc = true; + c->context.scope = scope; + + // NOTE(bill): This is slightly memory leaking if the type already exists + // Maybe it's better to check with the previous types first? + Type *final_proc_type = make_type_proc(c->allocator, c->context.scope, NULL, 0, NULL, 0, false, pt->calling_convention); + check_procedure_type(c, final_proc_type, pt->node, operands); + + auto *found = map_get(&c->info.gen_procs, hash_pointer(base_entity->identifier)); + if (found) { + for_array(i, *found) { + Entity *other = (*found)[i]; + if (are_types_identical(other->type, final_proc_type)) { + // NOTE(bill): This scope is not needed any more, destroy it + destroy_scope(scope); + return other; + } + } + } + AstNode *proc_decl = clone_ast_node(a, old_decl->proc_decl); + ast_node(pd, ProcDecl, proc_decl); + // NOTE(bill): Associate the scope declared above with this procedure declaration's type + add_scope(c, pd->type, final_proc_type->Proc.scope); + final_proc_type->Proc.is_generic_specialized = true; + + u64 tags = base_entity->Procedure.tags; + AstNode *ident = clone_ast_node(a, base_entity->identifier); + Token token = ident->Ident; + DeclInfo *d = make_declaration_info(c->allocator, c->context.scope, old_decl->parent); + d->gen_proc_type = final_proc_type; + d->type_expr = pd->type; + d->proc_decl = proc_decl; + + + Entity *entity = make_entity_procedure(c->allocator, NULL, token, final_proc_type, tags); + entity->identifier = ident; + + add_entity_and_decl_info(c, ident, entity, d); + entity->scope = base_entity->scope; + + ProcedureInfo proc_info = {}; + proc_info.file = c->curr_ast_file; + proc_info.token = token; + proc_info.decl = d; + proc_info.type = final_proc_type; + proc_info.body = pd->body; + proc_info.tags = tags; + + if (found) { + array_add(found, entity); + } else { + Array array = {}; + array_init(&array, heap_allocator()); + array_add(&array, entity); + map_set(&c->info.gen_procs, hash_pointer(entity->identifier), array); + } + + GB_ASSERT(entity != NULL); + + + if (proc_info_) *proc_info_ = proc_info; + return entity; +} CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { ast_node(ce, CallExpr, call); @@ -5056,89 +5147,17 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { } } else { // NOTE(bill): Generate the procedure type for this generic instance - // TODO(bill): Clean this shit up! - ProcedureInfo proc_info = {}; if (pt->is_generic && !pt->is_generic_specialized) { - GB_ASSERT(entity != NULL); - - DeclInfo *old_decl = decl_info_of_entity(&c->info, entity); - GB_ASSERT(old_decl != NULL); - - gbAllocator a = heap_allocator(); - - CheckerContext prev_context = c->context; - defer (c->context = prev_context); - - Scope *scope = make_scope(entity->scope, a); - scope->is_proc = true; - c->context.scope = scope; - - // NOTE(bill): This is slightly memory leaking if the type already exists - // Maybe it's better to check with the previous types first? - final_proc_type = make_type_proc(c->allocator, c->context.scope, NULL, 0, NULL, 0, false, pt->calling_convention); - check_procedure_type(c, final_proc_type, pt->node, &operands); - - bool skip = false; - auto *found = map_get(&c->info.gen_procs, hash_pointer(entity->identifier)); - if (found) { - for_array(i, *found) { - Entity *other = (*found)[i]; - if (are_types_identical(other->type, final_proc_type)) { - skip = true; - gen_entity = other; - final_proc_type = other->type; - break; - } - } + gen_entity = find_or_generate_polymorphic_procedure(c, entity, &operands, &proc_info); + if (gen_entity != NULL) { + GB_ASSERT(is_type_proc(gen_entity->type)); + final_proc_type = gen_entity->type; } - - if (skip) { - // NOTE(bill): It is not needed any more, destroy it - destroy_scope(scope); - } else { - AstNode *proc_decl = clone_ast_node(a, old_decl->proc_decl); - ast_node(pd, ProcDecl, proc_decl); - // NOTE(bill): Associate the scope declared above with this procedure declaration's type - add_scope(c, pd->type, final_proc_type->Proc.scope); - final_proc_type->Proc.is_generic_specialized = true; - - u64 tags = entity->Procedure.tags; - AstNode *ident = clone_ast_node(a, entity->identifier); - Token token = ident->Ident; - DeclInfo *d = make_declaration_info(c->allocator, c->context.scope, old_decl->parent); - d->gen_proc_type = final_proc_type; - d->type_expr = pd->type; - d->proc_decl = proc_decl; - - gen_entity = make_entity_procedure(c->allocator, NULL, token, final_proc_type, tags); - gen_entity->identifier = ident; - - add_entity_and_decl_info(c, ident, gen_entity, d); - gen_entity->scope = entity->scope; - add_entity_use(c, ident, gen_entity); - - proc_info.file = c->curr_ast_file; - proc_info.token = token; - proc_info.decl = d; - proc_info.type = final_proc_type; - proc_info.body = pd->body; - proc_info.tags = tags; - - if (found) { - array_add(found, gen_entity); - } else { - Array array = {}; - array_init(&array, heap_allocator()); - array_add(&array, gen_entity); - map_set(&c->info.gen_procs, hash_pointer(entity->identifier), array); - } - } - - GB_ASSERT(gen_entity != NULL); } + GB_ASSERT(is_type_proc(final_proc_type)); TypeProc *pt = &final_proc_type->Proc; GB_ASSERT(pt->params != NULL); @@ -5154,7 +5173,7 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { if (o.mode == Addressing_Invalid) { continue; } else if (o.mode != Addressing_Type) { - error(o.expr, "Expected a type for the argument"); + error(o.expr, "Expected a type for the argument `%.*s`", LIT(e->token.string)); err = CallArgumentError_WrongTypes; } @@ -5214,7 +5233,10 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { } if (gen_entity != NULL && err == CallArgumentError_None) { - check_procedure_later(c, proc_info); + if (proc_info.decl != NULL) { + // NOTE(bill): Check the newly generated procedure body + check_procedure_later(c, proc_info); + } } } } @@ -5278,6 +5300,10 @@ CALL_ARGUMENT_CHECKER(check_named_call_arguments) { isize param_count = pt->param_count; bool *visited = gb_alloc_array(c->allocator, bool, param_count); + Array ordered_operands = {}; + array_init_count(&ordered_operands, heap_allocator(), param_count); + defer (array_free(&ordered_operands)); + for_array(i, ce->args) { AstNode *arg = ce->args[i]; ast_node(fv, FieldValue, arg); @@ -5308,36 +5334,9 @@ CALL_ARGUMENT_CHECKER(check_named_call_arguments) { } visited[index] = true; - Operand *o = &operands[i]; - Entity *e = pt->params->Tuple.variables[index]; - - if (e->kind == Entity_TypeName) { - GB_ASSERT(pt->is_generic); - GB_ASSERT(!pt->variadic); - if (o->mode == Addressing_Invalid) { - continue; - } else if (o->mode != Addressing_Type) { - error(o->expr, "Expected a type for the argument"); - } - if (are_types_identical(e->type, o->type)) { - score += assign_score_function(1); - } else { - score += assign_score_function(5); - } - } else { - i64 s = 0; - if (!check_is_assignable_to_with_score(c, o, e->type, &s)) { - if (show_error) { - check_assignment(c, o, e->type, str_lit("procedure argument")); - } - err = CallArgumentError_WrongTypes; - } - score += s; - } + ordered_operands[index] = operands[i]; } - -#if 1 isize param_count_to_check = param_count; if (pt->variadic) { param_count_to_check--; @@ -5368,19 +5367,58 @@ CALL_ARGUMENT_CHECKER(check_named_call_arguments) { err = CallArgumentError_ParameterMissing; } } -#endif - if (pt->is_generic) { - if (show_error) { - error(call, "Generic procedures do not yet support named arguments"); + Entity *gen_entity = NULL; + if (pt->is_generic && err == CallArgumentError_None) { + // err = CallArgumentError_GenericProcedureNotSupported; + ProcedureInfo proc_info = {}; + gen_entity = find_or_generate_polymorphic_procedure(c, entity, &ordered_operands, &proc_info); + if (gen_entity != NULL) { + if (proc_info.decl != NULL) { + check_procedure_later(c, proc_info); + } + + pt = &base_type(gen_entity->type)->Proc; + } + } + + for (isize i = 0; i < param_count; i++) { + Operand *o = &ordered_operands[i]; + if (o->mode == Addressing_Invalid) { + continue; + } + Entity *e = pt->params->Tuple.variables[i]; + + if (e->kind == Entity_TypeName) { + GB_ASSERT(pt->is_generic); + GB_ASSERT(!pt->variadic); + if (o->mode != Addressing_Type) { + if (show_error) { + error(o->expr, "Expected a type for the argument `%.*s`", LIT(e->token.string)); + } + err = CallArgumentError_WrongTypes; + } + if (are_types_identical(e->type, o->type)) { + score += assign_score_function(1); + } else { + score += assign_score_function(10); + } + } else { + i64 s = 0; + if (!check_is_assignable_to_with_score(c, o, e->type, &s)) { + if (show_error) { + check_assignment(c, o, e->type, str_lit("procedure argument")); + } + err = CallArgumentError_WrongTypes; + } + score += s; } - err = CallArgumentError_GenericProcedureNotSupported; } if (data) { data->score = score; data->result_type = pt->results; - data->gen_entity = NULL; + data->gen_entity = gen_entity; } return err; @@ -5487,12 +5525,14 @@ CallArgumentData check_call_arguments(Checker *c, Operand *operand, Type *proc_t proc_type = e->type; CallArgumentData data = {}; CallArgumentError err = call_checker(c, call, proc_type, e, operands, CallArgumentMode_ShowErrors, &data); + if (data.gen_entity != NULL) add_entity_use(c, ce->proc, data.gen_entity); return data; } } else { Entity *e = entity_of_ident(&c->info, operand->expr); CallArgumentData data = {}; CallArgumentError err = call_checker(c, call, proc_type, e, operands, CallArgumentMode_ShowErrors, &data); + if (data.gen_entity != NULL) add_entity_use(c, ce->proc, data.gen_entity); return data; } diff --git a/src/checker.cpp b/src/checker.cpp index a7e3aa524..7d717e7cb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1169,7 +1169,9 @@ void add_type_info_type(Checker *c, Type *t) { } void check_procedure_later(Checker *c, ProcedureInfo info) { - map_set(&c->procs, hash_decl_info(info.decl), info); + if (info.decl != NULL) { + map_set(&c->procs, hash_decl_info(info.decl), info); + } } void check_procedure_later(Checker *c, AstFile *file, Token token, DeclInfo *decl, Type *type, AstNode *body, u64 tags) { @@ -1247,8 +1249,10 @@ Map generate_minimum_dependency_map(CheckerInfo *info, Entity *start) for_array(i, info->definitions.entries) { Entity *e = info->definitions.entries[i].value; if (e->scope->is_global) { - // NOTE(bill): Require runtime stuff - add_dependency_to_map(&map, info, e); + if (!is_type_gen_proc(e->type)) { + // NOTE(bill): Require runtime stuff + add_dependency_to_map(&map, info, e); + } } else if (e->kind == Entity_Procedure) { if ((e->Procedure.tags & ProcTag_export) != 0) { add_dependency_to_map(&map, info, e); diff --git a/src/ir.cpp b/src/ir.cpp index d5fc33174..e0ca61522 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -4680,15 +4680,19 @@ irValue *ir_build_expr(irProcedure *proc, AstNode *expr) { TypeTuple *pt = &type->params->Tuple; for (isize i = 0; i < param_count; i++) { Entity *e = pt->variables[i]; - GB_ASSERT(e->kind == Entity_Variable); - if (args[i] == NULL) { - if (e->Variable.default_value.kind != ExactValue_Invalid) { - args[i] = ir_value_constant(proc->module->allocator, e->type, e->Variable.default_value); - } else { - args[i] = ir_value_nil(proc->module->allocator, e->type); - } + if (e->kind == Entity_TypeName) { + args[i] = ir_value_nil(proc->module->allocator, e->type); } else { - args[i] = ir_emit_conv(proc, args[i], e->type); + GB_ASSERT(e->kind == Entity_Variable); + if (args[i] == NULL) { + if (e->Variable.default_value.kind != ExactValue_Invalid) { + args[i] = ir_value_constant(proc->module->allocator, e->type, e->Variable.default_value); + } else { + args[i] = ir_value_nil(proc->module->allocator, e->type); + } + } else { + args[i] = ir_emit_conv(proc, args[i], e->type); + } } }