From e16442df36055f66eb3f3b50c495604d0d04001a Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 17:21:29 +0200 Subject: [PATCH 01/22] Allow `use` of non-module declarations --- include/artic/ast.h | 14 ++++++++++++-- src/ast.cpp | 10 ++++++++++ src/check.cpp | 15 +++++++-------- src/emit.cpp | 4 +++- test/CMakeLists.txt | 4 +++- test/simple/{use.art => use1.art} | 0 test/simple/use2.art | 14 ++++++++++++++ test/simple/use3.art | 8 ++++++++ 8 files changed, 57 insertions(+), 12 deletions(-) rename test/simple/{use.art => use1.art} (100%) create mode 100644 test/simple/use2.art create mode 100644 test/simple/use3.art diff --git a/include/artic/ast.h b/include/artic/ast.h index 0b1739fa..1641678c 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" @@ -198,9 +199,9 @@ struct Path : public Node { : Node(loc), elems(std::move(elems)) {} - const artic::Type* infer(TypeChecker&, bool, Ptr* = nullptr); + const artic::Type* infer(TypeChecker&, std::optional, Ptr* = nullptr); const artic::Type* infer(TypeChecker& checker) override { - return infer(checker, false, nullptr); + return infer(checker, std::nullopt, nullptr); } const thorin::Def* emit(Emitter&) const override; @@ -1207,6 +1208,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 +1217,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. @@ -1529,6 +1534,9 @@ struct ModDecl : public NamedDecl { struct UseDecl : public NamedDecl { Path path; + // 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 +1547,8 @@ struct UseDecl : public NamedDecl { void bind(NameBinder&) override; void resolve_summons(Summoner&) override {}; void print(Printer&) const override; + + bool is_value() override; }; /// Incorrect declaration, coming from parsing. diff --git a/src/ast.cpp b/src/ast.cpp index f11eb046..992eda87 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/check.cpp b/src/check.cpp index ccbacbd5..09abb9ed 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -625,14 +625,14 @@ const artic::Type* Ptrn::check(TypeChecker& checker, const artic::Type* expected // Path ---------------------------------------------------------------------------- -const artic::Type* Path::infer(TypeChecker& checker, bool value_expected, Ptr* arg) { +const artic::Type* Path::infer(TypeChecker& checker, std::optional value_expected, Ptr* arg) { if (!start_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_value = elems.size() == 1 && start_decl->is_value(); is_ctor = start_decl->isa(); // Inspect every element of the path @@ -673,7 +673,7 @@ const artic::Type* Path::infer(TypeChecker& checker, bool value_expected, Ptr(type); - is_ctor && value_expected && struct_type && struct_type->is_tuple_like()) { + is_ctor && (*value_expected && 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) @@ -723,15 +723,15 @@ 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_value = member.is_value(); is_ctor = member.isa(); } else return checker.type_expected(elem.loc, type, "module or enum"); } } - if (is_value != value_expected) { - checker.error(loc, "{} expected, but got '{}'", value_expected ? "value" : "type", *this); + if (value_expected && 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 +1732,8 @@ const artic::Type* UseDecl::infer(TypeChecker& checker) { if (!checker.enter_decl(this)) return checker.type_table.type_error(); auto path_type = checker.infer(path); + is_value_ = path.is_value; checker.exit_decl(this); - if (!path_type->isa()) - return checker.type_expected(path.loc, path_type, "module type"); return path_type; } diff --git a/src/emit.cpp b/src/emit.cpp index 2e49d83b..986d71b4 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1760,7 +1760,9 @@ const thorin::Def* ModDecl::emit(Emitter& emitter) const { return nullptr; } -const thorin::Def* UseDecl::emit(Emitter&) const { +const thorin::Def* UseDecl::emit(Emitter& emitter) const { + if (path.is_value) + return path.emit(emitter); return nullptr; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a1e3f70e..c4daddb9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,7 +62,9 @@ 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_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 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; +} From a82a3fce271262678ef2a735892ec96310a5310d Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 17:28:18 +0200 Subject: [PATCH 02/22] remove 'allow_type' from parse_path as it's always true --- include/artic/parser.h | 4 ++-- src/emit.cpp | 1 + src/parser.cpp | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/artic/parser.h b/include/artic/parser.h index 15b248f2..e60de01b 100644 --- a/include/artic/parser.h +++ b/include/artic/parser.h @@ -101,8 +101,8 @@ 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::Path parse_path(ast::Identifier&&); + ast::Path parse_path() { return parse_path(parse_path_elem()); } ast::Identifier parse_path_elem(); ast::Identifier parse_id(); ast::AsmExpr::Constr parse_constr(); diff --git a/src/emit.cpp b/src/emit.cpp index 986d71b4..1080d350 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1181,6 +1181,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); } diff --git a/src/parser.cpp b/src/parser.cpp index 2f3634e2..5e4af15a 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -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)); 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) { Tracker tracker(this, id.loc); std::vector elems; @@ -1179,7 +1179,7 @@ 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()); }); From 4f15a45962a2153a3aebcd4e1a7e1a4c0dde7b3f Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 18:44:55 +0200 Subject: [PATCH 03/22] remove dead field in UseDecl --- include/artic/ast.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index 1641678c..7eeb2e9b 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -1504,8 +1504,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. From 48eacc8af31ecb3e03eaae37cfd2a9b59809b0e6 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 18:45:19 +0200 Subject: [PATCH 04/22] add find_member helper method on ModDecl --- include/artic/ast.h | 1 + src/bind.cpp | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/include/artic/ast.h b/include/artic/ast.h index 7eeb2e9b..84adbd72 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -1519,6 +1519,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; diff --git a/src/bind.cpp b/src/bind.cpp index e094c608..b38f8271 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -524,6 +524,15 @@ 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; +} + void UseDecl::bind_head(NameBinder& binder) { if (id.name != "") binder.insert_symbol(*this); From 232a92f37a87200aeaebcd25c4910a680dea86ee Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 18:45:47 +0200 Subject: [PATCH 05/22] parser: added wildcard support to Path --- include/artic/parser.h | 6 +++--- src/parser.cpp | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/include/artic/parser.h b/include/artic/parser.h index e60de01b..fbecaeab 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&&); - ast::Path parse_path() { return parse_path(parse_path_elem()); } - 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/parser.cpp b/src/parser.cpp index 5e4af15a..d09ff24d 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)); + auto path = parse_path(std::move(id), false); if (ahead().tag() == Token::LBrace) ptrn = parse_record_ptrn(std::move(path)); else if (allow_types) { @@ -1171,7 +1171,7 @@ Ptr Parser::parse_attr() { } } -ast::Path Parser::parse_path(ast::Identifier&& id) { +ast::Path Parser::parse_path(ast::Identifier&& id, bool allow_wildcard) { Tracker tracker(this, id.loc); std::vector elems; @@ -1185,16 +1185,20 @@ ast::Path Parser::parse_path(ast::Identifier&& id) { }); } 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(); } From f6ca46f378e17208d1152999bd1fd43812431f99 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 18:47:42 +0200 Subject: [PATCH 06/22] hacky support for wildcard binders in 'use' declarations --- include/artic/ast.h | 2 ++ src/bind.cpp | 44 +++++++++++++++++++++++++++++++++++++++++++- src/check.cpp | 2 ++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index 84adbd72..75686491 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -1533,6 +1533,8 @@ struct ModDecl : public NamedDecl { struct UseDecl : public NamedDecl { Path path; + PtrVector wildcard_imports; + // Set during type-checking bool is_value_ = false; diff --git a/src/bind.cpp b/src/bind.cpp index b38f8271..48cb2149 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -536,12 +536,54 @@ std::optional ModDecl::find_member(const std::string_view& name) con 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); + else { + path.bind(binder); + NamedDecl* decl = path.start_decl; + ModDecl* mod = nullptr; + for (size_t i = 1; i < path.elems.size(); i++) { + if ((mod = decl->isa())) { + if (i == path.elems.size() - 1) + break; + auto member = mod->find_member(path.elems[i].id.name); + if (member) { + decl = *member; + } else { + binder.error(path.elems[i].id.loc, "no member '{}' in '{}'", path.elems[i].id.name, mod->id.name); + return; + } + } else { + binder.error(path.elems[i].id.loc, "'{}' is not a module", decl); + } + } + + for (auto& decl : 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(NameBinder& binder) { path.bind(binder); + + for (auto& m : wildcard_imports) { + m->bind(binder); + } } void ErrorDecl::bind(NameBinder&) {} diff --git a/src/check.cpp b/src/check.cpp index 09abb9ed..a3b879f7 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -628,6 +628,8 @@ const artic::Type* Ptrn::check(TypeChecker& checker, const artic::Type* expected const artic::Type* Path::infer(TypeChecker& checker, std::optional value_expected, Ptr* arg) { if (!start_decl) return checker.type_table.type_error(); + if (elems.back().id.name == "*") + return nullptr; type = elems[0].is_super() ? checker.type_table.mod_type(*start_decl->as()) From 0badd8f9843f34db5d5979204aa103c0778e952d Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 18:49:02 +0200 Subject: [PATCH 07/22] added a simple test for wildcard uses --- test/CMakeLists.txt | 1 + test/simple/use4.art | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 test/simple/use4.art diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c4daddb9..16fb079b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -65,6 +65,7 @@ add_test(NAME simple_mod3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC 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/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 } +} From b6f40350b636e3332a3e17d99ea811e3d23c6265 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 4 Jul 2024 18:52:01 +0200 Subject: [PATCH 08/22] document the new use capabilities in the README --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0da11500..1c6eb3c7 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); From a54b74f08b5acb1360d81156125f6c4406740179 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 5 Jul 2024 16:02:09 +0200 Subject: [PATCH 09/22] bind Path during NameBinding --- include/artic/ast.h | 12 ++- include/artic/bind.h | 2 + src/bind.cpp | 181 ++++++++++++++++++++++++++++++------------- 3 files changed, 138 insertions(+), 57 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index 75686491..18f5474f 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -51,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. @@ -172,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)) @@ -189,7 +194,8 @@ 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; @@ -1468,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; @@ -1533,6 +1541,7 @@ struct ModDecl : public NamedDecl { struct UseDecl : public NamedDecl { Path path; + NamedDecl* bound_to; PtrVector wildcard_imports; // Set during type-checking @@ -1549,6 +1558,7 @@ struct UseDecl : public NamedDecl { void resolve_summons(Summoner&) override {}; void print(Printer&) const override; + void bind_wildcard(NameBinder&); bool is_value() override; }; diff --git a/include/artic/bind.h b/include/artic/bind.h index 70b48b0b..d8e15ffa 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/src/bind.cpp b/src/bind.cpp index 48cb2149..a2c3f9e1 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,78 @@ 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 + decl = binder.cur_mod; for (auto& elem : elems) { + assert(decl); + //while (auto use = decl->isa()) { + // decl = use.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 (!start_decl) { + binder.error(elem.loc, "wildcards cannot appear at the start of a path!"); + return; + } + decl = nullptr; + } else if (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 use = decl->isa()) { + binder.bind(*use); + assert(use->bound_to); + decl = use->bound_to; + } 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; + + elem.decl = decl; + + // Bind the type arguments of each element for (auto& arg : elem.args) binder.bind(*arg); } @@ -519,6 +570,10 @@ 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; @@ -533,53 +588,67 @@ std::optional ModDecl::find_member(const std::string_view& name) con return std::nullopt; } -void UseDecl::bind_head(NameBinder& binder) { - if (id.name != "") - binder.insert_symbol(*this); - else if (path.elems.back().id.name != "*") - binder.insert_symbol(*this, path.elems.back().id.name); - else { - path.bind(binder); - NamedDecl* decl = path.start_decl; - ModDecl* mod = nullptr; - for (size_t i = 1; i < path.elems.size(); i++) { - if ((mod = decl->isa())) { - if (i == path.elems.size() - 1) - break; - auto member = mod->find_member(path.elems[i].id.name); - if (member) { - decl = *member; - } else { - binder.error(path.elems[i].id.loc, "no member '{}' in '{}'", path.elems[i].id.name, mod->id.name); - return; - } +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); + NamedDecl* decl = path.start_decl; + ModDecl* mod = nullptr; + for (size_t i = 1; i < path.elems.size(); i++) { + if ((mod = decl->isa())) { + if (i == path.elems.size() - 1) + break; + auto member = mod->find_member(path.elems[i].id.name); + if (member) { + decl = *member; } else { - binder.error(path.elems[i].id.loc, "'{}' is not a module", decl); + binder.error(path.elems[i].id.loc, "no member '{}' in '{}'", path.elems[i].id.name, mod->id.name); + return; } + } else { + binder.error(path.elems[i].id.loc, "'{}' is not a module", decl); } + } - for (auto& decl : 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); + for (auto& decl : 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 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); From 673aa3c03b418165f8ec72d291a841464d30b922 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 5 Jul 2024 16:42:52 +0200 Subject: [PATCH 10/22] move the is_value logic out of Path::infer --- include/artic/ast.h | 5 ++- src/bind.cpp | 97 +++++++++++++++++++++++---------------------- src/check.cpp | 54 ++++++++++++++----------- src/emit.cpp | 11 +++-- 4 files changed, 91 insertions(+), 76 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index 18f5474f..10c2fdb5 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -205,9 +205,10 @@ struct Path : public Node { : Node(loc), elems(std::move(elems)) {} - const artic::Type* infer(TypeChecker&, std::optional, Ptr* = nullptr); + const artic::Type* infer(TypeChecker&, Ptr*); + const artic::Type* infer(TypeChecker&, bool, Ptr* = nullptr); const artic::Type* infer(TypeChecker& checker) override { - return infer(checker, std::nullopt, nullptr); + return infer(checker, nullptr); } const thorin::Def* emit(Emitter&) const override; diff --git a/src/bind.cpp b/src/bind.cpp index a2c3f9e1..66911f31 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -69,55 +69,56 @@ void Path::bind(NameBinder& binder) { decl = binder.cur_mod; for (auto& elem : elems) { assert(decl); - //while (auto use = decl->isa()) { - // decl = use.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 (!start_decl) { - binder.error(elem.loc, "wildcards cannot appear at the start of a path!"); - return; - } - decl = nullptr; - } else if (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 use = decl->isa()) { - binder.bind(*use); - assert(use->bound_to); - decl = use->bound_to; - } 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; + + while (true) { + 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 (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 use = decl->isa()) { + binder.bind(*use); + assert(use->bound_to); + decl = use->bound_to; + continue; + } 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); } - decl = *found; - } else { - // ... - assert(false); + break; } if (!start_decl) diff --git a/src/check.cpp b/src/check.cpp index a3b879f7..6d4eb3d3 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -625,11 +625,11 @@ const artic::Type* Ptrn::check(TypeChecker& checker, const artic::Type* expected // Path ---------------------------------------------------------------------------- -const artic::Type* Path::infer(TypeChecker& checker, std::optional value_expected, Ptr* arg) { - if (!start_decl) - return checker.type_table.type_error(); - if (elems.back().id.name == "*") +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()) @@ -673,21 +673,6 @@ const artic::Type* Path::infer(TypeChecker& checker, std::optional value_e } elem.type = type; - // Treat tuple-like structure constructors as functions - if (auto [type_app, struct_type] = match_app(type); - is_ctor && (*value_expected && 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()) { @@ -732,10 +717,32 @@ const artic::Type* Path::infer(TypeChecker& checker, std::optional value_e } } - if (value_expected && is_value != *value_expected) { - checker.error(loc, "{} expected, but got '{}'", *value_expected ? "value" : "type", *this); + return type; +} + +const artic::Type* Path::infer(artic::TypeChecker& checker, bool value_expected, Ptr* arg) { + infer(checker, arg); + + // 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; } @@ -1734,7 +1741,8 @@ const artic::Type* UseDecl::infer(TypeChecker& checker) { if (!checker.enter_decl(this)) return checker.type_table.type_error(); auto path_type = checker.infer(path); - is_value_ = path.is_value; + if (path.decl) + is_value_ = path.decl->is_value(); checker.exit_decl(this); return path_type; } @@ -1800,7 +1808,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 1080d350..197c38da 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()) { @@ -1756,14 +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& emitter) const { - if (path.is_value) - return path.emit(emitter); + if (path.decl) + return emitter.emit(*path.decl); return nullptr; } From 0c861b634f8d48ad78b7749e96af5987cb2ad901 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 5 Jul 2024 17:32:05 +0200 Subject: [PATCH 11/22] use new Path::bind capability to resolve wildcards --- src/bind.cpp | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/bind.cpp b/src/bind.cpp index 66911f31..177d1043 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -602,22 +602,12 @@ void UseDecl::bind_wildcard(artic::NameBinder& binder) { return; binder.bind(path); - NamedDecl* decl = path.start_decl; - ModDecl* mod = nullptr; - for (size_t i = 1; i < path.elems.size(); i++) { - if ((mod = decl->isa())) { - if (i == path.elems.size() - 1) - break; - auto member = mod->find_member(path.elems[i].id.name); - if (member) { - decl = *member; - } else { - binder.error(path.elems[i].id.loc, "no member '{}' in '{}'", path.elems[i].id.name, mod->id.name); - return; - } - } else { - binder.error(path.elems[i].id.loc, "'{}' is not a module", decl); - } + assert(path.elems.size() >= 2); + auto& penultimate = path.elems[path.elems.size() - 2]; + NamedDecl* decl = penultimate.decl; + auto mod = decl->isa(); + if (!mod) { + binder.error(penultimate.id.loc, "'{}' is not a module", decl->id.name); } for (auto& decl : mod->decls) { From 7d44e9b31e1f50cb2be96b433072283a721b40bf Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 5 Jul 2024 17:33:06 +0200 Subject: [PATCH 12/22] naming --- src/bind.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bind.cpp b/src/bind.cpp index 177d1043..63216e0c 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -604,13 +604,13 @@ void UseDecl::bind_wildcard(artic::NameBinder& binder) { binder.bind(path); assert(path.elems.size() >= 2); auto& penultimate = path.elems[path.elems.size() - 2]; - NamedDecl* decl = penultimate.decl; - auto mod = decl->isa(); - if (!mod) { - binder.error(penultimate.id.loc, "'{}' is not a module", decl->id.name); + 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 : mod->decls) { + for (auto& decl : imported_mod->decls) { auto member = decl->isa(); if (!member) continue; From a2a0c7c9b8a10d7826166ec583e082e4d22cc626 Mon Sep 17 00:00:00 2001 From: Gobrosse Date: Tue, 9 Jul 2024 14:31:28 +0200 Subject: [PATCH 13/22] don't pre-resolve intermediary UseDecls in Path::bind --- src/bind.cpp | 102 ++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/src/bind.cpp b/src/bind.cpp index 63216e0c..337346da 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -66,64 +66,68 @@ namespace ast { // Path ---------------------------------------------------------------------------- void Path::bind(NameBinder& binder) { - decl = binder.cur_mod; + NamedDecl* decl = binder.cur_mod; + size_t i = 0; for (auto& elem : elems) { assert(decl); - while (true) { - 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 (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 use = decl->isa()) { - binder.bind(*use); - assert(use->bound_to); - decl = use->bound_to; - continue; - } 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); + // 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; } - break; + 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 From a51f8be0b19e0d44e9a3b1ad8afa0309c5c29f4c Mon Sep 17 00:00:00 2001 From: Gobrosse Date: Tue, 9 Jul 2024 14:34:03 +0200 Subject: [PATCH 14/22] removed is_ctor member from Path --- include/artic/ast.h | 1 - src/check.cpp | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index 10c2fdb5..58827c72 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -199,7 +199,6 @@ struct Path : public Node { // 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)) diff --git a/src/check.cpp b/src/check.cpp index 6d4eb3d3..d719f4ae 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -635,7 +635,6 @@ const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { ? checker.type_table.mod_type(*start_decl->as()) : checker.infer(*start_decl); is_value = elems.size() == 1 && start_decl->is_value(); - is_ctor = start_decl->isa(); // Inspect every element of the path for (size_t i = 0, n = elems.size(); i < n; ++i) { @@ -693,11 +692,10 @@ const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { 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; + is_value = true; } } else if (auto mod_type = type->isa()) { auto index = mod_type->find_member(elems[i + 1].id.name); @@ -711,7 +709,6 @@ const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { ? checker.type_table.mod_type(*member.as()) : checker.infer(mod_type->member(*index)); is_value = member.is_value(); - is_ctor = member.isa(); } else return checker.type_expected(elem.loc, type, "module or enum"); } @@ -723,6 +720,8 @@ const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { 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()) { From 3551b5eb67eef6da9a79592f359646237863ba95 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Wed, 7 Jan 2026 14:51:43 +0100 Subject: [PATCH 15/22] allow importing definitions with type params --- include/artic/ast.h | 6 ++++-- include/artic/parser.h | 4 ++-- src/ast.cpp | 2 +- src/bind.cpp | 2 +- src/check.cpp | 2 +- src/parser.cpp | 6 +++--- test/CMakeLists.txt | 1 + test/simple/use5.art | 10 ++++++++++ 8 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 test/simple/use5.art diff --git a/include/artic/ast.h b/include/artic/ast.h index 593fb376..d90adc07 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -187,6 +187,8 @@ struct Path : public Node { }; 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. @@ -197,8 +199,8 @@ struct Path : public Node { // Set during type-checking bool is_value = 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*); diff --git a/include/artic/parser.h b/include/artic/parser.h index 36f74e32..b148cdae 100644 --- a/include/artic/parser.h +++ b/include/artic/parser.h @@ -101,8 +101,8 @@ class Parser : public Logger { Ptr parse_attr_list(); Ptr parse_attr(); - 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::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(); diff --git a/src/ast.cpp b/src/ast.cpp index 366ecf21..756b66ab 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -651,7 +651,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 77f2e91b..f80db4d2 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -635,7 +635,7 @@ void UseDecl::bind_wildcard(artic::NameBinder& binder) { 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)); + Path member_path(path.loc, false, 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); diff --git a/src/check.cpp b/src/check.cpp index dcf81a49..35168bd5 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -667,7 +667,7 @@ const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { type = user_type ? checker.type_table.type_app(user_type, std::move(type_args)) : forall_type->instantiate(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(); } diff --git a/src/parser.cpp b/src/parser.cpp index db6ec79e..1f7b2a9e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1181,7 +1181,7 @@ Ptr Parser::parse_attr() { } } -ast::Path Parser::parse_path(ast::Identifier&& id, bool allow_wildcard) { +ast::Path Parser::parse_path(ast::Identifier&& id, bool is_use_path) { Tracker tracker(this, id.loc); std::vector elems; @@ -1199,10 +1199,10 @@ ast::Path Parser::parse_path(ast::Identifier&& id, bool allow_wildcard) { break; if (!accept(Token::DblColon)) break; - id = parse_path_elem(allow_wildcard); + 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(bool allow_wildcard) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89712755..f0b1dd34 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -67,6 +67,7 @@ add_test(NAME simple_use1 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC 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_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/use5.art b/test/simple/use5.art new file mode 100644 index 00000000..24e8f500 --- /dev/null +++ b/test/simple/use5.art @@ -0,0 +1,10 @@ +mod A { + struct S[T] { + t: T + } + + fn f(S[i32]) -> (); +} + +use A::S; +fn g(s: S[i32]) = A::f(s); \ No newline at end of file From 99b89a895d8e9ff5d9a86c8be1432bff5496f287 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Wed, 7 Jan 2026 15:01:03 +0100 Subject: [PATCH 16/22] fix wildcard uses not allowing unspecialized polymorphic defs --- src/bind.cpp | 2 +- test/CMakeLists.txt | 1 + test/simple/use6.art | 11 +++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/simple/use6.art diff --git a/src/bind.cpp b/src/bind.cpp index f80db4d2..40c44f28 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -635,7 +635,7 @@ void UseDecl::bind_wildcard(artic::NameBinder& binder) { member_path_elements.emplace_back(std::move(nelem)); } member_path_elements.back().id.name = member->id.name; - Path member_path(path.loc, false, std::move(member_path_elements)); + 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); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f0b1dd34..68fea9ed 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -68,6 +68,7 @@ add_test(NAME simple_use2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC 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) 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 From 3c8fd85d80946b4b15b4ef9a8b720384759844b6 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Wed, 7 Jan 2026 15:02:23 +0100 Subject: [PATCH 17/22] minor cleanup --- src/bind.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/bind.cpp b/src/bind.cpp index 40c44f28..e56c0fa2 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -585,10 +585,6 @@ 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; @@ -647,6 +643,7 @@ void UseDecl::bind_head(NameBinder& binder) { binder.insert_symbol(*this); else if (path.elems.back().id.name != "*") binder.insert_symbol(*this, path.elems.back().id.name); + bind_wildcard(binder); } void UseDecl::bind(NameBinder& binder) { From b1df0e89d45a502d8356f2e5781c09f6e0bbffa0 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 8 Jan 2026 15:39:49 +0100 Subject: [PATCH 18/22] add support for emitting empty tuple-like structs from a non-trivial path --- include/artic/ast.h | 20 +++--- src/ast.cpp | 8 --- src/bind.cpp | 14 ++--- src/check.cpp | 121 +++++++++++++++++++----------------- src/emit.cpp | 30 ++++----- test/simple/tuple_like2.art | 2 + 6 files changed, 100 insertions(+), 95 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index d90adc07..040996e5 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -184,6 +184,8 @@ struct Path : public Node { 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; @@ -198,6 +200,8 @@ struct Path : public Node { // 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, bool is_use_path, std::vector&& elems) : Node(loc), is_use_path_(is_use_path), elems(std::move(elems)) @@ -1225,8 +1229,6 @@ 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. @@ -1234,8 +1236,6 @@ 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. @@ -1555,9 +1555,6 @@ struct UseDecl : public NamedDecl { 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)) {} @@ -1570,9 +1567,16 @@ struct UseDecl : public NamedDecl { void print(Printer&) const override; void bind_wildcard(NameBinder&); - bool is_value() override; }; +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/src/ast.cpp b/src/ast.cpp index 756b66ab..d6033872 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -609,14 +609,6 @@ bool AsmExpr::has_side_effect() const { // 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 e56c0fa2..724a44f9 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -72,11 +72,7 @@ void Path::bind(NameBinder& binder) { 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; - } + NamedDecl* actual_decl = resolve_use_decl(decl); if (elem.id.name[0] == '_') binder.error(elem.id.loc, "identifiers beginning with '_' cannot be referenced"); @@ -103,14 +99,14 @@ void Path::bind(NameBinder& binder) { binder.note("did you mean '{}'?", similar->decl->id.name); } else decl = symbol->decl; - } else if (auto mod = decl->isa()) { + } 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 = decl->isa()) { + } 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); @@ -122,8 +118,10 @@ void Path::bind(NameBinder& binder) { assert(false); } - if (!start_decl) + if (!start_decl) { + assert(i == 0); start_decl = decl; + } if (i++ == elems.size() - 1) this->decl = decl; diff --git a/src/check.cpp b/src/check.cpp index 35168bd5..cce39a35 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -630,24 +630,71 @@ const artic::Type* Ptrn::check(TypeChecker& checker, const artic::Type* expected // Path ---------------------------------------------------------------------------- +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(*path.start_decl->as()); + } else { + return type = checker.infer(*path.start_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"); +} + 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->is_value(); - // 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() @@ -664,8 +711,8 @@ const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { return checker.type_table.type_error(); } elem.inferred_args = type_args; - type = user_type - ? checker.type_table.type_app(user_type, std::move(type_args)) + elem.type = user_type + ? checker.type_table.type_app(user_type, type_args) : forall_type->instantiate(type_args); } 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()); @@ -675,61 +722,22 @@ const artic::Type* Path::infer(TypeChecker& checker, Ptr* arg) { checker.error(elem.loc, "type arguments are not allowed here"); return checker.type_table.type_error(); } - elem.type = type; - - // 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; - } 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 = 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.is_value(); - } else - return checker.type_expected(elem.loc, type, "module or enum"); - } } - return type; + return type = elems.back().type; } const artic::Type* Path::infer(artic::TypeChecker& checker, bool value_expected, Ptr* arg) { - infer(checker, arg); + type = infer(checker, arg); + + auto last_decl = resolve_use_decl(decl); - auto is_ctor = decl->isa(); + 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); - is_ctor && value_expected && struct_type && struct_type->is_tuple_like()) { + 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) @@ -740,6 +748,7 @@ const artic::Type* Path::infer(artic::TypeChecker& checker, bool value_expected, type = checker.type_table.fn_type(dom, type); } is_value = true; + is_ctor = true; } if (is_value != value_expected) { @@ -1840,8 +1849,6 @@ 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); return path_type; } diff --git a/src/emit.cpp b/src/emit.cpp index e677443a..28db86d9 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1069,24 +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_super()) - decl = i == 0 ? start_decl : decl->as()->super; 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()) + continue; + + 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 (auto mod_type = elems[i].type->isa()) { - decl = &mod_type->member(elems[i + 1].index); - } else if (!elems[i].decl->isa()) { + if (!is_ctor) { // If type arguments are present, this is a polymorphic application std::unordered_map map; if (!elems[i].inferred_args.empty()) { @@ -1110,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 @@ -1127,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. 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; From e4ad8d6ea357300614e33a9ba25be0f7ca2f133d Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 8 Jan 2026 15:40:21 +0100 Subject: [PATCH 19/22] add test to validate we can emit polymorphic fns from paths with uses --- test/simple/use5.art | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/simple/use5.art b/test/simple/use5.art index 24e8f500..d62ebd25 100644 --- a/test/simple/use5.art +++ b/test/simple/use5.art @@ -4,7 +4,12 @@ mod A { } fn f(S[i32]) -> (); + + fn foo[T](t: T) = S[T] { t = t }; } use A::S; -fn g(s: S[i32]) = A::f(s); \ No newline at end of file +fn g(s: S[i32]) = A::f(s); + +use A::foo; +fn h() = foo(5); \ No newline at end of file From 73be37572ffa28ae0dd5242e916863fef6669dd7 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 8 Jan 2026 15:49:45 +0100 Subject: [PATCH 20/22] allow arbitrary long paths for constant static expressions --- src/ast.cpp | 4 ++-- test/simple/static.art | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index d6033872..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; } 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 } From f4cfc53730251677c703ea53f4a685ba89ecb137 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Thu, 8 Jan 2026 17:03:37 +0100 Subject: [PATCH 21/22] cleanup/improve support for static values as array sizes --- include/artic/ast.h | 2 + include/artic/check.h | 2 + src/bind.cpp | 5 +- src/check.cpp | 105 +++++++++++---------------------------- test/CMakeLists.txt | 2 + test/failure/arrays3.art | 8 +++ test/failure/arrays4.art | 2 + test/simple/arrays3.art | 7 +++ 8 files changed, 53 insertions(+), 80 deletions(-) create mode 100644 test/failure/arrays3.art create mode 100644 test/failure/arrays4.art diff --git a/include/artic/ast.h b/include/artic/ast.h index 040996e5..68c08c8b 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -163,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 { 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/src/bind.cpp b/src/bind.cpp index 724a44f9..09d84275 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -85,7 +85,7 @@ void Path::bind(NameBinder& binder) { } else binder.error(elem.id.loc, "''super' can only be used on modules"); } else if (elem.is_wildcard()) { - if (!start_decl) { + if (i == 0) { binder.error(elem.loc, "wildcards cannot appear at the start of a path!"); return; } @@ -118,8 +118,7 @@ void Path::bind(NameBinder& binder) { assert(false); } - if (!start_decl) { - assert(i == 0); + if (i == 0) { start_decl = decl; } diff --git a/src/check.cpp b/src/check.cpp index cce39a35..4b572ee7 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -604,6 +604,26 @@ 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(); + auto ty = static_decl->as()->type->isa(); + auto ty_ok = ty && is_int_type(ty); + ast::LiteralExpr* lit_value = nullptr; + if (static_decl && !static_decl->is_mut && static_decl->init) + lit_value = static_decl->init->isa(); + if (ty_ok && lit_value) + 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 (!ty_ok) + 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) { @@ -633,9 +653,9 @@ const artic::Type* Ptrn::check(TypeChecker& checker, const artic::Type* expected 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(*path.start_decl->as()); + return type = checker.type_table.mod_type(*decl->as()); } else { - return type = checker.infer(*path.start_decl); + return type = checker.infer(*decl); } } else if (is_super()) { assert(prev_elem_type); @@ -869,31 +889,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 (!decl->isa()) { - 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); @@ -1044,31 +1041,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 (!decl->isa()) { - 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); @@ -1077,31 +1051,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 (!decl->isa()) { - 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", diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 68fea9ed..9a097310 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -108,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]; From 9e33f89dcea1d75598c4c37f23f60c6925707de6 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 9 Jan 2026 11:20:54 +0100 Subject: [PATCH 22/22] fix broken check for static array sizes --- src/check.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/check.cpp b/src/check.cpp index 4b572ee7..2c5dd19f 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -607,19 +607,17 @@ const Type* TypeChecker::infer_record_type(const TypeApp* type_app, const Struct 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(); - auto ty = static_decl->as()->type->isa(); - auto ty_ok = ty && is_int_type(ty); ast::LiteralExpr* lit_value = nullptr; if (static_decl && !static_decl->is_mut && static_decl->init) lit_value = static_decl->init->isa(); - if (ty_ok && lit_value) + 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 (!ty_ok) + if (!lit_value || !lit_value->lit.is_integer()) note(static_decl->loc, "{} is not of an integer type", path); return 0; }