diff --git a/README.md b/README.md index c01e8de4..8629cc77 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ mod A { ```rust mod A { use super::C; - fn foo() { C::baz() } + fn foo() { baz() } mod B { fn bar() { super::foo() } } @@ -135,6 +135,17 @@ mod C { fn baz() { D::bar } } ``` + - Wildcard imports are now supported: +```rust +mod A { + fn f() -> i32 = 42; + fn g() -> i32 = 69; +} +use A::*; + +fn main() = f() + g(); +``` + - Tuples cannot be indexed with constant integers anymore: ```rust let t = (1, 2); diff --git a/include/artic/ast.h b/include/artic/ast.h index 6db1f7a0..68c08c8b 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "artic/arena.h" #include "artic/loc.h" @@ -47,6 +48,7 @@ struct Node : public Cast { /// Location of the node in the source file. Loc loc; + mutable bool bound = false; /// Type assigned after type inference. Not all nodes are typeable. mutable const artic::Type* type = nullptr; /// IR definition assigned after IR emission. @@ -161,6 +163,8 @@ struct Ptrn : public Node { // Path ---------------------------------------------------------------------------- +struct StaticDecl; + /// A path of the form A[T1, ..., TN]:: ... ::Z[U1, ..., UN] struct Path : public Node { struct Elem { @@ -168,36 +172,47 @@ struct Path : public Node { Identifier id; PtrVector args; + // Set during name-binding + NamedDecl* decl = nullptr; + // These members are set during type-checking const artic::Type* type = nullptr; size_t index = 0; std::vector inferred_args; bool is_super() const { return id.name == "super"; } + bool is_wildcard() const { return id.name == "*"; } Elem(const Loc& loc, Identifier&& id, PtrVector&& args) : loc(loc), id(std::move(id)), args(std::move(args)) {} + + const artic::Type* infer(TypeChecker& checker, const artic::Type* prev_elem_type, Path& path); }; std::vector elems; + // use paths are allowed to have un-instantiated type params + bool is_use_path_ = false; // Set during name-binding, corresponds to the declaration that // is associated with the _first_ element of the path. // The rest of the path is resolved during type-checking. - ast::NamedDecl* start_decl; + ast::NamedDecl* start_decl = nullptr; + ast::NamedDecl* decl = nullptr; // Set during type-checking bool is_value = false; + // represents tuple-like enum options, or tuple-like structs bool is_ctor = false; - Path(const Loc& loc, std::vector&& elems) - : Node(loc), elems(std::move(elems)) + Path(const Loc& loc, bool is_use_path, std::vector&& elems) + : Node(loc), is_use_path_(is_use_path), elems(std::move(elems)) {} + const artic::Type* infer(TypeChecker&, Ptr*); const artic::Type* infer(TypeChecker&, bool, Ptr* = nullptr); const artic::Type* infer(TypeChecker& checker) override { - return infer(checker, false, nullptr); + return infer(checker, nullptr); } const thorin::Def* emit(Emitter&) const override; @@ -1472,6 +1487,8 @@ struct EnumDecl : public CtorDecl { , options(std::move(options)) {} + std::optional find_member(const std::string_view&) const; + const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind_head(NameBinder&) override; @@ -1508,8 +1525,6 @@ struct ModDecl : public NamedDecl { PtrVector decls; ModDecl* super = nullptr; - std::vector members; - /// Constructor for the implicitly defined global module. /// When using this constructor, the user is responsible for calling /// `set_super()` once the declarations have been added to the module. @@ -1525,6 +1540,7 @@ struct ModDecl : public NamedDecl { } void set_super(); + std::optional find_member(const std::string_view& name) const; const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; @@ -1538,6 +1554,9 @@ struct ModDecl : public NamedDecl { struct UseDecl : public NamedDecl { Path path; + NamedDecl* bound_to; + PtrVector wildcard_imports; + UseDecl(const Loc& loc, Path&& path, Identifier&& id) : NamedDecl(loc, std::move(id)), path(std::move(path)) {} @@ -1548,8 +1567,18 @@ struct UseDecl : public NamedDecl { void bind(NameBinder&) override; void resolve_summons(Summoner&) override {}; void print(Printer&) const override; + + void bind_wildcard(NameBinder&); }; +static inline NamedDecl* resolve_use_decl(NamedDecl* decl) { + while (auto use_decl = decl->isa()) { + assert(use_decl->bound_to && "run binding first"); + decl = use_decl->bound_to; + } + return decl; +} + /// Incorrect declaration, coming from parsing. struct ErrorDecl : public Decl { ErrorDecl(const Loc& loc) : Decl(loc) {} diff --git a/include/artic/bind.h b/include/artic/bind.h index 70b48b0b..22bc69b0 100644 --- a/include/artic/bind.h +++ b/include/artic/bind.h @@ -15,11 +15,12 @@ namespace artic { /// Binds identifiers to the nodes of the AST. class NameBinder : public Logger { public: - NameBinder(Log& log) + NameBinder(Log& log, Arena& arena) : Logger(log) , cur_fn(nullptr) , cur_loop(nullptr) , cur_mod(nullptr) + , arena_(arena) { push_scope(true); } @@ -35,6 +36,7 @@ class NameBinder : public Logger { ast::FnExpr* cur_fn; ast::LoopExpr* cur_loop; ast::ModDecl* cur_mod; + Arena& arena_; void bind_head(ast::Decl&); void bind(ast::Node&); @@ -73,6 +75,8 @@ class NameBinder : public Logger { return best; } + void unknown_member(const Loc&, const ast::NamedDecl*, const std::string_view&); + private: // Levenshtein distance is used to suggest similar identifiers to the user static constexpr size_t levenshtein_threshold() { return 3; } diff --git a/include/artic/check.h b/include/artic/check.h index 70b662b4..952ee5e1 100644 --- a/include/artic/check.h +++ b/include/artic/check.h @@ -80,6 +80,8 @@ class TypeChecker : public Logger { bool infer_type_args(const Loc&, const ForallType*, const Type*, std::vector&); const Type* infer_record_type(const TypeApp*, const StructType*, size_t&); + size_t path_to_size(ast::Path& path, const std::string_view&); + private: std::unordered_set decls_; Arena& _arena; diff --git a/include/artic/parser.h b/include/artic/parser.h index f27b6d44..b148cdae 100644 --- a/include/artic/parser.h +++ b/include/artic/parser.h @@ -101,9 +101,9 @@ class Parser : public Logger { Ptr parse_attr_list(); Ptr parse_attr(); - ast::Path parse_path(ast::Identifier&&, bool); - ast::Path parse_path(bool allow_types = true) { return parse_path(parse_path_elem(), allow_types); } - ast::Identifier parse_path_elem(); + ast::Path parse_path(ast::Identifier&&, bool is_use_path); + ast::Path parse_path(bool is_use_path = false) { return parse_path(parse_path_elem(), is_use_path); } + ast::Identifier parse_path_elem(bool allow_wildcard = false); ast::Identifier parse_id(); ast::AsmExpr::Constr parse_constr(); Literal parse_lit(); diff --git a/src/ast.cpp b/src/ast.cpp index 2af4e41e..30e9e0d8 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -593,9 +593,9 @@ bool ImplicitCastExpr::has_side_effect() const { bool ImplicitCastExpr::is_constant() const { assert(expr->type); if (auto path_expr = expr->isa(); - path_expr && path_expr->path.elems.size() == 1 && path_expr->path.start_decl) + path_expr && path_expr->path.elems.back().decl) { - if (auto static_decl = path_expr->path.start_decl->isa()) { + if (auto static_decl = path_expr->path.elems.back().decl->isa()) { // Allow using other constant static declarations as constants return !static_decl->is_mut; } @@ -607,6 +607,8 @@ bool AsmExpr::has_side_effect() const { return !outs.empty() || std::find(opts.begin(), opts.end(), "volatile") != opts.end(); } +// Decls --------------------------------------------------------------------------- + // Patterns ------------------------------------------------------------------------ void Ptrn::collect_bound_ptrns(std::vector&) const {} @@ -641,7 +643,7 @@ const Expr* IdPtrn::to_expr(Arena& arena) { Identifier id = decl->id; std::vector elems; elems.push_back(Path::Elem( loc, std::move(id), {} )); - Path path = Path(loc, std::move(elems)); + Path path = Path(loc, false, std::move(elems)); path.start_decl = decl.get(); path.is_value = true; as_expr = arena.make_ptr(std::move(path)); diff --git a/src/bind.cpp b/src/bind.cpp index 37c4a3f4..09d84275 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -13,9 +13,12 @@ void NameBinder::bind_head(ast::Decl& decl) { } void NameBinder::bind(ast::Node& node) { + if (node.bound) + return; if (node.attrs) node.attrs->bind(*this); node.bind(*this); + node.bound = true; } void NameBinder::pop_scope() { @@ -51,30 +54,80 @@ void NameBinder::insert_symbol(ast::NamedDecl& decl, const std::string& name) { } } +void NameBinder::unknown_member(const Loc& loc, const ast::NamedDecl* user_type, const std::string_view& member) { + if (auto mod_type = user_type->isa(); mod_type && mod_type->id.name == "") + error(loc, "no member '{}' in top-level module", member); + else + error(loc, "no member '{}' in '{}'", member, *user_type); +} + namespace ast { // Path ---------------------------------------------------------------------------- void Path::bind(NameBinder& binder) { - // Bind the first element of the path - auto& first = elems.front(); - if (first.id.name[0] == '_') - binder.error(first.id.loc, "identifiers beginning with '_' cannot be referenced"); - else if (first.is_super()) { - start_decl = binder.cur_mod->super; - if (!start_decl) - binder.error(first.id.loc, "top-level module has no super-module"); - } else { - auto symbol = binder.find_symbol(first.id.name); - if (!symbol) { - binder.error(first.id.loc, "unknown identifier '{}'", first.id.name); - if (auto similar = binder.find_similar_symbol(first.id.name)) - binder.note("did you mean '{}'?", similar->decl->id.name); - } else - start_decl = symbol->decl; - } - // Bind the type arguments of each element + NamedDecl* decl = binder.cur_mod; + size_t i = 0; for (auto& elem : elems) { + assert(decl); + + // We need to look inside UseDecls to bind paths properly. + NamedDecl* actual_decl = resolve_use_decl(decl); + + if (elem.id.name[0] == '_') + binder.error(elem.id.loc, "identifiers beginning with '_' cannot be referenced"); + else if (elem.is_super()) { + if (auto mod = decl->isa()) { + if (!mod->super) + binder.error(elem.id.loc, "top-level module has no super-module"); + else + decl = mod->super; + } else + binder.error(elem.id.loc, "''super' can only be used on modules"); + } else if (elem.is_wildcard()) { + if (i == 0) { + binder.error(elem.loc, "wildcards cannot appear at the start of a path!"); + return; + } + decl = nullptr; + } else if (i == 0) { + assert(decl == binder.cur_mod); + auto symbol = binder.find_symbol(elem.id.name); + if (!symbol) { + binder.error(elem.id.loc, "unknown identifier '{}'", elem.id.name); + if (auto similar = binder.find_similar_symbol(elem.id.name)) + binder.note("did you mean '{}'?", similar->decl->id.name); + } else + decl = symbol->decl; + } else if (auto mod = actual_decl->isa()) { + auto member = mod->find_member(elem.id.name); + if (!member) { + binder.unknown_member(elem.loc, mod, elem.id.name); + return; + } + decl = *member; + } else if (auto enu = actual_decl->isa()) { + auto found = enu->find_member(elem.id.name); + if (!found) { + binder.unknown_member(elem.loc, mod, elem.id.name); + return; + } + decl = *found; + } else { + // ... + assert(false); + } + + if (i == 0) { + start_decl = decl; + } + + if (i++ == elems.size() - 1) + this->decl = decl; + + elem.decl = decl; + + // Bind the type arguments of each element for (auto& arg : elem.args) binder.bind(*arg); } @@ -534,15 +587,71 @@ void ModDecl::bind(NameBinder& binder) { binder.cur_mod = old_mod; } +std::optional ModDecl::find_member(const std::string_view& name) const { + for (const auto& decl : decls) { + if (auto named = decl->isa()) + if (named->id.name == name) + return std::make_optional(named); + } + return std::nullopt; +} + +std::optional EnumDecl::find_member(const std::string_view& name) const { + for (const auto& decl : options) { + if (decl->id.name == name) + return std::make_optional(&*decl); + } + return std::nullopt; +} + +void UseDecl::bind_wildcard(artic::NameBinder& binder) { + if (path.elems.back().id.name != "*") + return; + + binder.bind(path); + assert(path.elems.size() >= 2); + auto& penultimate = path.elems[path.elems.size() - 2]; + NamedDecl* importee = penultimate.decl; + auto imported_mod = importee->isa(); + if (!imported_mod) { + binder.error(penultimate.id.loc, "'{}' is not a module", importee->id.name); + } + + for (auto& decl : imported_mod->decls) { + auto member = decl->isa(); + if (!member) + continue; + std::vector member_path_elements; + for (auto& elem : path.elems) { + assert(elem.args.empty()); + auto nelem = Path::Elem(elem.loc, std::move(Identifier(elem.id)), std::move(PtrVector())); + member_path_elements.emplace_back(std::move(nelem)); + } + member_path_elements.back().id.name = member->id.name; + Path member_path(path.loc, true, std::move(member_path_elements)); + Identifier nid = member->id; + wildcard_imports.push_back(binder.arena_.make_ptr(loc, std::move(member_path), std::move(nid))); + wildcard_imports.back()->bind_head(binder); + } +} + void UseDecl::bind_head(NameBinder& binder) { if (id.name != "") binder.insert_symbol(*this); - else + else if (path.elems.back().id.name != "*") binder.insert_symbol(*this, path.elems.back().id.name); + bind_wildcard(binder); } void UseDecl::bind(NameBinder& binder) { - path.bind(binder); + binder.bind(path); + + if (path.decl) + bound_to = path.decl; + + for (auto& m : wildcard_imports) { + m->bind(binder); + } } void ErrorDecl::bind(NameBinder&) {} diff --git a/src/check.cpp b/src/check.cpp index 95808cc3..2c5dd19f 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -604,6 +604,24 @@ const Type* TypeChecker::infer_record_type(const TypeApp* type_app, const Struct return type_app ? type_app->as() : struct_type; } +size_t TypeChecker::path_to_size(ast::Path& path, const std::string_view& element) { + auto decl = resolve_use_decl(path.elems.back().decl); + auto static_decl = decl->isa(); + ast::LiteralExpr* lit_value = nullptr; + if (static_decl && !static_decl->is_mut && static_decl->init) + lit_value = static_decl->init->isa(); + if (lit_value && lit_value->lit.is_integer()) + return lit_value->lit.as_integer(); + error(path.loc, "{} can only be a literal, or a constant", element); + if (static_decl->is_mut) + note(static_decl->loc, "{} is mutable", path); + if (!static_decl->init) + note(static_decl->loc, "{} lacks an initializer", path); + if (!lit_value || !lit_value->lit.is_integer()) + note(static_decl->loc, "{} is not of an integer type", path); + return 0; +} + namespace ast { const artic::Type* Node::check(TypeChecker& checker, const artic::Type* expected) { @@ -630,23 +648,71 @@ const artic::Type* Ptrn::check(TypeChecker& checker, const artic::Type* expected // Path ---------------------------------------------------------------------------- -const artic::Type* Path::infer(TypeChecker& checker, bool value_expected, Ptr* arg) { - if (!start_decl) - return checker.type_table.type_error(); +const artic::Type* Path::Elem::infer(TypeChecker& checker, const artic::Type* prev_elem_type, Path& path) { + if (!prev_elem_type) { + if (is_super()) { + return type = checker.type_table.mod_type(*decl->as()); + } else { + return type = checker.infer(*decl); + } + } else if (is_super()) { + assert(prev_elem_type); + auto mod_type = prev_elem_type->isa(); + if (!mod_type) { + checker.error(loc, "'super' can only be used on modules"); + return type = checker.type_table.type_error(); + } + return type = checker.type_table.mod_type(*mod_type->decl.super); + } else if (auto mod_type = prev_elem_type->isa()) { + auto index = mod_type->find_member(id.name); + if (!index) + return type = checker.unknown_member(loc, mod_type, id.name); + this->index = *index; + auto& member = mod_type->member(*index); + // We do not want infer the declaration if it is a module, since we can immediately + // create a type for it and lazily infer member types as required. + return type = member.isa() + ? checker.type_table.mod_type(*member.as()) + : checker.infer(mod_type->member(*index)); + } else if (auto [type_app, enum_type] = match_app(prev_elem_type); enum_type) { + auto index = enum_type->find_member(id.name); + if (!index) + return type = checker.unknown_member(loc, enum_type, id.name); + this->index = *index; + if (enum_type->decl.options[*index]->struct_type) { + // If the enumeration option uses the record syntax, we use the corresponding structure type + type = enum_type->decl.options[*index]->struct_type; + if (type_app) + type = checker.type_table.type_app(type->as(), type_app->type_args); + return type; + } else { + auto member = member_type(type_app, enum_type, *index); + path.is_ctor = true; + if (is_unit_type(member)) { + return type = prev_elem_type; + } else { + return type = checker.type_table.fn_type(member, prev_elem_type); + } + } + } else + return checker.type_expected(loc, type, "module or enum"); +} - type = elems[0].is_super() - ? checker.type_table.mod_type(*start_decl->as()) - : checker.infer(*start_decl); - is_value = elems.size() == 1 && start_decl->isa(); - is_ctor = start_decl->isa(); +const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { + if (elems.back().is_wildcard()) + return nullptr; + if (!decl) + return checker.type_table.type_error(); // Inspect every element of the path for (size_t i = 0, n = elems.size(); i < n; ++i) { auto& elem = elems[i]; + elem.infer(checker, i == 0 ? nullptr : elems[i - 1].type, *this); + // Apply type arguments (if any) - auto user_type = type->isa(); - auto forall_type = type->isa(); + auto user_type = elem.type->isa(); + auto forall_type = elem.type->isa(); if ((user_type && user_type->type_params()) || forall_type) { const size_t type_param_count = user_type ? user_type->type_params()->params.size() @@ -663,10 +729,10 @@ const artic::Type* Path::infer(TypeChecker& checker, bool value_expected, Ptrinstantiate(type_args); - } else { + } else if (!elem.args.empty() || /* we allow leaving out type params when importing definitions */ !is_use_path_) { checker.error(elem.loc, "expected {} type argument(s), but got {}", type_param_count, elem.args.size()); return checker.type_table.type_error(); } @@ -674,71 +740,40 @@ const artic::Type* Path::infer(TypeChecker& checker, bool value_expected, Ptr(type); - is_ctor && value_expected && struct_type && struct_type->is_tuple_like()) { - if (struct_type->member_count() > 0) { - SmallArray tuple_args(struct_type->member_count()); - for (size_t i = 0, n = struct_type->member_count(); i < n; ++i) - tuple_args[i] = member_type(type_app, struct_type, i); - auto dom = struct_type->member_count() == 1 - ? tuple_args.front() - : checker.type_table.tuple_type(tuple_args); - type = checker.type_table.fn_type(dom, type); - } - is_value = true; - } + } - // Perform a lookup inside the current object if the path is not finished - if (i != n - 1) { - if (elems[i + 1].is_super()) { - auto mod_type = type->isa(); - if (!mod_type) { - checker.error(elems[i + 1].loc, "'super' can only be used on modules"); - return checker.type_table.type_error(); - } - type = checker.type_table.mod_type(*mod_type->decl.super); - } else if (auto [type_app, enum_type] = match_app(type); enum_type) { - auto index = enum_type->find_member(elems[i + 1].id.name); - if (!index) - return checker.unknown_member(elem.loc, enum_type, elems[i + 1].id.name); - elems[i + 1].index = *index; - if (enum_type->decl.options[*index]->struct_type) { - // If the enumeration option uses the record syntax, we use the corresponding structure type - type = enum_type->decl.options[*index]->struct_type; - if (type_app) - type = checker.type_table.type_app(type->as(), type_app->type_args); - is_value = false; - is_ctor = true; - } else { - auto member = member_type(type_app, enum_type, *index); - type = is_unit_type(member) ? type : checker.type_table.fn_type(member, type); - is_value = is_ctor = true; - } - } else if (auto mod_type = type->isa()) { - auto index = mod_type->find_member(elems[i + 1].id.name); - if (!index) - return checker.unknown_member(elems[i + 1].loc, mod_type, elems[i + 1].id.name); - elems[i + 1].index = *index; - auto& member = mod_type->member(*index); - // We do not want infer the declaration if it is a module, since we can immediately - // create a type for it and lazily infer member types as required. - type = member.isa() - ? checker.type_table.mod_type(*member.as()) - : checker.infer(mod_type->member(*index)); - is_value = member.isa(); - is_ctor = member.isa(); - } else - return checker.type_expected(elem.loc, type, "module or enum"); + return type = elems.back().type; +} + +const artic::Type* Path::infer(artic::TypeChecker& checker, bool value_expected, Ptr* arg) { + type = infer(checker, arg); + + auto last_decl = resolve_use_decl(decl); + + is_value |= static_cast(last_decl->isa()); + is_value |= is_ctor; + + // Treat tuple-like structure constructors as functions + if (auto [type_app, struct_type] = match_app(type); + last_decl->isa() && value_expected && struct_type && struct_type->is_tuple_like()) { + if (struct_type->member_count() > 0) { + SmallArray tuple_args(struct_type->member_count()); + for (size_t i = 0, n = struct_type->member_count(); i < n; ++i) + tuple_args[i] = member_type(type_app, struct_type, i); + auto dom = struct_type->member_count() == 1 + ? tuple_args.front() + : checker.type_table.tuple_type(tuple_args); + type = checker.type_table.fn_type(dom, type); } + is_value = true; + is_ctor = true; } if (is_value != value_expected) { checker.error(loc, "{} expected, but got '{}'", value_expected ? "value" : "type", *this); return checker.type_table.type_error(); } + return type; } @@ -852,31 +887,8 @@ const artic::Type* SizedArrayType::infer(TypeChecker& checker) { if (std::holds_alternative(size)) { auto &path = std::get(size); - const auto* decl = path.start_decl; - - for (size_t i = 0, n = path.elems.size(); i < n; ++i) { - if (path.elems[i].is_super()) - decl = i == 0 ? path.start_decl : decl->as()->super; - if (auto mod_type = path.elems[i].type->isa()) { - decl = &mod_type->member(path.elems[i + 1].index); - } else if (!path.is_ctor) { - assert(path.elems[i].inferred_args.empty()); - assert(decl->isa() && "The only supported type right now."); - break; - } else if (match_app(path.elems[i].type).second) { - assert(false && "This is not supported as a size for repeated arrays."); - } else if (auto [type_app, enum_type] = match_app(path.elems[i].type); enum_type) { - assert(false && "This is not supported as a size for repeated arrays."); - } - } - - auto static_decl = decl->as(); - assert(!static_decl->is_mut); - assert(static_decl->init); - auto& value = static_decl->init; - auto lit_value = value->as()->lit; - - size = lit_value.as_integer(); + checker.infer(path); + size = checker.path_to_size(path, "sized array size"); } return checker.type_table.sized_array_type(elem_type, std::get(size), is_simd); @@ -1027,31 +1039,8 @@ const artic::Type* RepeatArrayExpr::infer(TypeChecker& checker) { if (std::holds_alternative(size)) { auto &path = std::get(size); - const auto* decl = path.start_decl; - - for (size_t i = 0, n = path.elems.size(); i < n; ++i) { - if (path.elems[i].is_super()) - decl = i == 0 ? path.start_decl : decl->as()->super; - if (auto mod_type = path.elems[i].type->isa()) { - decl = &mod_type->member(path.elems[i + 1].index); - } else if (!path.is_ctor) { - assert(path.elems[i].inferred_args.empty()); - assert(decl->isa() && "The only supported type right now."); - break; - } else if (match_app(path.elems[i].type).second) { - assert(false && "This is not supported as a size for repeated arrays."); - } else if (auto [type_app, enum_type] = match_app(path.elems[i].type); enum_type) { - assert(false && "This is not supported as a size for repeated arrays."); - } - } - - auto static_decl = decl->as(); - assert(!static_decl->is_mut); - assert(static_decl->init); - auto& value = static_decl->init; - auto lit_value = value->as()->lit; - - size = lit_value.as_integer(); + checker.infer(path); + size = checker.path_to_size(path, "repeat array expression size"); } return checker.type_table.sized_array_type(elem_type, std::get(size), is_simd); @@ -1060,31 +1049,8 @@ const artic::Type* RepeatArrayExpr::infer(TypeChecker& checker) { const artic::Type* RepeatArrayExpr::check(TypeChecker& checker, const artic::Type* expected) { if (std::holds_alternative(size)) { auto &path = std::get(size); - const auto* decl = path.start_decl; - - for (size_t i = 0, n = path.elems.size(); i < n; ++i) { - if (path.elems[i].is_super()) - decl = i == 0 ? path.start_decl : decl->as()->super; - if (auto mod_type = path.elems[i].type->isa()) { - decl = &mod_type->member(path.elems[i + 1].index); - } else if (!path.is_ctor) { - assert(path.elems[i].inferred_args.empty()); - assert(decl->isa() && "The only supported type right now."); - break; - } else if (match_app(path.elems[i].type).second) { - assert(false && "This is not supported as a size for repeated arrays."); - } else if (auto [type_app, enum_type] = match_app(path.elems[i].type); enum_type) { - assert(false && "This is not supported as a size for repeated arrays."); - } - } - - auto static_decl = decl->as(); - assert(!static_decl->is_mut); - assert(static_decl->init); - auto& value = static_decl->init; - auto lit_value = value->as()->lit; - - size = lit_value.as_integer(); + checker.infer(path); + size = checker.path_to_size(path, "repeat array expression size"); } return checker.check_array(loc, "array expression", @@ -1833,8 +1799,6 @@ const artic::Type* UseDecl::infer(TypeChecker& checker) { return checker.type_table.type_error(); auto path_type = checker.infer(path); checker.exit_decl(this); - if (!path_type->isa()) - return checker.type_expected(path.loc, path_type, "module type"); return path_type; } @@ -1899,7 +1863,7 @@ const artic::Type* RecordPtrn::infer(TypeChecker& checker) { const artic::Type* CtorPtrn::infer(TypeChecker& checker) { auto path_type = path.infer(checker, true); - if (!path.is_ctor) { + if (!path.decl->isa()) { checker.error(path.loc, "structure or enumeration constructor expected"); return checker.type_table.type_error(); } diff --git a/src/emit.cpp b/src/emit.cpp index 86f1d66c..28db86d9 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1069,22 +1069,24 @@ const thorin::Def* Node::emit(Emitter&) const { // Path ---------------------------------------------------------------------------- const thorin::Def* Path::emit(Emitter& emitter) const { - // Currently only supports paths of the form A/A::B/A[T, ...]/A[T, ...]::B - if (auto struct_decl = start_decl->isa(); - struct_decl && struct_decl->is_tuple_like && struct_decl->fields.empty()) { - return emitter.world.struct_agg( - type->convert(emitter)->as(), {}, - emitter.debug_info(*this)); - } - - const auto* decl = start_decl; for (size_t i = 0, n = elems.size(); i < n; ++i) { + if (elems[i].is_wildcard()) + return nullptr; + const auto decl = resolve_use_decl(elems[i].decl); + if (elems[i].type->isa()) + continue; if (elems[i].is_super()) - decl = i == 0 ? start_decl : decl->as()->super; + continue; - if (auto mod_type = elems[i].type->isa()) { - decl = &mod_type->member(elems[i + 1].index); - } else if (!is_ctor) { + if (auto struct_decl = decl->isa(); + struct_decl && struct_decl->is_tuple_like && struct_decl->fields.empty()) { + assert(is_ctor); + return emitter.world.struct_agg( + type->convert(emitter)->as(), {}, + emitter.debug_info(*this)); + } + + if (!is_ctor) { // If type arguments are present, this is a polymorphic application std::unordered_map map; if (!elems[i].inferred_args.empty()) { @@ -1108,6 +1110,7 @@ const thorin::Def* Path::emit(Emitter& emitter) const { } return def; } else if (match_app(elems[i].type).second) { + assert(is_ctor); if (auto it = emitter.struct_ctors.find(elems[i].type); it != emitter.struct_ctors.end()) return it->second; // Create a constructor for this (tuple-like) structure @@ -1125,6 +1128,7 @@ const thorin::Def* Path::emit(Emitter& emitter) const { emitter.jump(cont->params().back(), struct_value, emitter.debug_info(*this)); return emitter.struct_ctors[elems[i].type] = cont; } else if (auto [type_app, enum_type] = match_app(elems[i].type); enum_type) { + assert(is_ctor); // Find the variant constructor for that enum, if it exists. // Remember that the type application (if present) might be polymorphic (i.e. `E[T, U]::A`), and that, thus, // we need to replace bound type variables (`T` and `U` in the previous example) to find the constructor in the map. @@ -1191,6 +1195,7 @@ const thorin::Def* TypedExpr::emit(Emitter& emitter) const { } const thorin::Def* PathExpr::emit(Emitter& emitter) const { + assert(emitter.emit(path)); return emitter.emit(path); } @@ -1765,12 +1770,17 @@ const thorin::Def* ModDecl::emit(Emitter& emitter) const { // Likewise, we do not emit implicit declarations if (auto implicit = decl->isa()) continue; + // Don't emit use declarations: they might point to modules. When used in expressions we'll emit them. + if (auto implicit = decl->isa()) + continue; emitter.emit(*decl); } return nullptr; } -const thorin::Def* UseDecl::emit(Emitter&) const { +const thorin::Def* UseDecl::emit(Emitter& emitter) const { + if (path.decl) + return emitter.emit(*path.decl); return nullptr; } @@ -2096,7 +2106,7 @@ std::tuple, bool> compile( program->set_super(); - NameBinder name_binder(log); + NameBinder name_binder(log, arena); name_binder.warns_as_errors = warns_as_errors; if (enable_all_warns) name_binder.warn_on_shadowing = true; diff --git a/src/parser.cpp b/src/parser.cpp index f50a3864..1f7b2a9e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -262,7 +262,7 @@ Ptr Parser::parse_mod_decl() { Ptr Parser::parse_use_decl() { Tracker tracker(this); eat(Token::Use); - auto path = parse_path(); + auto path = parse_path(true); ast::Identifier id; if (accept(Token::As)) id = parse_id(); @@ -303,7 +303,7 @@ Ptr Parser::parse_ptrn(bool allow_types, bool allow_implicits) { ahead().tag() == Token::LParen || ahead().tag() == Token::LBrace || (allow_types && ahead().tag() != Token::Colon && ahead().tag() != Token::As)) { - auto path = parse_path(std::move(id), true); + auto path = parse_path(std::move(id), false); if (ahead().tag() == Token::LBrace) ptrn = parse_record_ptrn(std::move(path)); else if (allow_types) { @@ -490,7 +490,7 @@ Ptr Parser::parse_typed_expr(Ptr&& expr) { } Ptr Parser::parse_path_expr() { - auto path = parse_path(true); + auto path = parse_path(); return _arena.make_ptr(std::move(path)); } @@ -1181,7 +1181,7 @@ Ptr Parser::parse_attr() { } } -ast::Path Parser::parse_path(ast::Identifier&& id, bool allow_types) { +ast::Path Parser::parse_path(ast::Identifier&& id, bool is_use_path) { Tracker tracker(this, id.loc); std::vector elems; @@ -1189,22 +1189,26 @@ ast::Path Parser::parse_path(ast::Identifier&& id, bool allow_types) { Tracker elem_tracker(this, id.loc); PtrVector args; // Do not accept type arguments on `super` - if (allow_types && id.name != "super" && accept(Token::LBracket)) { + if (id.name != "super" && accept(Token::LBracket)) { parse_list(Token::RBracket, Token::Comma, [&] { args.emplace_back(parse_type()); }); } elems.emplace_back(elem_tracker(), std::move(id), std::move(args)); + if (id.name == "*") + break; if (!accept(Token::DblColon)) break; - id = parse_path_elem(); + id = parse_path_elem(is_use_path); } while (true) ; - return ast::Path(tracker(), std::move(elems)); + return ast::Path(tracker(), is_use_path, std::move(elems)); } -ast::Identifier Parser::parse_path_elem() { +ast::Identifier Parser::parse_path_elem(bool allow_wildcard) { auto prev_loc = ahead().loc(); + if (allow_wildcard && accept(Token::Mul)) + return ast::Identifier(prev_loc, "*"); return accept(Token::Super) ? ast::Identifier(prev_loc, "super") : parse_id(); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 57fcc34e..9a097310 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,7 +63,12 @@ add_test(NAME simple_ops COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC add_test(NAME simple_mod1 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/mod1.art) add_test(NAME simple_mod2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/mod2.art) add_test(NAME simple_mod3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/mod3.art) -add_test(NAME simple_use COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/use.art) +add_test(NAME simple_use1 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/use1.art) +add_test(NAME simple_use2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/use2.art) +add_test(NAME simple_use3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/use3.art) +add_test(NAME simple_use4 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/use4.art) +add_test(NAME simple_use5 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/use5.art) +add_test(NAME simple_use6 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/use6.art) add_test(NAME simple_poly_fn1 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/poly_fn1.art) add_test(NAME simple_poly_fn2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/poly_fn2.art) add_test(NAME simple_nested_fns COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/nested_fns.art) @@ -103,6 +108,8 @@ add_failure_test(NAME failure_uninferable3 COMMAND artic ${CMAKE_CURRENT_SOURC add_failure_test(NAME failure_noret COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/noret.art) add_failure_test(NAME failure_arrays1 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/arrays1.art) add_failure_test(NAME failure_arrays2 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/arrays2.art) +add_failure_test(NAME failure_arrays3 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/arrays3.art) +add_failure_test(NAME failure_arrays4 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/arrays4.art) add_failure_test(NAME failure_if COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/if.art) add_failure_test(NAME failure_if_let COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/if_let.art) add_failure_test(NAME failure_implicit1 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/implicit1.art) diff --git a/test/failure/arrays3.art b/test/failure/arrays3.art new file mode 100644 index 00000000..cd4a8f70 --- /dev/null +++ b/test/failure/arrays3.art @@ -0,0 +1,8 @@ +static mut x = 3; +static y: i32; +static arr1 = [3; x]; +static arr2 = [3; y]; + +mod M { + static arr = [3; super::x]; +} \ No newline at end of file diff --git a/test/failure/arrays4.art b/test/failure/arrays4.art new file mode 100644 index 00000000..e04bf3be --- /dev/null +++ b/test/failure/arrays4.art @@ -0,0 +1,2 @@ +static b = true; +static nah = [3; b]; \ No newline at end of file diff --git a/test/simple/arrays3.art b/test/simple/arrays3.art index 0256c315..ca452e8e 100644 --- a/test/simple/arrays3.art +++ b/test/simple/arrays3.art @@ -1,5 +1,12 @@ static n = 3; +mod M { + static x = [3; super::n]; + + use super::n as m; + static y = [3; m]; +} + #[export] fn test() { let a = [3; n] : [i32 * n]; diff --git a/test/simple/static.art b/test/simple/static.art index fc495da1..e602933f 100644 --- a/test/simple/static.art +++ b/test/simple/static.art @@ -1,7 +1,12 @@ static mut x = 1; static y = 0; static z = test; + +mod M { + static w = super::y; +} + #[export] fn test() { - x += y + x += y + M::w } diff --git a/test/simple/tuple_like2.art b/test/simple/tuple_like2.art index f886f01c..5c0dc55a 100644 --- a/test/simple/tuple_like2.art +++ b/test/simple/tuple_like2.art @@ -1,7 +1,9 @@ mod test { struct S(i32, u32); struct T[U](i32, U); + struct U; } fn foo() = test::S(1, 2); fn bar() = test::T[u32](1, 2); +fn baz() = test::U; diff --git a/test/simple/use.art b/test/simple/use1.art similarity index 100% rename from test/simple/use.art rename to test/simple/use1.art diff --git a/test/simple/use2.art b/test/simple/use2.art new file mode 100644 index 00000000..80353a57 --- /dev/null +++ b/test/simple/use2.art @@ -0,0 +1,14 @@ +mod blob { + struct Blob { x: i32 } + + fn g(x: i32, y: i32) = x + y; +} + +use blob::Blob; +use blob::g; + +fn main(a: i32) -> Blob { + let x = g(a, -a); + let b = Blob { x = x }; + b +} diff --git a/test/simple/use3.art b/test/simple/use3.art new file mode 100644 index 00000000..6848afea --- /dev/null +++ b/test/simple/use3.art @@ -0,0 +1,8 @@ +mod foo { + static x: i32 = 42; + + fn f() = super::bar::y; +} +mod bar { + use super::foo::x as y; +} diff --git a/test/simple/use4.art b/test/simple/use4.art new file mode 100644 index 00000000..5e8fb888 --- /dev/null +++ b/test/simple/use4.art @@ -0,0 +1,11 @@ +mod blob { + struct Blob { x: i32 } + fn g(x: i32, y: i32) = x + y; +} + +use blob::*; + +fn main(a: i32) -> Blob { + let x = g(a, -a); + Blob { x = x } +} diff --git a/test/simple/use5.art b/test/simple/use5.art new file mode 100644 index 00000000..d62ebd25 --- /dev/null +++ b/test/simple/use5.art @@ -0,0 +1,15 @@ +mod A { + struct S[T] { + t: T + } + + fn f(S[i32]) -> (); + + fn foo[T](t: T) = S[T] { t = t }; +} + +use A::S; +fn g(s: S[i32]) = A::f(s); + +use A::foo; +fn h() = foo(5); \ No newline at end of file diff --git a/test/simple/use6.art b/test/simple/use6.art new file mode 100644 index 00000000..b1a7a82d --- /dev/null +++ b/test/simple/use6.art @@ -0,0 +1,11 @@ +mod A { + struct S[T] { + t: T + } + + fn f(S[i32]) -> (); +} + +use A::*; + +fn g(s: S[i32]) = A::f(s); \ No newline at end of file