diff --git a/README.md b/README.md index 0da1150..1c6eb3c 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 0b1739f..58827c7 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "artic/loc.h" #include "artic/log.h" @@ -50,6 +51,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. @@ -171,12 +173,16 @@ 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)) @@ -188,19 +194,20 @@ struct Path : public Node { // 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; - bool is_ctor = false; Path(const Loc& loc, std::vector&& elems) : Node(loc), 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; @@ -1207,6 +1214,8 @@ struct NamedDecl : public Decl { NamedDecl(const Loc& loc, Identifier&& id) : Decl(loc), id(std::move(id)) {} + + virtual bool is_value(); }; /// Value declaration associated with an identifier. @@ -1214,6 +1223,8 @@ struct ValueDecl : public NamedDecl { ValueDecl(const Loc& loc, Identifier&& id) : NamedDecl(loc, std::move(id)) {} + + bool is_value() override; }; /// Datatype declaration with a constructor associated with an identifier. @@ -1463,6 +1474,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; @@ -1499,8 +1512,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. @@ -1516,6 +1527,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; @@ -1529,6 +1541,12 @@ struct ModDecl : public NamedDecl { struct UseDecl : public NamedDecl { Path path; + NamedDecl* bound_to; + PtrVector wildcard_imports; + + // Set during type-checking + bool is_value_ = false; + UseDecl(const Loc& loc, Path&& path, Identifier&& id) : NamedDecl(loc, std::move(id)), path(std::move(path)) {} @@ -1539,6 +1557,9 @@ struct UseDecl : public NamedDecl { void bind(NameBinder&) override; void resolve_summons(Summoner&) override {}; void print(Printer&) const override; + + void bind_wildcard(NameBinder&); + bool is_value() override; }; /// Incorrect declaration, coming from parsing. diff --git a/include/artic/bind.h b/include/artic/bind.h index 70b48b0..d8e15ff 100644 --- a/include/artic/bind.h +++ b/include/artic/bind.h @@ -73,6 +73,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/parser.h b/include/artic/parser.h index 15b248f..fbecaea 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 allow_wildcard); + ast::Path parse_path(bool allow_wildcard = false) { return parse_path(parse_path_elem(), allow_wildcard); } + 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 f11eb04..992eda8 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -607,6 +607,16 @@ bool AsmExpr::has_side_effect() const { return !outs.empty() || std::find(opts.begin(), opts.end(), "volatile") != opts.end(); } +// Decls --------------------------------------------------------------------------- + +bool NamedDecl::is_value() { return false; } + +bool ValueDecl::is_value() { return true; } + +bool UseDecl::is_value() { + return is_value_; +} + // Patterns ------------------------------------------------------------------------ void Ptrn::collect_bound_ptrns(std::vector&) const {} diff --git a/src/bind.cpp b/src/bind.cpp index e094c60..337346d 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,83 @@ 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. + while (auto use = decl->isa()) { + binder.bind(*use); + assert(use->bound_to); + decl = use->bound_to; + } + + 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 (!start_decl) { + 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 = 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 = 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 (!start_decl) + 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); } @@ -519,20 +575,79 @@ void ModDecl::bind(NameBinder& binder) { binder.cur_mod = this; binder.push_scope(); for (auto& decl : decls) binder.bind_head(*decl); + for (auto& decl : decls) { + if (auto use = decl->isa()) + use->bind_wildcard(binder); + } for (auto& decl : decls) binder.bind(*decl); std::swap(binder.scopes_, old_scopes); 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, std::move(member_path_elements)); + Identifier nid = member->id; + wildcard_imports.push_back(std::make_unique(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); } 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 ccbacbd..d719f4a 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -625,15 +625,16 @@ 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) +const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { + if (elems.back().is_wildcard()) + return nullptr; + if (!decl) return checker.type_table.type_error(); 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(); + is_value = elems.size() == 1 && start_decl->is_value(); // Inspect every element of the path for (size_t i = 0, n = elems.size(); i < n; ++i) { @@ -671,21 +672,6 @@ 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()) { @@ -706,11 +692,10 @@ const artic::Type* Path::infer(TypeChecker& checker, bool value_expected, Ptras(), 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; + is_value = true; } } else if (auto mod_type = type->isa()) { auto index = mod_type->find_member(elems[i + 1].id.name); @@ -723,17 +708,40 @@ const artic::Type* Path::infer(TypeChecker& checker, bool value_expected, Ptr() ? checker.type_table.mod_type(*member.as()) : checker.infer(mod_type->member(*index)); - is_value = member.isa(); - is_ctor = member.isa(); + is_value = member.is_value(); } else return checker.type_expected(elem.loc, type, "module or enum"); } } + return type; +} + +const artic::Type* Path::infer(artic::TypeChecker& checker, bool value_expected, Ptr* arg) { + infer(checker, arg); + + auto is_ctor = decl->isa(); + + // Treat tuple-like structure constructors as functions + if (auto [type_app, struct_type] = match_app(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; + } + if (is_value != value_expected) { checker.error(loc, "{} expected, but got '{}'", value_expected ? "value" : "type", *this); return checker.type_table.type_error(); } + return type; } @@ -1732,9 +1740,9 @@ const artic::Type* UseDecl::infer(TypeChecker& checker) { if (!checker.enter_decl(this)) return checker.type_table.type_error(); auto path_type = checker.infer(path); + if (path.decl) + is_value_ = path.decl->is_value(); checker.exit_decl(this); - if (!path_type->isa()) - return checker.type_expected(path.loc, path_type, "module type"); return path_type; } @@ -1799,7 +1807,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 2e49d83..197c38d 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1071,10 +1071,12 @@ const thorin::Def* Path::emit(Emitter& emitter) const { for (size_t i = 0, n = elems.size(); i < n; ++i) { if (elems[i].is_super()) decl = i == 0 ? start_decl : decl->as()->super; + if (elems[i].is_wildcard()) + return nullptr; if (auto mod_type = elems[i].type->isa()) { decl = &mod_type->member(elems[i + 1].index); - } else if (!is_ctor) { + } else if (!elems[i].decl->isa()) { // If type arguments are present, this is a polymorphic application std::unordered_map map; if (!elems[i].inferred_args.empty()) { @@ -1181,6 +1183,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); } @@ -1755,12 +1758,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; } diff --git a/src/parser.cpp b/src/parser.cpp index 2f3634e..d09ff24 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -258,7 +258,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(); @@ -299,7 +299,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) { @@ -486,7 +486,7 @@ Ptr Parser::parse_typed_expr(Ptr&& expr) { } Ptr Parser::parse_path_expr() { - auto path = parse_path(true); + auto path = parse_path(); return make_ptr(std::move(path)); } @@ -1171,7 +1171,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 allow_wildcard) { Tracker tracker(this, id.loc); std::vector elems; @@ -1179,22 +1179,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(allow_wildcard); } while (true) ; return ast::Path(tracker(), 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 a1e3f70..16fb079 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,7 +62,10 @@ 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_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) 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 0000000..80353a5 --- /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 0000000..6848afe --- /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 0000000..5e8fb88 --- /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 } +}