diff options
| author | Ginger Bill <bill@gingerbill.org> | 2017-07-13 22:35:00 +0100 |
|---|---|---|
| committer | Ginger Bill <bill@gingerbill.org> | 2017-07-13 22:35:00 +0100 |
| commit | 1c5ddd65b4f66c4d37bd9da0facb229bbcea22eb (patch) | |
| tree | 78078fbb2cf23b31689bb6842ba30e487f199447 /src/check_expr.cpp | |
| parent | b8697fb4ed34d0da0fa0888b57e6edcc37a0ce81 (diff) | |
Rudimentary support for parametric polymorphic types
Diffstat (limited to 'src/check_expr.cpp')
| -rw-r--r-- | src/check_expr.cpp | 525 |
1 files changed, 461 insertions, 64 deletions
diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a5557ee86..47151cc74 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -12,6 +12,7 @@ enum CallArgumentError { CallArgumentError_ParameterNotFound, CallArgumentError_ParameterMissing, CallArgumentError_DuplicateParameter, + CallArgumentError_NoneConstantParameter, }; enum CallArgumentErrorMode { @@ -972,7 +973,7 @@ Entity *make_names_field_for_record(Checker *c, Scope *scope) { return e; } -void check_struct_type(Checker *c, Type *struct_type, AstNode *node) { +void check_struct_type(Checker *c, Type *struct_type, AstNode *node, Array<Operand> *poly_operands) { GB_ASSERT(is_type_struct(struct_type)); ast_node(st, StructType, node); @@ -984,11 +985,132 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node) { min_field_count += f->names.count; case_end; } - } struct_type->Record.names = make_names_field_for_record(c, c->context.scope); - auto fields = check_fields(c, node, st->fields, min_field_count, str_lit("struct")); + Type *polymorphic_params = nullptr; + bool is_polymorphic = false; + if (st->polymorphic_params != nullptr) { + ast_node(field_list, FieldList, st->polymorphic_params); + Array<AstNode *> params = field_list->list; + if (params.count != 0) { + isize variable_count = 0; + for_array(i, params) { + AstNode *field = params[i]; + if (ast_node_expect(field, AstNode_Field)) { + ast_node(f, Field, field); + variable_count += gb_max(f->names.count, 1); + } + } + + Array<Entity *> entities = {}; + array_init(&entities, c->allocator, variable_count); + + for_array(i, params) { + AstNode *param = params[i]; + if (param->kind != AstNode_Field) { + continue; + } + ast_node(p, Field, param); + AstNode *type_expr = p->type; + Type *type = nullptr; + bool is_type_param = false; + bool is_type_polymorphic_type = false; + if (type_expr == nullptr) { + error(param, "Expected a type for this parameter"); + continue; + } + if (type_expr->kind == AstNode_Ellipsis) { + type_expr = type_expr->Ellipsis.expr; + error(param, "A polymorphic parameter cannot be variadic"); + } + if (type_expr->kind == AstNode_TypeType) { + is_type_param = true; + type = make_type_generic(c->allocator, 0, str_lit("")); + } else { + type = check_type(c, type_expr); + if (is_type_polymorphic(type)) { + is_type_polymorphic_type = true; + } + } + + if (type == nullptr) { + error(params[i], "Invalid parameter type"); + type = t_invalid; + } + if (is_type_untyped(type)) { + if (is_type_untyped_undef(type)) { + error(params[i], "Cannot determine parameter type from ---"); + } else { + error(params[i], "Cannot determine parameter type from a nil"); + } + type = t_invalid; + } + + if (is_type_polymorphic_type) { + gbString str = type_to_string(type); + error(params[i], "Parameter types cannot be polymorphic, got %s", str); + gb_string_free(str); + type = t_invalid; + } + + if (!is_type_param && !is_type_constant_type(type)) { + gbString str = type_to_string(type); + error(params[i], "A parameter must be a valid constant type, got %s", str); + gb_string_free(str); + } + + Scope *scope = c->context.scope; + for_array(j, p->names) { + AstNode *name = p->names[j]; + if (!ast_node_expect(name, AstNode_Ident)) { + continue; + } + Entity *e = nullptr; + + Token token = name->Ident.token; + + if (poly_operands != nullptr) { + Operand operand = (*poly_operands)[entities.count]; + if (is_type_param) { + GB_ASSERT(operand.mode == Addressing_Type); + e = make_entity_type_name(c->allocator, scope, token, operand.type); + e->TypeName.is_type_alias = true; + } else { + GB_ASSERT(operand.mode == Addressing_Constant); + e = make_entity_constant(c->allocator, scope, token, operand.type, operand.value); + } + } else { + if (is_type_param) { + e = make_entity_type_name(c->allocator, scope, token, type); + e->TypeName.is_type_alias = true; + } else { + e = make_entity_constant(c->allocator, scope, token, type, empty_exact_value); + } + } + + add_entity(c, scope, name, e); + array_add(&entities, e); + } + } + + if (entities.count > 0) { + Type *tuple = make_type_tuple(c->allocator); + tuple->Tuple.variables = entities.data; + tuple->Tuple.variable_count = entities.count; + + polymorphic_params = tuple; + } + } + } + + is_polymorphic = polymorphic_params != nullptr && poly_operands == nullptr; + + Array<Entity *> fields = {}; + + if (!is_polymorphic) { + fields = check_fields(c, node, st->fields, min_field_count, str_lit("struct")); + } struct_type->Record.scope = c->context.scope; struct_type->Record.is_packed = st->is_packed; @@ -996,6 +1118,9 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node) { struct_type->Record.fields = fields.data; struct_type->Record.fields_in_src_order = fields.data; struct_type->Record.field_count = fields.count; + struct_type->Record.polymorphic_params = polymorphic_params; + struct_type->Record.is_polymorphic = is_polymorphic; + type_set_offsets(c->allocator, struct_type); @@ -1703,53 +1828,55 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *_params, bool *is_vari for_array(j, p->names) { AstNode *name = p->names[j]; - if (ast_node_expect(name, AstNode_Ident)) { - Entity *param = nullptr; - if (is_type_param) { - if (operands != nullptr) { - Operand o = (*operands)[variable_index]; - if (o.mode == Addressing_Type) { - type = o.type; - } else { - if (!c->context.no_polymorphic_errors) { - error(o.expr, "Expected a type to assign to the type parameter"); - } - success = false; - type = t_invalid; - } - } - param = make_entity_type_name(c->allocator, scope, name->Ident.token, type); - param->TypeName.is_type_alias = true; - } else { - if (operands != nullptr && is_type_polymorphic_type) { - Operand op = (*operands)[variable_index]; - type = determine_type_from_polymorphic(c, type, op); - if (type == t_invalid) { - success = false; - } - } + if (!ast_node_expect(name, AstNode_Ident)) { + continue; + } - if (p->flags&FieldFlag_no_alias) { - if (!is_type_pointer(type)) { - error(params[i], "`#no_alias` can only be applied to fields of pointer type"); - p->flags &= ~FieldFlag_no_alias; // Remove the flag + Entity *param = nullptr; + if (is_type_param) { + if (operands != nullptr) { + Operand o = (*operands)[variable_index]; + if (o.mode == Addressing_Type) { + type = o.type; + } else { + if (!c->context.no_polymorphic_errors) { + error(o.expr, "Expected a type to assign to the type parameter"); } + success = false; + type = t_invalid; + } + } + param = make_entity_type_name(c->allocator, scope, name->Ident.token, type); + param->TypeName.is_type_alias = true; + } else { + if (operands != nullptr && is_type_polymorphic_type) { + Operand op = (*operands)[variable_index]; + type = determine_type_from_polymorphic(c, type, op); + if (type == t_invalid) { + success = false; } - - param = make_entity_param(c->allocator, scope, name->Ident.token, type, - (p->flags&FieldFlag_using) != 0, false); - param->Variable.default_value = value; - param->Variable.default_is_nil = default_is_nil; - param->Variable.default_is_location = default_is_location; - } + if (p->flags&FieldFlag_no_alias) { - param->flags |= EntityFlag_NoAlias; + if (!is_type_pointer(type)) { + error(params[i], "`#no_alias` can only be applied to fields of pointer type"); + p->flags &= ~FieldFlag_no_alias; // Remove the flag + } } - add_entity(c, scope, name, param); - variables[variable_index++] = param; + param = make_entity_param(c->allocator, scope, name->Ident.token, type, + (p->flags&FieldFlag_using) != 0, false); + param->Variable.default_value = value; + param->Variable.default_is_nil = default_is_nil; + param->Variable.default_is_location = default_is_location; + + } + if (p->flags&FieldFlag_no_alias) { + param->flags |= EntityFlag_NoAlias; } + + add_entity(c, scope, name, param); + variables[variable_index++] = param; } } @@ -2358,7 +2485,7 @@ i64 check_array_or_map_count(Checker *c, AstNode *e, bool is_map) { if (count >= 0) { return count; } - error(e, "Invalid array count"); + error(e, "Invalid negative array count %lld", cast(long long)count); } return 0; } @@ -2663,7 +2790,7 @@ bool check_type_internal(Checker *c, AstNode *e, Type **type, Type *named_type) *type = make_type_struct(c->allocator); set_base_type(named_type, *type); check_open_scope(c, e); - check_struct_type(c, *type, e); + check_struct_type(c, *type, e, nullptr); check_close_scope(c); (*type)->Record.node = e; return true; @@ -6114,8 +6241,263 @@ Entity *find_using_index_expr(Type *t) { return nullptr; } +isize lookup_polymorphic_struct_parameter(TypeRecord *st, String parameter_name) { + if (!st->is_polymorphic) return -1; + + TypeTuple *params = &st->polymorphic_params->Tuple; + isize param_count = params->variable_count; + for (isize i = 0; i < param_count; i++) { + Entity *e = params->variables[i]; + String name = e->token.string; + if (is_blank_ident(name)) { + continue; + } + if (name == parameter_name) { + return i; + } + } + return -1; +} + +CallArgumentError check_polymorphic_struct_type(Checker *c, Operand *operand, AstNode *call) { + ast_node(ce, CallExpr, call); + + Type *original_type = operand->type; + Type *struct_type = base_type(operand->type); + GB_ASSERT(is_type_struct(struct_type)); + TypeRecord *st = &struct_type->Record; + GB_ASSERT(st->is_polymorphic); + + bool show_error = true; + + Array<Operand> operands = {}; + defer (array_free(&operands)); + + bool named_fields = false; + + if (is_call_expr_field_value(ce)) { + named_fields = true; + array_init_count(&operands, heap_allocator(), ce->args.count); + for_array(i, ce->args) { + AstNode *arg = ce->args[i]; + ast_node(fv, FieldValue, arg); + check_expr_or_type(c, &operands[i], fv->value); + } + + bool vari_expand = (ce->ellipsis.pos.line != 0); + if (vari_expand) { + error(ce->ellipsis, "Invalid use of `..` in a polymorphic type call`"); + } + + } else { + array_init(&operands, heap_allocator(), 2*ce->args.count); + check_unpack_arguments(c, -1, &operands, ce->args, false); + } + + CallArgumentError err = CallArgumentError_None; + + TypeTuple *tuple = &st->polymorphic_params->Tuple; + isize param_count = tuple->variable_count; + + Array<Operand> ordered_operands = operands; + if (named_fields) { + bool *visited = gb_alloc_array(c->allocator, bool, param_count); + + array_init_count(&ordered_operands, c->tmp_allocator, param_count); + + for_array(i, ce->args) { + AstNode *arg = ce->args[i]; + ast_node(fv, FieldValue, arg); + if (fv->field->kind != AstNode_Ident) { + if (show_error) { + gbString expr_str = expr_to_string(fv->field); + error(arg, "Invalid parameter name `%s` in polymorphic type call", expr_str); + gb_string_free(expr_str); + } + err = CallArgumentError_InvalidFieldValue; + continue; + } + String name = fv->field->Ident.token.string; + isize index = lookup_polymorphic_struct_parameter(st, name); + if (index < 0) { + if (show_error) { + error(arg, "No parameter named `%.*s` for this polymorphic type", LIT(name)); + } + err = CallArgumentError_ParameterNotFound; + continue; + } + if (visited[index]) { + if (show_error) { + error(arg, "Duplicate parameter `%.*s` in polymorphic type", LIT(name)); + } + err = CallArgumentError_DuplicateParameter; + continue; + } + + visited[index] = true; + ordered_operands[index] = operands[i]; + } + + for (isize i = 0; i < param_count; i++) { + if (!visited[i]) { + Entity *e = tuple->variables[i]; + if (is_blank_ident(e->token)) { + continue; + } + + if (show_error) { + if (e->kind == Entity_TypeName) { + error(call, "Type parameter `%.*s` is missing in polymorphic type call", + LIT(e->token.string)); + } else { + gbString str = type_to_string(e->type); + error(call, "Parameter `%.*s` of type `%s` is missing in polymorphic type call", + LIT(e->token.string), str); + gb_string_free(str); + } + } + err = CallArgumentError_ParameterMissing; + } + } + } + + if (err != 0) { + operand->mode = Addressing_Invalid; + return err; + } + + i64 score = 0; + for (isize i = 0; i < param_count; i++) { + Operand *o = &ordered_operands[i]; + if (o->mode == Addressing_Invalid) { + continue; + } + Entity *e = tuple->variables[i]; + + if (e->kind == Entity_TypeName) { + 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("polymorphic type argument")); + } + err = CallArgumentError_WrongTypes; + } + o->type = e->type; + if (o->mode != Addressing_Constant) { + if (show_error) { + error(o->expr, "Expected a constant value for this polymorphic type argument"); + } + err = CallArgumentError_NoneConstantParameter; + } + score += s; + } + } + + if (param_count < ordered_operands.count) { + error(call, "Too many polymorphic type arguments, expected %td, got %td", param_count, ordered_operands.count); + err = CallArgumentError_TooManyArguments; + } else if (param_count > ordered_operands.count) { + error(call, "Too few polymorphic type arguments, expected %td, got %td", param_count, ordered_operands.count); + err = CallArgumentError_TooFewArguments; + } + + if (err == 0) { + // TODO(bill): Check for previous types + gbAllocator a = c->allocator; + + auto *found_gen_types = map_get(&c->info.gen_types, hash_pointer(original_type)); + + if (found_gen_types != nullptr) { + for_array(i, *found_gen_types) { + Entity *e = (*found_gen_types)[i]; + Type *t = base_type(e->type); + TypeTuple *tuple = &t->Record.polymorphic_params->Tuple; + bool ok = true; + GB_ASSERT(param_count == tuple->variable_count); + for (isize j = 0; j < param_count; j++) { + Entity *p = tuple->variables[j]; + Operand o = ordered_operands[j]; + if (p->kind == Entity_TypeName) { + if (!are_types_identical(o.type, p->type)) { + ok = false; + } + } else if (p->kind == Entity_Constant) { + if (!are_types_identical(o.type, p->type)) { + ok = false; + } + if (!compare_exact_values(Token_CmpEq, o.value, p->Constant.value)) { + ok = false; + } + } else { + GB_PANIC("Unknown entity kind"); + } + } + if (ok) { + operand->mode = Addressing_Type; + operand->type = e->type; + return err; + } + } + } + + String generated_name = make_string_c(expr_to_string(call)); + + Type *named_type = make_type_named(a, generated_name, nullptr, nullptr); + Type *struct_type = make_type_struct(a); + AstNode *node = clone_ast_node(a, st->node); + set_base_type(named_type, struct_type); + check_open_scope(c, node); + check_struct_type(c, struct_type, node, &ordered_operands); + check_close_scope(c); + struct_type->Record.node = node; + + Entity *e = nullptr; + + { + Token token = ast_node_token(node); + token.kind = Token_String; + token.string = generated_name; + + AstNode *node = gb_alloc_item(a, AstNode); + node->kind = AstNode_Ident; + node->Ident.token = token; + + e = make_entity_type_name(a, st->scope->parent, token, named_type); + add_entity(c, st->scope->parent, node, e); + add_entity_use(c, node, e); + } + + named_type->Named.type_name = e; + + if (found_gen_types) { + array_add(found_gen_types, e); + } else { + Array<Entity *> array = {}; + array_init(&array, heap_allocator()); + array_add(&array, e); + map_set(&c->info.gen_types, hash_pointer(original_type), array); + } + + operand->mode = Addressing_Type; + operand->type = named_type; + } + return err; +} + + ExprKind check_call_expr(Checker *c, Operand *operand, AstNode *call) { - GB_ASSERT(call->kind == AstNode_CallExpr); ast_node(ce, CallExpr, call); if (ce->proc != nullptr && ce->proc->kind == AstNode_BasicDirective) { @@ -6170,28 +6552,43 @@ ExprKind check_call_expr(Checker *c, Operand *operand, AstNode *call) { if (operand->mode == Addressing_Type) { Type *t = operand->type; - gbString str = type_to_string(t); - defer (gb_string_free(str)); - - operand->mode = Addressing_Invalid; - isize arg_count = ce->args.count; - switch (arg_count) { - case 0: error(call, "Missing argument in conversion to `%s`", str); break; - default: error(call, "Too many arguments in conversion to `%s`", str); break; - case 1: { - AstNode *arg = ce->args[0]; - if (arg->kind == AstNode_FieldValue) { - error(call, "`field = value` cannot be used in a type conversion"); - arg = arg->FieldValue.value; - // NOTE(bill): Carry on the cast regardless + if (is_type_polymorphic_struct(t)) { + auto err = check_polymorphic_struct_type(c, operand, call); + if (err == 0) { + AstNode *ident = operand->expr; + while (ident->kind == AstNode_SelectorExpr) { + AstNode *s = ident->SelectorExpr.selector; + ident = s; + } + add_entity_use(c, ident, entity_of_ident(&c->info, ident)); + add_type_and_value(&c->info, call, Addressing_Type, operand->type, empty_exact_value); + } else { + operand->mode = Addressing_Invalid; + operand->type = t_invalid; } - check_expr(c, operand, arg); - if (operand->mode != Addressing_Invalid) { - check_cast(c, operand, t); + } else { + gbString str = type_to_string(t); + defer (gb_string_free(str)); + + operand->mode = Addressing_Invalid; + isize arg_count = ce->args.count; + switch (arg_count) { + case 0: error(call, "Missing argument in conversion to `%s`", str); break; + default: error(call, "Too many arguments in conversion to `%s`", str); break; + case 1: { + AstNode *arg = ce->args[0]; + if (arg->kind == AstNode_FieldValue) { + error(call, "`field = value` cannot be used in a type conversion"); + arg = arg->FieldValue.value; + // NOTE(bill): Carry on the cast regardless + } + check_expr(c, operand, arg); + if (operand->mode != Addressing_Invalid) { + check_cast(c, operand, t); + } + } break; } - } break; } - return Expr_Expr; } |