mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-19 13:00:28 +00:00
Remove dead code from checker
This commit is contained in:
@@ -735,6 +735,14 @@ deprecated_attribute :: proc() {
|
||||
// foo_v1(1);
|
||||
}
|
||||
|
||||
A :: struct {x, y, z: int};
|
||||
B :: struct {x, y, z: int};
|
||||
C :: struct {
|
||||
using a: A,
|
||||
b: B,
|
||||
}
|
||||
|
||||
|
||||
main :: proc() {
|
||||
when true {
|
||||
general_stuff();
|
||||
|
||||
@@ -544,9 +544,6 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
|
||||
}
|
||||
pt->calling_convention = ProcCC_Contextless;
|
||||
if (e->pkg->kind == Package_Init) {
|
||||
gb_mutex_lock(&ctx->info->mutex);
|
||||
defer (gb_mutex_unlock(&ctx->info->mutex));
|
||||
|
||||
if (ctx->info->entry_point != nullptr) {
|
||||
error(e->token, "Redeclaration of the entry pointer procedure 'main'");
|
||||
} else {
|
||||
@@ -612,9 +609,6 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
|
||||
|
||||
init_entity_foreign_library(ctx, e);
|
||||
|
||||
gb_mutex_lock(&ctx->info->mutex);
|
||||
defer (gb_mutex_unlock(&ctx->info->mutex));
|
||||
|
||||
auto *fp = &ctx->info->foreigns;
|
||||
HashKey key = hash_string(name);
|
||||
Entity **found = map_get(fp, key);
|
||||
@@ -647,9 +641,6 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
|
||||
name = e->Procedure.link_name;
|
||||
}
|
||||
if (e->Procedure.link_name.len > 0 || is_export) {
|
||||
gb_mutex_lock(&ctx->info->mutex);
|
||||
defer (gb_mutex_unlock(&ctx->info->mutex));
|
||||
|
||||
auto *fp = &ctx->info->foreigns;
|
||||
HashKey key = hash_string(name);
|
||||
Entity **found = map_get(fp, key);
|
||||
@@ -726,8 +717,6 @@ void check_var_decl(CheckerContext *ctx, Entity *e, Entity **entities, isize ent
|
||||
if (e->Variable.link_name.len > 0) {
|
||||
name = e->Variable.link_name;
|
||||
}
|
||||
gb_mutex_lock(&ctx->info->mutex);
|
||||
defer (gb_mutex_unlock(&ctx->info->mutex));
|
||||
|
||||
auto *fp = &ctx->info->foreigns;
|
||||
HashKey key = hash_string(name);
|
||||
@@ -777,9 +766,8 @@ void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, DeclInfo *d)
|
||||
// places the entity within itself
|
||||
pg_entity->type = t_invalid;
|
||||
|
||||
PtrSet<Entity *> entity_map = {};
|
||||
ptr_set_init(&entity_map, heap_allocator());
|
||||
defer (ptr_set_destroy(&entity_map));
|
||||
PtrSet<Entity *> entity_set = {};
|
||||
ptr_set_init(&entity_set, heap_allocator(), 2*pg->args.count);
|
||||
|
||||
for_array(i, pg->args) {
|
||||
AstNode *arg = pg->args[i];
|
||||
@@ -806,14 +794,17 @@ void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, DeclInfo *d)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr_set_exists(&entity_map, e)) {
|
||||
if (ptr_set_exists(&entity_set, e)) {
|
||||
error(arg, "Previous use of `%.*s` in procedure group", LIT(e->token.string));
|
||||
continue;
|
||||
}
|
||||
ptr_set_add(&entity_map, e);
|
||||
ptr_set_add(&entity_set, e);
|
||||
array_add(&pge->entities, e);
|
||||
}
|
||||
|
||||
ptr_set_destroy(&entity_set);
|
||||
|
||||
|
||||
for_array(j, pge->entities) {
|
||||
Entity *p = pge->entities[j];
|
||||
if (p->type == t_invalid) {
|
||||
@@ -1021,6 +1012,7 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
|
||||
if (token.kind == Token_Ident) {
|
||||
error(bs->close, "Missing return statement at the end of the procedure '%.*s'", LIT(token.string));
|
||||
} else {
|
||||
// NOTE(bill): Anonymous procedure (lambda)
|
||||
error(bs->close, "Missing return statement at the end of the procedure");
|
||||
}
|
||||
}
|
||||
@@ -1029,9 +1021,6 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
|
||||
check_scope_usage(ctx->checker, ctx->scope);
|
||||
|
||||
if (decl->parent != nullptr) {
|
||||
gb_mutex_lock(&ctx->checker->mutex);
|
||||
defer (gb_mutex_unlock(&ctx->checker->mutex));
|
||||
|
||||
// NOTE(bill): Add the dependencies from the procedure literal (lambda)
|
||||
for_array(i, decl->deps.entries) {
|
||||
Entity *e = decl->deps.entries[i].ptr;
|
||||
|
||||
@@ -267,9 +267,6 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti
|
||||
}
|
||||
|
||||
|
||||
gb_mutex_lock(&nctx.info->mutex);
|
||||
defer (gb_mutex_unlock(&nctx.info->mutex));
|
||||
|
||||
auto *found_gen_procs = map_get(&nctx.info->gen_procs, hash_pointer(base_entity->identifier));
|
||||
if (found_gen_procs) {
|
||||
auto procs = *found_gen_procs;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
void populate_using_entity_map(CheckerContext *ctx, AstNode *node, Type *t, Map<Entity *> *entity_map) {
|
||||
void populate_using_entity_scope(CheckerContext *ctx, AstNode *node, Type *t) {
|
||||
t = base_type(type_deref(t));
|
||||
gbString str = nullptr;
|
||||
defer (gb_string_free(str));
|
||||
@@ -11,11 +11,9 @@ void populate_using_entity_map(CheckerContext *ctx, AstNode *node, Type *t, Map<
|
||||
for_array(i, t->Struct.fields) {
|
||||
Entity *f = t->Struct.fields[i];
|
||||
GB_ASSERT(f->kind == Entity_Variable);
|
||||
String name = f->token.string;
|
||||
HashKey key = hash_string(name);
|
||||
Entity **found = map_get(entity_map, key);
|
||||
if (found != nullptr && name != "_") {
|
||||
Entity *e = *found;
|
||||
String name = f->token.string;;
|
||||
Entity *e = current_scope_lookup_entity(ctx->scope, name);
|
||||
if (e != nullptr && name != "_") {
|
||||
// TODO(bill): Better type error
|
||||
if (str != nullptr) {
|
||||
error(e->token, "'%.*s' is already declared in '%s'", LIT(name), str);
|
||||
@@ -23,10 +21,9 @@ void populate_using_entity_map(CheckerContext *ctx, AstNode *node, Type *t, Map<
|
||||
error(e->token, "'%.*s' is already declared", LIT(name));
|
||||
}
|
||||
} else {
|
||||
map_set(entity_map, key, f);
|
||||
add_entity(ctx->checker, ctx->scope, nullptr, f);
|
||||
if (f->flags & EntityFlag_Using) {
|
||||
populate_using_entity_map(ctx, node, f->type, entity_map);
|
||||
populate_using_entity_scope(ctx, node, f->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,11 +35,6 @@ void check_struct_fields(CheckerContext *ctx, AstNode *node, Array<Entity *> *fi
|
||||
isize init_field_capacity, Type *named_type, String context) {
|
||||
*fields = array_make<Entity *>(heap_allocator(), 0, init_field_capacity);
|
||||
|
||||
Map<Entity *> entity_map = {};
|
||||
map_init(&entity_map, ctx->allocator, 2*init_field_capacity);
|
||||
defer (map_destroy(&entity_map));
|
||||
|
||||
|
||||
GB_ASSERT(node->kind == AstNode_StructType);
|
||||
|
||||
isize variable_count = 0;
|
||||
@@ -117,7 +109,7 @@ void check_struct_fields(CheckerContext *ctx, AstNode *node, Array<Entity *> *fi
|
||||
continue;
|
||||
}
|
||||
|
||||
populate_using_entity_map(ctx, node, type, &entity_map);
|
||||
populate_using_entity_scope(ctx, node, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,9 +158,6 @@ bool check_custom_align(CheckerContext *ctx, AstNode *node, i64 *align_) {
|
||||
|
||||
|
||||
Entity *find_polymorphic_struct_entity(CheckerContext *ctx, Type *original_type, isize param_count, Array<Operand> ordered_operands) {
|
||||
gb_mutex_lock(&ctx->checker->mutex);
|
||||
defer (gb_mutex_unlock(&ctx->checker->mutex));
|
||||
|
||||
auto *found_gen_types = map_get(&ctx->checker->info.gen_types, hash_pointer(original_type));
|
||||
if (found_gen_types != nullptr) {
|
||||
for_array(i, *found_gen_types) {
|
||||
@@ -258,6 +247,8 @@ void check_struct_type(CheckerContext *ctx, Type *struct_type, AstNode *node, Ar
|
||||
}
|
||||
struct_type->Struct.names = make_names_field_for_struct(ctx, ctx->scope);
|
||||
|
||||
scope_reserve(ctx->scope, min_field_count);
|
||||
|
||||
if (st->is_raw_union) {
|
||||
struct_type->Struct.is_raw_union = true;
|
||||
context = str_lit("struct #raw_union");
|
||||
@@ -513,10 +504,6 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
|
||||
enum_type->Enum.base_type = base_type;
|
||||
enum_type->Enum.scope = ctx->scope;
|
||||
|
||||
Map<Entity *> entity_map = {}; // Key: String
|
||||
map_init(&entity_map, ctx->allocator, 2*(et->fields.count));
|
||||
defer (map_destroy(&entity_map));
|
||||
|
||||
auto fields = array_make<Entity *>(ctx->allocator, 0, et->fields.count);
|
||||
|
||||
Type *constant_type = enum_type;
|
||||
@@ -528,6 +515,8 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
|
||||
ExactValue min_value = exact_value_i64(0);
|
||||
ExactValue max_value = exact_value_i64(0);
|
||||
|
||||
scope_reserve(ctx->scope, et->fields.count);
|
||||
|
||||
for_array(i, et->fields) {
|
||||
AstNode *field = et->fields[i];
|
||||
AstNode *ident = nullptr;
|
||||
@@ -600,13 +589,12 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
|
||||
e->flags |= EntityFlag_Visited;
|
||||
e->state = EntityState_Resolved;
|
||||
|
||||
HashKey key = hash_string(name);
|
||||
if (map_get(&entity_map, key) != nullptr) {
|
||||
if (current_scope_lookup_entity(ctx->scope, name) != nullptr) {
|
||||
error(ident, "'%.*s' is already declared in this enumeration", LIT(name));
|
||||
} else {
|
||||
map_set(&entity_map, key, e);
|
||||
add_entity(ctx->checker, ctx->scope, nullptr, e);
|
||||
array_add(&fields, e);
|
||||
// TODO(bill): Should I add a use for the enum value?
|
||||
add_entity_use(ctx, field, e);
|
||||
}
|
||||
}
|
||||
@@ -643,14 +631,12 @@ void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, AstNode *no
|
||||
ast_node(bft, BitFieldType, node);
|
||||
GB_ASSERT(is_type_bit_field(bit_field_type));
|
||||
|
||||
Map<Entity *> entity_map = {}; // Key: String
|
||||
map_init(&entity_map, ctx->allocator, 2*(bft->fields.count));
|
||||
defer (map_destroy(&entity_map));
|
||||
|
||||
auto fields = array_make<Entity*>(ctx->allocator, 0, bft->fields.count);
|
||||
auto sizes = array_make<u32> (ctx->allocator, 0, bft->fields.count);
|
||||
auto offsets = array_make<u32> (ctx->allocator, 0, bft->fields.count);
|
||||
|
||||
scope_reserve(ctx->scope, bft->fields.count);
|
||||
|
||||
u32 curr_offset = 0;
|
||||
for_array(i, bft->fields) {
|
||||
AstNode *field = bft->fields[i];
|
||||
@@ -687,13 +673,12 @@ void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, AstNode *no
|
||||
e->identifier = ident;
|
||||
e->flags |= EntityFlag_BitFieldValue;
|
||||
|
||||
HashKey key = hash_string(name);
|
||||
if (!is_blank_ident(name) &&
|
||||
map_get(&entity_map, key) != nullptr) {
|
||||
current_scope_lookup_entity(ctx->scope, name) != nullptr) {
|
||||
error(ident, "'%.*s' is already declared in this bit field", LIT(name));
|
||||
} else {
|
||||
map_set(&entity_map, key, e);
|
||||
add_entity(ctx->checker, ctx->scope, nullptr, e);
|
||||
// TODO(bill): Should this entity be "used"?
|
||||
add_entity_use(ctx, field, e);
|
||||
|
||||
array_add(&fields, e);
|
||||
|
||||
276
src/checker.cpp
276
src/checker.cpp
@@ -34,7 +34,13 @@ void scope_reset(Scope *scope) {
|
||||
map_clear (&scope->elements);
|
||||
ptr_set_clear(&scope->implicit);
|
||||
ptr_set_clear(&scope->imported);
|
||||
ptr_set_clear(&scope->exported);
|
||||
}
|
||||
|
||||
void scope_reserve(Scope *scope, isize capacity) {
|
||||
isize cap = 2*capacity;
|
||||
if (cap > scope->elements.hashes.capacity) {
|
||||
map_rehash(&scope->elements, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
i32 is_scope_an_ancestor(Scope *parent, Scope *child) {
|
||||
@@ -119,11 +125,10 @@ void import_graph_node_set_remove(ImportGraphNodeSet *s, ImportGraphNode *n) {
|
||||
ptr_set_remove(s, n);
|
||||
}
|
||||
|
||||
ImportGraphNode *import_graph_node_create(gbAllocator a, Scope *scope) {
|
||||
ImportGraphNode *import_graph_node_create(gbAllocator a, AstPackage *pkg) {
|
||||
ImportGraphNode *n = gb_alloc_item(a, ImportGraphNode);
|
||||
n->scope = scope;
|
||||
n->path = scope->package->fullpath;
|
||||
n->package_id = scope->package->id;
|
||||
n->pkg = pkg;
|
||||
n->scope = pkg->scope;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -144,7 +149,7 @@ int import_graph_node_cmp(ImportGraphNode **data, isize i, isize j) {
|
||||
bool xg = x->scope->is_global;
|
||||
bool yg = y->scope->is_global;
|
||||
if (xg != yg) return xg ? -1 : +1;
|
||||
if (xg && yg) return x->package_id < y->package_id ? +1 : -1;
|
||||
if (xg && yg) return x->pkg->id < y->pkg->id ? +1 : -1;
|
||||
if (x->dep_count < y->dep_count) return -1;
|
||||
if (x->dep_count > y->dep_count) return +1;
|
||||
return 0;
|
||||
@@ -218,10 +223,9 @@ Scope *create_scope(Scope *parent, gbAllocator allocator, isize init_elements_ca
|
||||
map_init(&s->elements, heap_allocator(), init_elements_capacity);
|
||||
ptr_set_init(&s->implicit, heap_allocator(), 0);
|
||||
ptr_set_init(&s->imported, heap_allocator(), 0);
|
||||
ptr_set_init(&s->exported, heap_allocator(), 0);
|
||||
|
||||
s->delayed_imports.allocator = heap_allocator();
|
||||
s->delayed_asserts.allocator = heap_allocator();
|
||||
s->delayed_directives.allocator = heap_allocator();
|
||||
|
||||
if (parent != nullptr && parent != universal_scope) {
|
||||
DLIST_APPEND(parent->first_child, parent->last_child, s);
|
||||
@@ -237,7 +241,7 @@ Scope *create_scope_from_file(CheckerContext *c, AstFile *f) {
|
||||
Scope *s = create_scope(f->pkg->scope, c->allocator);
|
||||
|
||||
array_reserve(&s->delayed_imports, f->imports.count);
|
||||
array_reserve(&s->delayed_asserts, f->assert_decl_count);
|
||||
array_reserve(&s->delayed_directives, f->assert_decl_count);
|
||||
|
||||
s->is_file = true;
|
||||
s->file = f;
|
||||
@@ -296,10 +300,9 @@ void destroy_scope(Scope *scope) {
|
||||
|
||||
map_destroy(&scope->elements);
|
||||
array_free(&scope->delayed_imports);
|
||||
array_free(&scope->delayed_asserts);
|
||||
array_free(&scope->delayed_directives);
|
||||
ptr_set_destroy(&scope->implicit);
|
||||
ptr_set_destroy(&scope->imported);
|
||||
ptr_set_destroy(&scope->exported);
|
||||
|
||||
// NOTE(bill): No need to free scope as it "should" be allocated in an arena (except for the global scope)
|
||||
}
|
||||
@@ -623,7 +626,6 @@ void init_checker_info(CheckerInfo *i) {
|
||||
map_init(&i->files, a);
|
||||
map_init(&i->packages, a);
|
||||
array_init(&i->variable_init_order, a);
|
||||
gb_mutex_init(&i->mutex);
|
||||
}
|
||||
|
||||
void destroy_checker_info(CheckerInfo *i) {
|
||||
@@ -639,7 +641,6 @@ void destroy_checker_info(CheckerInfo *i) {
|
||||
map_destroy(&i->files);
|
||||
map_destroy(&i->packages);
|
||||
array_free(&i->variable_init_order);
|
||||
gb_mutex_destroy(&i->mutex);
|
||||
}
|
||||
|
||||
CheckerContext make_checker_context(Checker *c) {
|
||||
@@ -666,7 +667,6 @@ void init_checker(Checker *c, Parser *parser) {
|
||||
|
||||
c->parser = parser;
|
||||
init_checker_info(&c->info);
|
||||
gb_mutex_init(&c->mutex);
|
||||
|
||||
array_init(&c->procs_to_check, a);
|
||||
|
||||
@@ -677,26 +677,16 @@ void init_checker(Checker *c, Parser *parser) {
|
||||
|
||||
c->allocator = heap_allocator();
|
||||
|
||||
isize pkg_cap = 2*c->parser->packages.count;
|
||||
|
||||
map_init(&c->package_scopes, heap_allocator(), pkg_cap);
|
||||
|
||||
array_init(&c->package_order, heap_allocator(), 0, c->parser->packages.count);
|
||||
|
||||
c->init_ctx = make_checker_context(c);
|
||||
}
|
||||
|
||||
void destroy_checker(Checker *c) {
|
||||
destroy_checker_info(&c->info);
|
||||
gb_mutex_destroy(&c->mutex);
|
||||
|
||||
array_free(&c->procs_to_check);
|
||||
|
||||
// gb_arena_free(&c->tmp_arena);
|
||||
|
||||
map_destroy(&c->package_scopes);
|
||||
array_free(&c->package_order);
|
||||
|
||||
destroy_checker_context(&c->init_ctx);
|
||||
}
|
||||
|
||||
@@ -709,9 +699,6 @@ Entity *entity_of_ident(AstNode *identifier) {
|
||||
}
|
||||
|
||||
TypeAndValue type_and_value_of_expr(CheckerInfo *i, AstNode *expr) {
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
|
||||
TypeAndValue result = {};
|
||||
TypeAndValue *found = map_get(&i->types, hash_node(expr));
|
||||
if (found) result = *found;
|
||||
@@ -719,9 +706,6 @@ TypeAndValue type_and_value_of_expr(CheckerInfo *i, AstNode *expr) {
|
||||
}
|
||||
|
||||
Type *type_of_expr(CheckerInfo *i, AstNode *expr) {
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
|
||||
TypeAndValue tav = type_and_value_of_expr(i, expr);
|
||||
if (tav.mode != Addressing_Invalid) {
|
||||
return tav.type;
|
||||
@@ -784,8 +768,6 @@ DeclInfo *decl_info_of_ident(AstNode *ident) {
|
||||
}
|
||||
|
||||
AstFile *ast_file_of_filename(CheckerInfo *i, String filename) {
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
AstFile **found = map_get(&i->files, hash_string(filename));
|
||||
if (found != nullptr) {
|
||||
return *found;
|
||||
@@ -796,18 +778,12 @@ Scope *scope_of_node(AstNode *node) {
|
||||
return node->scope;
|
||||
}
|
||||
ExprInfo *check_get_expr_info(CheckerInfo *i, AstNode *expr) {
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
return map_get(&i->untyped, hash_node(expr));
|
||||
}
|
||||
void check_set_expr_info(CheckerInfo *i, AstNode *expr, ExprInfo info) {
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
map_set(&i->untyped, hash_node(expr), info);
|
||||
}
|
||||
void check_remove_expr_info(CheckerInfo *i, AstNode *expr) {
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
map_remove(&i->untyped, hash_node(expr));
|
||||
}
|
||||
|
||||
@@ -819,9 +795,6 @@ isize type_info_index(CheckerInfo *info, Type *type, bool error_on_failure) {
|
||||
type = t_bool;
|
||||
}
|
||||
|
||||
gb_mutex_lock(&info->mutex);
|
||||
defer (gb_mutex_unlock(&info->mutex));
|
||||
|
||||
isize entry_index = -1;
|
||||
HashKey key = hash_type(type);
|
||||
isize *found_entry_index = map_get(&info->type_info_map, key);
|
||||
@@ -860,8 +833,6 @@ void add_untyped(CheckerInfo *i, AstNode *expression, bool lhs, AddressingMode m
|
||||
if (mode == Addressing_Constant && type == t_invalid) {
|
||||
compiler_error("add_untyped - invalid type: %s", type_to_string(type));
|
||||
}
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
map_set(&i->untyped, hash_node(expression), make_expr_info(mode, type, value, lhs));
|
||||
}
|
||||
|
||||
@@ -876,9 +847,6 @@ void add_type_and_value(CheckerInfo *i, AstNode *expression, AddressingMode mode
|
||||
compiler_error("add_type_and_value - invalid type: %s", type_to_string(type));
|
||||
}
|
||||
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
|
||||
TypeAndValue tv = {};
|
||||
tv.type = type;
|
||||
tv.value = value;
|
||||
@@ -898,9 +866,6 @@ void add_entity_definition(CheckerInfo *i, AstNode *identifier, Entity *entity)
|
||||
}
|
||||
GB_ASSERT(entity != nullptr);
|
||||
|
||||
gb_mutex_lock(&i->mutex);
|
||||
defer (gb_mutex_unlock(&i->mutex));
|
||||
|
||||
identifier->Ident.entity = entity;
|
||||
entity->identifier = identifier;
|
||||
array_add(&i->definitions, entity);
|
||||
@@ -1030,9 +995,6 @@ void add_type_info_type(CheckerContext *c, Type *t) {
|
||||
return;
|
||||
}
|
||||
|
||||
gb_mutex_lock(&c->info->mutex);
|
||||
defer (gb_mutex_unlock(&c->info->mutex));
|
||||
|
||||
auto found = map_get(&c->info->type_info_map, hash_type(t));
|
||||
if (found != nullptr) {
|
||||
// Types have already been added
|
||||
@@ -1171,9 +1133,7 @@ void add_type_info_type(CheckerContext *c, Type *t) {
|
||||
|
||||
void check_procedure_later(Checker *c, ProcedureInfo info) {
|
||||
GB_ASSERT(info.decl != nullptr);
|
||||
gb_mutex_lock(&c->mutex);
|
||||
array_add(&c->procs_to_check, info);
|
||||
gb_mutex_unlock(&c->mutex);
|
||||
}
|
||||
|
||||
void check_procedure_later(Checker *c, AstFile *file, Token token, DeclInfo *decl, Type *type, AstNode *body, u64 tags) {
|
||||
@@ -2176,16 +2136,12 @@ void check_add_foreign_block_decl(CheckerContext *ctx, AstNode *decl) {
|
||||
void check_collect_entities(CheckerContext *c, Array<AstNode *> nodes) {
|
||||
for_array(decl_index, nodes) {
|
||||
AstNode *decl = nodes[decl_index];
|
||||
if (c->scope->is_file) {
|
||||
}
|
||||
if (!is_ast_node_decl(decl) && !is_ast_node_when_stmt(decl)) {
|
||||
|
||||
if (c->scope->is_file && decl->kind == AstNode_ExprStmt) {
|
||||
AstNode *expr = decl->ExprStmt.expr;
|
||||
if (expr->kind == AstNode_CallExpr &&
|
||||
expr->CallExpr.proc->kind == AstNode_BasicDirective &&
|
||||
expr->CallExpr.proc->BasicDirective.name == "assert") {
|
||||
array_add(&c->scope->delayed_asserts, expr);
|
||||
expr->CallExpr.proc->kind == AstNode_BasicDirective) {
|
||||
array_add(&c->scope->delayed_directives, expr);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2368,46 +2324,43 @@ String path_to_entity_name(String name, String fullpath) {
|
||||
#if 1
|
||||
|
||||
void add_import_dependency_node(Checker *c, AstNode *decl, Map<ImportGraphNode *> *M) {
|
||||
Scope *parent_package_scope = decl->file->pkg->scope;
|
||||
AstPackage *parent_pkg = decl->file->pkg;
|
||||
|
||||
switch (decl->kind) {
|
||||
case_ast_node(id, ImportDecl, decl);
|
||||
String path = id->fullpath;
|
||||
HashKey key = hash_string(path);
|
||||
Scope **found = map_get(&c->package_scopes, key);
|
||||
AstPackage **found = map_get(&c->info.packages, key);
|
||||
if (found == nullptr) {
|
||||
for_array(scope_index, c->package_scopes.entries) {
|
||||
Scope *scope = c->package_scopes.entries[scope_index].value;
|
||||
gb_printf_err("%.*s\n", LIT(scope->package->fullpath));
|
||||
for_array(pkg_index, c->info.packages.entries) {
|
||||
AstPackage *pkg = c->info.packages.entries[pkg_index].value;
|
||||
gb_printf_err("%.*s\n", LIT(pkg->fullpath));
|
||||
}
|
||||
Token token = ast_node_token(decl);
|
||||
gb_printf_err("%.*s(%td:%td)\n", LIT(token.pos.file), token.pos.line, token.pos.column);
|
||||
GB_PANIC("Unable to find scope for file: %.*s", LIT(path));
|
||||
GB_PANIC("Unable to find package: %.*s", LIT(path));
|
||||
}
|
||||
Scope *scope = *found;
|
||||
GB_ASSERT(scope != nullptr);
|
||||
AstPackage *pkg = *found;
|
||||
GB_ASSERT(pkg->scope != nullptr);
|
||||
|
||||
id->package = scope->package;
|
||||
id->package = pkg;
|
||||
|
||||
ImportGraphNode **found_node = nullptr;
|
||||
ImportGraphNode *m = nullptr;
|
||||
ImportGraphNode *n = nullptr;
|
||||
|
||||
found_node = map_get(M, hash_pointer(scope));
|
||||
found_node = map_get(M, hash_pointer(pkg));
|
||||
GB_ASSERT(found_node != nullptr);
|
||||
m = *found_node;
|
||||
|
||||
found_node = map_get(M, hash_pointer(parent_package_scope));
|
||||
found_node = map_get(M, hash_pointer(parent_pkg));
|
||||
GB_ASSERT(found_node != nullptr);
|
||||
n = *found_node;
|
||||
|
||||
// TODO(bill): How should the edges be attched for 'import'?
|
||||
// TODO(bill): How should the edges be attached for 'import'?
|
||||
import_graph_node_set_add(&n->succ, m);
|
||||
import_graph_node_set_add(&m->pred, n);
|
||||
ptr_set_add(&m->scope->imported, n->scope);
|
||||
if (id->is_using) {
|
||||
ptr_set_add(&m->scope->exported, n->scope);
|
||||
}
|
||||
case_end;
|
||||
|
||||
case_ast_node(ws, WhenStmt, decl);
|
||||
@@ -2438,17 +2391,14 @@ void add_import_dependency_node(Checker *c, AstNode *decl, Map<ImportGraphNode *
|
||||
}
|
||||
|
||||
Array<ImportGraphNode *> generate_import_dependency_graph(Checker *c) {
|
||||
gbAllocator a = heap_allocator();
|
||||
|
||||
Map<ImportGraphNode *> M = {}; // Key: Scope *
|
||||
map_init(&M, a);
|
||||
Map<ImportGraphNode *> M = {}; // Key: AstPackage *
|
||||
map_init(&M, heap_allocator(), 2*c->parser->packages.count);
|
||||
defer (map_destroy(&M));
|
||||
|
||||
for_array(i, c->parser->packages) {
|
||||
Scope *scope = c->parser->packages[i]->scope;
|
||||
|
||||
ImportGraphNode *n = import_graph_node_create(heap_allocator(), scope);
|
||||
map_set(&M, hash_pointer(scope), n);
|
||||
AstPackage *pkg = c->parser->packages[i];
|
||||
ImportGraphNode *n = import_graph_node_create(heap_allocator(), pkg);
|
||||
map_set(&M, hash_pointer(pkg), n);
|
||||
}
|
||||
|
||||
// Calculate edges for graph M
|
||||
@@ -2464,28 +2414,25 @@ Array<ImportGraphNode *> generate_import_dependency_graph(Checker *c) {
|
||||
}
|
||||
|
||||
Array<ImportGraphNode *> G = {};
|
||||
array_init(&G, a);
|
||||
array_init(&G, heap_allocator(), 0, M.entries.count);
|
||||
|
||||
for_array(i, M.entries) {
|
||||
array_add(&G, M.entries[i].value);
|
||||
}
|
||||
|
||||
for_array(i, G) {
|
||||
ImportGraphNode *n = G[i];
|
||||
auto n = M.entries[i].value;
|
||||
n->index = i;
|
||||
n->dep_count = n->succ.entries.count;
|
||||
GB_ASSERT(n->dep_count >= 0);
|
||||
array_add(&G, n);
|
||||
}
|
||||
|
||||
return G;
|
||||
}
|
||||
|
||||
struct ImportPathItem {
|
||||
Scope * scope;
|
||||
AstNode *decl;
|
||||
AstPackage *pkg;
|
||||
AstNode * decl;
|
||||
};
|
||||
|
||||
Array<ImportPathItem> find_import_path(Checker *c, Scope *start, Scope *end, PtrSet<Scope *> *visited) {
|
||||
Array<ImportPathItem> find_import_path(Checker *c, AstPackage *start, AstPackage *end, PtrSet<AstPackage *> *visited) {
|
||||
Array<ImportPathItem> empty_path = {};
|
||||
|
||||
if (ptr_set_exists(visited, start)) {
|
||||
@@ -2494,32 +2441,32 @@ Array<ImportPathItem> find_import_path(Checker *c, Scope *start, Scope *end, Ptr
|
||||
ptr_set_add(visited, start);
|
||||
|
||||
|
||||
String path = start->package->fullpath;
|
||||
String path = start->fullpath;
|
||||
HashKey key = hash_string(path);
|
||||
Scope **found = map_get(&c->package_scopes, key);
|
||||
AstPackage **found = map_get(&c->info.packages, key);
|
||||
if (found) {
|
||||
AstPackage *p = (*found)->package;
|
||||
GB_ASSERT(p != nullptr);
|
||||
AstPackage *pkg = *found;
|
||||
GB_ASSERT(pkg != nullptr);
|
||||
|
||||
for_array(i, p->files) {
|
||||
AstFile *f = p->files[i];
|
||||
for_array(i, pkg->files) {
|
||||
AstFile *f = pkg->files[i];
|
||||
for_array(j, f->imports) {
|
||||
Scope *s = nullptr;
|
||||
AstPackage *pkg = nullptr;
|
||||
AstNode *decl = f->imports[j];
|
||||
if (decl->kind == AstNode_ImportDecl) {
|
||||
s = decl->ImportDecl.package->scope;
|
||||
pkg = decl->ImportDecl.package;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
GB_ASSERT(s != nullptr && s->is_package);
|
||||
GB_ASSERT(pkg != nullptr && pkg->scope != nullptr);
|
||||
|
||||
ImportPathItem item = {s, decl};
|
||||
if (s == end) {
|
||||
ImportPathItem item = {pkg, decl};
|
||||
if (pkg == end) {
|
||||
auto path = array_make<ImportPathItem>(heap_allocator());
|
||||
array_add(&path, item);
|
||||
return path;
|
||||
}
|
||||
auto next_path = find_import_path(c, s, end, visited);
|
||||
auto next_path = find_import_path(c, pkg, end, visited);
|
||||
if (next_path.count > 0) {
|
||||
array_add(&next_path, item);
|
||||
return next_path;
|
||||
@@ -2534,27 +2481,25 @@ void check_add_import_decl(CheckerContext *ctx, AstNodeImportDecl *id) {
|
||||
if (id->been_handled) return;
|
||||
id->been_handled = true;
|
||||
|
||||
gb_mutex_lock(&ctx->checker->mutex);
|
||||
defer (gb_mutex_unlock(&ctx->checker->mutex));
|
||||
|
||||
Scope *parent_scope = ctx->scope;
|
||||
GB_ASSERT(parent_scope->is_file);
|
||||
|
||||
auto *pkg_scopes = &ctx->checker->package_scopes;
|
||||
auto *pkgs = &ctx->checker->info.packages;
|
||||
|
||||
Token token = id->relpath;
|
||||
HashKey key = hash_string(id->fullpath);
|
||||
Scope **found = map_get(pkg_scopes, key);
|
||||
AstPackage **found = map_get(pkgs, key);
|
||||
if (found == nullptr) {
|
||||
for_array(scope_index, pkg_scopes->entries) {
|
||||
Scope *scope = pkg_scopes->entries[scope_index].value;
|
||||
gb_printf_err("%.*s\n", LIT(scope->package->fullpath));
|
||||
for_array(pkg_index, pkgs->entries) {
|
||||
AstPackage *pkg = pkgs->entries[pkg_index].value;
|
||||
gb_printf_err("%.*s\n", LIT(pkg->fullpath));
|
||||
}
|
||||
gb_printf_err("%.*s(%td:%td)\n", LIT(token.pos.file), token.pos.line, token.pos.column);
|
||||
GB_PANIC("Unable to find scope for package: %.*s", LIT(id->fullpath));
|
||||
}
|
||||
Scope *scope = *found;
|
||||
GB_ASSERT(scope->is_package && scope->package != nullptr);
|
||||
AstPackage *pkg = *found;
|
||||
Scope *scope = pkg->scope;
|
||||
GB_ASSERT(scope->is_package);
|
||||
|
||||
// TODO(bill): Should this be allowed or not?
|
||||
// if (scope->is_global) {
|
||||
@@ -2707,29 +2652,30 @@ void check_import_entities(Checker *c) {
|
||||
// NOTE(bill): Priority queue
|
||||
auto pq = priority_queue_create(dep_graph, import_graph_node_cmp, import_graph_node_swap);
|
||||
|
||||
PtrSet<Scope *> emitted = {};
|
||||
PtrSet<AstPackage *> emitted = {};
|
||||
ptr_set_init(&emitted, heap_allocator());
|
||||
defer (ptr_set_destroy(&emitted));
|
||||
|
||||
Array<ImportGraphNode *> package_order = {};
|
||||
array_init(&package_order, heap_allocator(), 0, c->parser->packages.count);
|
||||
defer (array_free(&package_order));
|
||||
|
||||
while (pq.queue.count > 0) {
|
||||
ImportGraphNode *n = priority_queue_pop(&pq);
|
||||
|
||||
Scope *s = n->scope;
|
||||
AstPackage *pkg = n->pkg;
|
||||
|
||||
if (n->dep_count > 0) {
|
||||
PtrSet<Scope *> visited = {};
|
||||
PtrSet<AstPackage *> visited = {};
|
||||
ptr_set_init(&visited, heap_allocator());
|
||||
defer (ptr_set_destroy(&visited));
|
||||
|
||||
auto path = find_import_path(c, s, s, &visited);
|
||||
auto path = find_import_path(c, pkg, pkg, &visited);
|
||||
defer (array_free(&path));
|
||||
|
||||
// TODO(bill): This needs better TokenPos finding
|
||||
auto const fn = [](ImportPathItem item) -> String {
|
||||
Scope *s = item.scope;
|
||||
// return remove_directory_from_path(s->package->fullpath);
|
||||
return s->package->name;
|
||||
return item.pkg->name;
|
||||
};
|
||||
|
||||
if (path.count == 1) {
|
||||
@@ -2759,24 +2705,24 @@ void check_import_entities(Checker *c) {
|
||||
priority_queue_fix(&pq, p->index);
|
||||
}
|
||||
|
||||
if (s == nullptr) {
|
||||
if (pkg == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (ptr_set_exists(&emitted, s)) {
|
||||
if (ptr_set_exists(&emitted, pkg)) {
|
||||
continue;
|
||||
}
|
||||
ptr_set_add(&emitted, s);
|
||||
ptr_set_add(&emitted, pkg);
|
||||
|
||||
array_add(&c->package_order, n);
|
||||
array_add(&package_order, n);
|
||||
}
|
||||
|
||||
for_array(i, c->package_order) {
|
||||
ImportGraphNode *node = c->package_order[i];
|
||||
for_array(i, package_order) {
|
||||
ImportGraphNode *node = package_order[i];
|
||||
GB_ASSERT(node->scope->is_package);
|
||||
AstPackage *p = node->scope->package;
|
||||
AstPackage *pkg = node->scope->package;
|
||||
|
||||
for_array(i, p->files) {
|
||||
AstFile *f = p->files[i];
|
||||
for_array(i, pkg->files) {
|
||||
AstFile *f = pkg->files[i];
|
||||
CheckerContext ctx = c->init_ctx;
|
||||
|
||||
add_curr_ast_file(&ctx, f);
|
||||
@@ -2787,21 +2733,17 @@ void check_import_entities(Checker *c) {
|
||||
}
|
||||
}
|
||||
|
||||
for_array(i, p->files) {
|
||||
AstFile *f = p->files[i];
|
||||
for_array(i, pkg->files) {
|
||||
AstFile *f = pkg->files[i];
|
||||
CheckerContext ctx = c->init_ctx;
|
||||
add_curr_ast_file(&ctx, f);
|
||||
for_array(j, f->scope->delayed_asserts) {
|
||||
AstNode *expr = f->scope->delayed_asserts[j];
|
||||
for_array(j, f->scope->delayed_directives) {
|
||||
AstNode *expr = f->scope->delayed_directives[j];
|
||||
Operand o = {};
|
||||
check_expr(&ctx, &o, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// gb_printf_err("End here!\n");
|
||||
// gb_exit(1);
|
||||
}
|
||||
|
||||
Array<Entity *> find_entity_path(Entity *start, Entity *end, Map<Entity *> *visited = nullptr) {
|
||||
@@ -2979,58 +2921,6 @@ GB_THREAD_PROC(check_proc_info_worker_proc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void check_proc_bodies(Checker *c) {
|
||||
// IMPORTANT TODO(bill): Figure out why this doesn't work on *nix sometimes
|
||||
#if 0 && defined(GB_SYSTEM_WINDOWS)
|
||||
isize thread_count = gb_max(build_context.thread_count, 1);
|
||||
gb_printf_err("Threads: %td\n", thread_count);
|
||||
// isize thread_count = 1;
|
||||
if (thread_count > 1) {
|
||||
isize volatile curr_proc_index = 0;
|
||||
|
||||
auto worker_threads = array_make<gbThread>(heap_allocator(), thread_count);
|
||||
defer (array_free(&worker_threads));
|
||||
|
||||
for_array(i, worker_threads) {
|
||||
gbThread *t = &worker_threads[i];
|
||||
gb_thread_init(t);
|
||||
}
|
||||
defer (for_array(i, worker_threads) {
|
||||
gb_thread_destroy(&worker_threads[i]);
|
||||
});
|
||||
|
||||
|
||||
for (;;) {
|
||||
bool are_any_alive = false;
|
||||
for_array(i, worker_threads) {
|
||||
gbThread *t = &worker_threads[i];
|
||||
if (gb_thread_is_running(t)) {
|
||||
are_any_alive = true;
|
||||
} else if (curr_proc_index < c->procs_to_check.count) {
|
||||
t->user_index = curr_proc_index;
|
||||
curr_proc_index++;
|
||||
gb_thread_start(t, check_proc_info_worker_proc, c);
|
||||
are_any_alive = true;
|
||||
}
|
||||
}
|
||||
if (!are_any_alive && curr_proc_index >= c->procs_to_check.count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
for_array(i, c->procs_to_check) {
|
||||
ProcedureInfo pi = c->procs_to_check[i];
|
||||
check_proc_info(c, pi);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for_array(i, c->procs_to_check) {
|
||||
ProcedureInfo pi = c->procs_to_check[i];
|
||||
check_proc_info(c, pi);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void check_parsed_files(Checker *c) {
|
||||
#if 0
|
||||
@@ -3054,7 +2944,6 @@ void check_parsed_files(Checker *c) {
|
||||
Scope *scope = create_scope_from_package(&c->init_ctx, p);
|
||||
p->decl_info = make_decl_info(c->allocator, scope, c->init_ctx.decl);
|
||||
HashKey key = hash_string(p->fullpath);
|
||||
map_set(&c->package_scopes, key, scope);
|
||||
map_set(&c->info.packages, key, p);
|
||||
|
||||
if (scope->is_init) {
|
||||
@@ -3100,7 +2989,10 @@ void check_parsed_files(Checker *c) {
|
||||
|
||||
TIME_SECTION("check procedure bodies");
|
||||
// NOTE(bill): Nested procedures bodies will be added to this "queue"
|
||||
check_proc_bodies(c);
|
||||
for_array(i, c->procs_to_check) {
|
||||
ProcedureInfo pi = c->procs_to_check[i];
|
||||
check_proc_info(c, pi);
|
||||
}
|
||||
|
||||
for_array(i, c->info.files.entries) {
|
||||
AstFile *f = c->info.files.entries[i].value;
|
||||
|
||||
@@ -220,10 +220,9 @@ struct Scope {
|
||||
PtrSet<Entity *> implicit;
|
||||
Scope * shared;
|
||||
|
||||
Array<AstNode *> delayed_asserts;
|
||||
Array<AstNode *> delayed_directives;
|
||||
Array<AstNode *> delayed_imports;
|
||||
PtrSet<Scope *> imported;
|
||||
PtrSet<Scope *> exported; // NOTE(bhall): Contains 'using import' too
|
||||
bool is_proc;
|
||||
bool is_global;
|
||||
bool is_package;
|
||||
@@ -259,9 +258,8 @@ typedef PtrSet<ImportGraphNode *> ImportGraphNodeSet;
|
||||
|
||||
|
||||
struct ImportGraphNode {
|
||||
AstPackage * pkg;
|
||||
Scope * scope;
|
||||
String path;
|
||||
isize package_id;
|
||||
ImportGraphNodeSet pred;
|
||||
ImportGraphNodeSet succ;
|
||||
isize index; // Index in array/queue
|
||||
@@ -278,6 +276,31 @@ struct ForeignContext {
|
||||
|
||||
typedef Array<Entity *> CheckerTypePath;
|
||||
|
||||
// CheckerInfo stores all the symbol information for a type-checked program
|
||||
struct CheckerInfo {
|
||||
Map<TypeAndValue> types; // Key: AstNode * | Expression -> Type (and value)
|
||||
Map<ExprInfo> untyped; // Key: AstNode * | Expression -> ExprInfo
|
||||
Map<AstFile *> files; // Key: String (full path)
|
||||
Map<AstPackage *> packages; // Key: String (full path)
|
||||
Map<Entity *> foreigns; // Key: String
|
||||
Array<Entity *> definitions;
|
||||
Array<Entity *> entities;
|
||||
Array<DeclInfo *> variable_init_order;
|
||||
|
||||
Map<Array<Entity *> > gen_procs; // Key: AstNode * | Identifier -> Entity
|
||||
Map<Array<Entity *> > gen_types; // Key: Type *
|
||||
|
||||
Array<Type *> type_info_types;
|
||||
Map<isize> type_info_map; // Key: Type *
|
||||
|
||||
|
||||
AstPackage * runtime_package;
|
||||
Scope * init_scope;
|
||||
Entity * entry_point;
|
||||
PtrSet<Entity *> minimum_dependency_set;
|
||||
PtrSet<isize> minimum_dependency_type_info_set;
|
||||
};
|
||||
|
||||
struct CheckerContext {
|
||||
Checker * checker;
|
||||
CheckerInfo * info;
|
||||
@@ -305,49 +328,14 @@ struct CheckerContext {
|
||||
Scope * polymorphic_scope;
|
||||
};
|
||||
|
||||
|
||||
// CheckerInfo stores all the symbol information for a type-checked program
|
||||
struct CheckerInfo {
|
||||
Map<TypeAndValue> types; // Key: AstNode * | Expression -> Type (and value)
|
||||
Map<ExprInfo> untyped; // Key: AstNode * | Expression -> ExprInfo
|
||||
Map<AstFile *> files; // Key: String (full path)
|
||||
Map<AstPackage *> packages; // Key: String (full path)
|
||||
Map<Entity *> foreigns; // Key: String
|
||||
Array<Entity *> definitions;
|
||||
Array<Entity *> entities;
|
||||
Array<DeclInfo *> variable_init_order;
|
||||
|
||||
Map<Array<Entity *> > gen_procs; // Key: AstNode * | Identifier -> Entity
|
||||
Map<Array<Entity *> > gen_types; // Key: Type *
|
||||
|
||||
Array<Type *> type_info_types;
|
||||
Map<isize> type_info_map; // Key: Type *
|
||||
|
||||
|
||||
AstPackage * runtime_package;
|
||||
Scope * init_scope;
|
||||
Entity * entry_point;
|
||||
PtrSet<Entity *> minimum_dependency_set;
|
||||
PtrSet<isize> minimum_dependency_type_info_set;
|
||||
|
||||
gbMutex mutex;
|
||||
};
|
||||
|
||||
struct Checker {
|
||||
Parser * parser;
|
||||
CheckerInfo info;
|
||||
gbMutex mutex;
|
||||
|
||||
|
||||
Array<ProcedureInfo> procs_to_check;
|
||||
Map<Scope *> package_scopes; // Key: String (fullpath)
|
||||
Array<ImportGraphNode *> package_order;
|
||||
|
||||
gbAllocator allocator;
|
||||
|
||||
CheckerContext init_ctx;
|
||||
|
||||
bool done_preload;
|
||||
Array<ProcedureInfo> procs_to_check;
|
||||
gbAllocator allocator;
|
||||
CheckerContext init_ctx;
|
||||
bool done_preload;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user