Allow x :: y when cond else proc(...){...}

This commit is contained in:
gingerBill
2024-07-04 13:48:52 +01:00
parent 45b2a6a19e
commit 657bc88535
3 changed files with 189 additions and 79 deletions

View File

@@ -155,6 +155,154 @@ gb_internal void check_init_variables(CheckerContext *ctx, Entity **lhs, isize l
}
}
gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_entity) {
// NOTE(bill): The original_entity's scope may not be same scope that it was inserted into
// e.g. file entity inserted into its package scope
String original_name = original_entity->token.string;
Scope *found_scope = nullptr;
Entity *found_entity = nullptr;
scope_lookup_parent(original_entity->scope, original_name, &found_scope, &found_entity);
if (found_scope == nullptr) {
return;
}
rw_mutex_lock(&found_scope->mutex);
defer (rw_mutex_unlock(&found_scope->mutex));
// IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the
// original entity was still used check checked, but the checking was only
// relying on "constant" data such as the Entity.type and Entity.Constant.value
//
// Therefore two things can be done: the type can be assigned to state that it
// has been "evaluated" and the variant data can be copied across
string_map_set(&found_scope->elements, original_name, new_entity);
original_entity->flags |= EntityFlag_Overridden;
original_entity->type = new_entity->type;
original_entity->aliased_of = new_entity;
Ast *empty_ident = nullptr;
original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier);
if (original_entity->identifier.load() != nullptr &&
original_entity->identifier.load()->kind == Ast_Ident) {
original_entity->identifier.load()->Ident.entity = new_entity;
}
// IMPORTANT NOTE(bill, 2021-04-10): copy only the variants
// This is most likely NEVER required, but it does not at all hurt to keep
isize offset = cast(u8 *)&original_entity->Dummy.start - cast(u8 *)original_entity;
isize size = gb_size_of(*original_entity) - offset;
gb_memmove(cast(u8 *)original_entity, cast(u8 *)new_entity, size);
}
gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d);
gb_internal bool check_try_override_const_decl(CheckerContext *ctx, Entity *e, Entity *entity, Ast *init, Type *named_type) {
if (entity == nullptr) {
retry_proc_lit:;
init = unparen_expr(init);
if (init == nullptr) {
return false;
}
if (init->kind == Ast_TernaryWhenExpr) {
ast_node(we, TernaryWhenExpr, init);
if (we->cond == nullptr) {
return false;
}
if (we->cond->tav.value.kind != ExactValue_Bool) {
return false;
}
init = we->cond->tav.value.value_bool ? we->x : we->y;
goto retry_proc_lit;
} if (init->kind == Ast_ProcLit) {
// NOTE(bill, 2024-07-04): Override as a procedure entity because this could be within a `when` statement
e->kind = Entity_Procedure;
e->type = nullptr;
DeclInfo *d = decl_info_of_entity(e);
d->proc_lit = init;
check_proc_decl(ctx, e, d);
return true;
}
return false;
}
switch (entity->kind) {
case Entity_TypeName:
// @TypeAliasingProblem
// NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases
// being "confused" as constants
//
// A :: B
// C :: proc "c" (^A)
// B :: struct {x: C}
//
// A gets evaluated first, and then checks B.
// B then checks C.
// C then tries to check A which is unresolved but thought to be a constant.
// Therefore within C's check, A errs as "not a type".
//
// This is because a const declaration may or may not be a type and this cannot
// be determined from a syntactical standpoint.
// This check allows the compiler to override the entity to be checked as a type.
//
// There is no problem if B is prefixed with the `#type` helper enforcing at
// both a syntax and semantic level that B must be a type.
//
// A :: #type B
//
// This approach is not fool proof and can fail in case such as:
//
// X :: type_of(x)
// X :: Foo(int).Type
//
// Since even these kind of declarations may cause weird checking cycles.
// For the time being, these are going to be treated as an unfortunate error
// until there is a proper delaying system to try declaration again if they
// have failed.
e->kind = Entity_TypeName;
check_type_decl(ctx, e, init, named_type);
return true;
case Entity_Builtin:
if (e->type != nullptr) {
return false;
}
e->kind = Entity_Builtin;
e->Builtin.id = entity->Builtin.id;
e->type = t_invalid;
return true;
case Entity_ProcGroup:
// NOTE(bill, 2020-06-10): It is better to just clone the contents than overriding the entity in the scope
// Thank goodness I made entities a tagged union to allow for this implace patching
e->kind = Entity_ProcGroup;
e->ProcGroup.entities = array_clone(heap_allocator(), entity->ProcGroup.entities);
return true;
}
if (e->type != nullptr && entity->type != nullptr) {
Operand x = {};
x.type = entity->type;
x.mode = Addressing_Variable;
if (!check_is_assignable_to(ctx, &x, e->type)) {
return false;
}
}
// NOTE(bill): Override aliased entity
switch (entity->kind) {
case Entity_ProcGroup:
case Entity_Procedure:
case Entity_LibraryName:
case Entity_ImportName:
override_entity_in_scope(e, entity);
return true;
}
return false;
}
gb_internal void check_init_constant(CheckerContext *ctx, Entity *e, Operand *operand) {
if (operand->mode == Addressing_Invalid ||
operand->type == t_invalid ||
@@ -165,6 +313,13 @@ gb_internal void check_init_constant(CheckerContext *ctx, Entity *e, Operand *op
return;
}
if (operand->mode != Addressing_Constant) {
Entity *entity = entity_of_node(operand->expr);
if (check_try_override_const_decl(ctx, e, entity, operand->expr, nullptr)) {
return;
}
}
if (operand->mode != Addressing_Constant) {
gbString str = expr_to_string(operand->expr);
error(operand->expr, "'%s' is not a compile-time known constant", str);
@@ -373,49 +528,6 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
}
gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_entity) {
// NOTE(bill): The original_entity's scope may not be same scope that it was inserted into
// e.g. file entity inserted into its package scope
String original_name = original_entity->token.string;
Scope *found_scope = nullptr;
Entity *found_entity = nullptr;
scope_lookup_parent(original_entity->scope, original_name, &found_scope, &found_entity);
if (found_scope == nullptr) {
return;
}
rw_mutex_lock(&found_scope->mutex);
defer (rw_mutex_unlock(&found_scope->mutex));
// IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the
// original entity was still used check checked, but the checking was only
// relying on "constant" data such as the Entity.type and Entity.Constant.value
//
// Therefore two things can be done: the type can be assigned to state that it
// has been "evaluated" and the variant data can be copied across
string_map_set(&found_scope->elements, original_name, new_entity);
original_entity->flags |= EntityFlag_Overridden;
original_entity->type = new_entity->type;
original_entity->aliased_of = new_entity;
Ast *empty_ident = nullptr;
original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier);
if (original_entity->identifier.load() != nullptr &&
original_entity->identifier.load()->kind == Ast_Ident) {
original_entity->identifier.load()->Ident.entity = new_entity;
}
// IMPORTANT NOTE(bill, 2021-04-10): copy only the variants
// This is most likely NEVER required, but it does not at all hurt to keep
isize offset = cast(u8 *)&original_entity->Dummy.start - cast(u8 *)original_entity;
isize size = gb_size_of(*original_entity) - offset;
gb_memmove(cast(u8 *)original_entity, cast(u8 *)new_entity, size);
}
gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init, Type *named_type) {
GB_ASSERT(e->type == nullptr);
GB_ASSERT(e->kind == Entity_Constant);
@@ -441,41 +553,7 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr
if (init != nullptr) {
Entity *entity = check_entity_from_ident_or_selector(ctx, init, false);
if (entity != nullptr && entity->kind == Entity_TypeName) {
// @TypeAliasingProblem
// NOTE(bill, 2022-02-03): This is used to solve the problem caused by type aliases
// being "confused" as constants
//
// A :: B
// C :: proc "c" (^A)
// B :: struct {x: C}
//
// A gets evaluated first, and then checks B.
// B then checks C.
// C then tries to check A which is unresolved but thought to be a constant.
// Therefore within C's check, A errs as "not a type".
//
// This is because a const declaration may or may not be a type and this cannot
// be determined from a syntactical standpoint.
// This check allows the compiler to override the entity to be checked as a type.
//
// There is no problem if B is prefixed with the `#type` helper enforcing at
// both a syntax and semantic level that B must be a type.
//
// A :: #type B
//
// This approach is not fool proof and can fail in case such as:
//
// X :: type_of(x)
// X :: Foo(int).Type
//
// Since even these kind of declarations may cause weird checking cycles.
// For the time being, these are going to be treated as an unfortunate error
// until there is a proper delaying system to try declaration again if they
// have failed.
e->kind = Entity_TypeName;
check_type_decl(ctx, e, init, named_type);
if (check_try_override_const_decl(ctx, e, entity, init, named_type)) {
return;
}
entity = nullptr;