From 6202fb83738027a312c88f544c121b65823313c1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 2 Jun 2018 19:44:34 +0100 Subject: [PATCH] Re-allow `when` statements at the file scope --- examples/demo/demo.odin | 8 +- src/checker.cpp | 258 ++++++++++++++++++++++++++++++++++++---- src/checker.hpp | 7 +- src/main.cpp | 11 ++ src/parser.cpp | 29 ++++- 5 files changed, 286 insertions(+), 27 deletions(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 7aef164e4..90663d7b3 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -16,9 +16,11 @@ import "core:unicode/utf16" import "core:unicode/utf8" import "core:c" -import "core:atomics" -import "core:thread" -import "core:sys/win32" +when os.OS == "windows" { + import "core:atomics" + import "core:thread" + import "core:sys/win32" +} @(link_name="general_stuff") general_stuff :: proc() { diff --git a/src/checker.cpp b/src/checker.cpp index fec46d10f..ddcf976e2 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -669,6 +669,7 @@ void init_checker(Checker *c, Parser *parser) { init_checker_info(&c->info); array_init(&c->procs_to_check, a); + ptr_set_init(&c->checked_packages, a); // NOTE(bill): Is this big enough or too small? isize item_size = gb_max3(gb_size_of(Entity), gb_size_of(Type), gb_size_of(Scope)); @@ -684,8 +685,7 @@ void destroy_checker(Checker *c) { destroy_checker_info(&c->info); array_free(&c->procs_to_check); - - // gb_arena_free(&c->tmp_arena); + ptr_set_destroy(&c->checked_packages); destroy_checker_context(&c->init_ctx); } @@ -1800,7 +1800,7 @@ DECL_ATTRIBUTE_PROC(var_decl_attribute) { -void check_decl_attributes(CheckerContext *c, Array attributes, DeclAttributeProc *proc, AttributeContext *ac) { +void check_decl_attributes(CheckerContext *c, Array const &attributes, DeclAttributeProc *proc, AttributeContext *ac) { if (attributes.count == 0) return; String original_link_prefix = {}; @@ -2129,19 +2129,21 @@ void check_add_foreign_block_decl(CheckerContext *ctx, AstNode *decl) { check_decl_attributes(&c, fb->attributes, foreign_block_decl_attribute, nullptr); + c.collect_delayed_decls = true; check_collect_entities(&c, fb->decls); } // NOTE(bill): If file_scopes == nullptr, this will act like a local scope -void check_collect_entities(CheckerContext *c, Array nodes) { +void check_collect_entities(CheckerContext *c, Array const &nodes) { for_array(decl_index, nodes) { AstNode *decl = nodes[decl_index]; 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) { - array_add(&c->scope->delayed_directives, expr); + if (expr->kind == AstNode_CallExpr && expr->CallExpr.proc->kind == AstNode_BasicDirective) { + if (c->collect_delayed_decls) { + array_add(&c->scope->delayed_directives, expr); + } continue; } } @@ -2167,7 +2169,9 @@ void check_collect_entities(CheckerContext *c, Array nodes) { // TODO(bill): Better error handling if it isn't continue; } - array_add(&c->scope->delayed_imports, decl); + if (c->collect_delayed_decls) { + array_add(&c->scope->delayed_imports, decl); + } case_end; case_ast_node(fl, ForeignImportDecl, decl); @@ -2194,7 +2198,7 @@ void check_collect_entities(CheckerContext *c, Array nodes) { // NOTE(bill): 'when' stmts need to be handled after the other as the condition may refer to something // declared after this stmt in source - if (!c->scope->is_file) { + if (!c->scope->is_file || c->collect_delayed_decls) { for_array(i, nodes) { AstNode *node = nodes[i]; switch (node->kind) { @@ -2591,6 +2595,7 @@ void check_add_import_decl(CheckerContext *ctx, AstNodeImportDecl *id) { } } + ptr_set_add(&ctx->checker->checked_packages, pkg); scope->has_been_imported = true; } @@ -2638,7 +2643,163 @@ void check_add_foreign_import_decl(CheckerContext *ctx, AstNode *decl) { add_entity(ctx->checker, parent_scope, nullptr, e); } +bool collect_checked_packages_from_decl_list(Checker *c, Array const &decls) { + bool new_files = false; + for_array(i, decls) { + AstNode *decl = decls[i]; + switch (decl->kind) { + case_ast_node(id, ImportDecl, decl); + HashKey key = hash_string(id->fullpath); + AstPackage **found = map_get(&c->info.packages, key); + if (found == nullptr) { + continue; + } + AstPackage *pkg = *found; + if (!ptr_set_exists(&c->checked_packages, pkg)) { + new_files = true; + ptr_set_add(&c->checked_packages, pkg); + } + case_end; + } + } + return new_files; +} +// Returns true if a new package is present +bool collect_file_decls(CheckerContext *ctx, Array const &decls); +bool collect_file_decls_from_when_stmt(CheckerContext *ctx, AstNodeWhenStmt *ws); + +bool collect_when_stmt_from_file(CheckerContext *ctx, AstNodeWhenStmt *ws) { + Operand operand = {Addressing_Invalid}; + if (!ws->is_cond_determined) { + check_expr(ctx, &operand, ws->cond); + if (operand.mode != Addressing_Invalid && !is_type_boolean(operand.type)) { + error(ws->cond, "Non-boolean condition in 'when' statement"); + } + if (operand.mode != Addressing_Constant) { + error(ws->cond, "Non-constant condition in 'when' statement"); + } + + ws->is_cond_determined = true; + ws->determined_cond = operand.value.kind == ExactValue_Bool && operand.value.value_bool; + } + + if (ws->body == nullptr || ws->body->kind != AstNode_BlockStmt) { + error(ws->cond, "Invalid body for 'when' statement"); + } else { + if (ws->determined_cond) { + return collect_checked_packages_from_decl_list(ctx->checker, ws->body->BlockStmt.stmts); + } else if (ws->else_stmt) { + switch (ws->else_stmt->kind) { + case AstNode_BlockStmt: + return collect_checked_packages_from_decl_list(ctx->checker, ws->else_stmt->BlockStmt.stmts); + case AstNode_WhenStmt: + return collect_when_stmt_from_file(ctx, &ws->else_stmt->WhenStmt); + default: + error(ws->else_stmt, "Invalid 'else' statement in 'when' statement"); + break; + } + } + } + + return false; +} + +bool collect_file_decls_from_when_stmt(CheckerContext *ctx, AstNodeWhenStmt *ws) { + Operand operand = {Addressing_Invalid}; + if (!ws->is_cond_determined) { + check_expr(ctx, &operand, ws->cond); + if (operand.mode != Addressing_Invalid && !is_type_boolean(operand.type)) { + error(ws->cond, "Non-boolean condition in 'when' statement"); + } + if (operand.mode != Addressing_Constant) { + error(ws->cond, "Non-constant condition in 'when' statement"); + } + + ws->is_cond_determined = true; + ws->determined_cond = operand.value.kind == ExactValue_Bool && operand.value.value_bool; + } + + if (ws->body == nullptr || ws->body->kind != AstNode_BlockStmt) { + error(ws->cond, "Invalid body for 'when' statement"); + } else { + if (ws->determined_cond) { + return collect_file_decls(ctx, ws->body->BlockStmt.stmts); + } else if (ws->else_stmt) { + switch (ws->else_stmt->kind) { + case AstNode_BlockStmt: + return collect_file_decls(ctx, ws->else_stmt->BlockStmt.stmts); + case AstNode_WhenStmt: + return collect_file_decls_from_when_stmt(ctx, &ws->else_stmt->WhenStmt); + default: + error(ws->else_stmt, "Invalid 'else' statement in 'when' statement"); + break; + } + } + } + + return false; +} + +bool collect_file_decls(CheckerContext *ctx, Array const &decls) { + GB_ASSERT(ctx->scope->is_file); + + if (collect_checked_packages_from_decl_list(ctx->checker, decls)) { + return true; + } + + for_array(i, decls) { + AstNode *decl = decls[i]; + switch (decl->kind) { + case_ast_node(vd, ValueDecl, decl); + check_collect_value_decl(ctx, decl); + case_end; + + case_ast_node(id, ImportDecl, decl); + check_add_import_decl(ctx, id); + case_end; + + case_ast_node(fl, ForeignImportDecl, decl); + check_add_foreign_import_decl(ctx, decl); + case_end; + + case_ast_node(fb, ForeignBlockDecl, decl); + check_add_foreign_block_decl(ctx, decl); + case_end; + + case_ast_node(ws, WhenStmt, decl); + if (!ws->is_cond_determined) { + if (collect_when_stmt_from_file(ctx, ws)) { + return true; + } + + CheckerContext nctx = *ctx; + nctx.collect_delayed_decls = true; + + if (collect_file_decls_from_when_stmt(&nctx, ws)) { + return true; + } + } else { + CheckerContext nctx = *ctx; + nctx.collect_delayed_decls = true; + + if (collect_file_decls_from_when_stmt(&nctx, ws)) { + return true; + } + } + case_end; + + case_ast_node(ce, CallExpr, decl); + if (ce->proc->kind == AstNode_BasicDirective) { + Operand o = {}; + check_expr(ctx, &o, decl); + } + case_end; + } + } + + return false; +} void check_import_entities(Checker *c) { Array dep_graph = generate_import_dependency_graph(c); @@ -2687,14 +2848,14 @@ void check_import_entities(Checker *c) { #endif } else if (path.count > 0) { ImportPathItem item = path[path.count-1]; - String filename = fn(item); - error(item.decl, "Cyclic importation of '%.*s'", LIT(filename)); + String pkg_name = item.pkg->name; + error(item.decl, "Cyclic importation of '%.*s'", LIT(pkg_name)); for (isize i = 0; i < path.count; i++) { - error(item.decl, "'%.*s' refers to", LIT(filename)); + error(item.decl, "'%.*s' refers to", LIT(pkg_name)); item = path[i]; - filename = fn(item); + pkg_name = item.pkg->name; } - error(item.decl, "'%.*s'", LIT(filename)); + error(item.decl, "'%.*s'", LIT(pkg_name)); } } @@ -2716,6 +2877,68 @@ void check_import_entities(Checker *c) { array_add(&package_order, n); } + for_array(i, c->parser->packages) { + AstPackage *pkg = c->parser->packages[i]; + switch (pkg->kind) { + case Package_Init: + case Package_Runtime: + ptr_set_add(&c->checked_packages, pkg); + break; + } + } + + for (isize loop_count = 0; ; loop_count++) { + bool new_files = false; + for_array(i, package_order) { + ImportGraphNode *node = package_order[i]; + GB_ASSERT(node->scope->is_package); + AstPackage *pkg = node->scope->package; + if (!ptr_set_exists(&c->checked_packages, pkg)) { + continue; + } + + for_array(i, pkg->files) { + AstFile *f = pkg->files[i]; + CheckerContext ctx = c->init_ctx; + add_curr_ast_file(&ctx, f); + new_files |= collect_checked_packages_from_decl_list(c, f->decls); + } + } + + if (!new_files) { + break; + } + } + + for (isize pkg_index = 0; pkg_index < package_order.count; pkg_index++) { + ImportGraphNode *node = package_order[pkg_index]; + AstPackage *pkg = node->pkg; + + if (!ptr_set_exists(&c->checked_packages, pkg)) { + continue; + } + + bool new_packages = false; + + for_array(i, pkg->files) { + AstFile *f = pkg->files[i]; + + CheckerContext ctx = c->init_ctx; + ctx.collect_delayed_decls = true; + add_curr_ast_file(&ctx, f); + + if (collect_file_decls(&ctx, f->decls)) { + new_packages = true; + break; + } + } + + if (new_packages) { + pkg_index = -1; + continue; + } + } + for_array(i, package_order) { ImportGraphNode *node = package_order[i]; GB_ASSERT(node->scope->is_package); @@ -2731,12 +2954,6 @@ void check_import_entities(Checker *c) { ast_node(id, ImportDecl, decl); check_add_import_decl(&ctx, id); } - } - - 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_directives) { AstNode *expr = f->scope->delayed_directives[j]; Operand o = {}; @@ -2963,6 +3180,7 @@ void check_parsed_files(Checker *c) { CheckerContext ctx = make_checker_context(c); defer (destroy_checker_context(&ctx)); ctx.pkg = p; + ctx.collect_delayed_decls = false; for_array(j, p->files) { AstFile *f = p->files[j]; diff --git a/src/checker.hpp b/src/checker.hpp index eb358b906..2efee5d9e 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -322,6 +322,7 @@ struct CheckerContext { CheckerTypePath *type_path; isize type_level; // TODO(bill): Actually handle correctly + bool collect_delayed_decls; bool allow_polymorphic_types; bool no_polymorphic_errors; bool in_polymorphic_specialization; @@ -333,6 +334,8 @@ struct Checker { CheckerInfo info; Array procs_to_check; + PtrSet checked_packages; + gbAllocator allocator; CheckerContext init_ctx; bool done_preload; @@ -386,7 +389,7 @@ void check_add_foreign_import_decl(CheckerContext *c, AstNode *decl); bool check_arity_match(CheckerContext *c, AstNodeValueDecl *vd, bool is_global = false); -void check_collect_entities(CheckerContext *c, Array nodes); +void check_collect_entities(CheckerContext *c, Array const &nodes); void check_collect_entities_from_when_stmt(CheckerContext *c, AstNodeWhenStmt *ws); void check_delayed_file_import_entity(CheckerContext *c, AstNode *decl); @@ -407,7 +410,7 @@ AttributeContext make_attribute_context(String link_prefix) { #define DECL_ATTRIBUTE_PROC(_name) bool _name(CheckerContext *c, AstNode *elem, String name, ExactValue value, AttributeContext *ac) typedef DECL_ATTRIBUTE_PROC(DeclAttributeProc); -void check_decl_attributes(CheckerContext *c, Array attributes, DeclAttributeProc *proc, AttributeContext *ac); +void check_decl_attributes(CheckerContext *c, Array const &attributes, DeclAttributeProc *proc, AttributeContext *ac); CheckerTypePath *new_checker_type_path(); void destroy_checker_type_path(CheckerTypePath *tp); diff --git a/src/main.cpp b/src/main.cpp index 8edd31dc5..a616433f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -603,6 +603,17 @@ void show_timings(Checker *c, Timings *t) { gb_printf("us/Token - %.3f\n", 1.0e6*parse_time/cast(f64)tokens); gb_printf("\n"); } + { + TimeStamp ts = t->sections[1]; + GB_ASSERT(ts.label == "type check"); + f64 parse_time = time_stamp_as_s(ts, t->freq); + gb_printf("Checker pass\n"); + gb_printf("LOC/s - %.3f\n", cast(f64)lines/parse_time); + gb_printf("us/LOC - %.3f\n", 1.0e6*parse_time/cast(f64)lines); + gb_printf("Tokens/s - %.3f\n", cast(f64)tokens/parse_time); + gb_printf("us/Token - %.3f\n", 1.0e6*parse_time/cast(f64)tokens); + gb_printf("\n"); + } { f64 total_time = t->total_time_seconds; gb_printf("Total pass\n"); diff --git a/src/parser.cpp b/src/parser.cpp index 697ccb2ec..98e3bc3c9 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4162,10 +4162,33 @@ bool determine_path_from_string(Parser *p, AstNode *node, String base_dir, Strin } -void parse_setup_file_decls(Parser *p, AstFile *f, String base_dir, Array decls) { + +void parse_setup_file_decls(Parser *p, AstFile *f, String base_dir, Array &decls); + +void parse_setup_file_when_stmt(Parser *p, AstFile *f, String base_dir, AstNodeWhenStmt *ws) { + if (ws->body != nullptr) { + auto stmts = ws->body->BlockStmt.stmts; + parse_setup_file_decls(p, f, base_dir, stmts); + } + + if (ws->else_stmt != nullptr) { + switch (ws->else_stmt->kind) { + case AstNode_BlockStmt: { + auto stmts = ws->else_stmt->BlockStmt.stmts; + parse_setup_file_decls(p, f, base_dir, stmts); + } break; + case AstNode_WhenStmt: + parse_setup_file_when_stmt(p, f, base_dir, &ws->else_stmt->WhenStmt); + break; + } + } +} + +void parse_setup_file_decls(Parser *p, AstFile *f, String base_dir, Array &decls) { for_array(i, decls) { AstNode *node = decls[i]; if (!is_ast_node_decl(node) && + node->kind != AstNode_WhenStmt && node->kind != AstNode_BadStmt && node->kind != AstNode_EmptyStmt) { // NOTE(bill): Sanity check @@ -4212,7 +4235,9 @@ void parse_setup_file_decls(Parser *p, AstFile *f, String base_dir, Arrayfullpath = foreign_path; } - + } else if (node->kind == AstNode_WhenStmt) { + ast_node(ws, WhenStmt, node); + parse_setup_file_when_stmt(p, f, base_dir, ws); } } }