Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
}
Expand All @@ -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);
Expand Down
31 changes: 26 additions & 5 deletions include/artic/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <memory>
#include <vector>
#include <variant>
#include <optional>

#include "artic/loc.h"
#include "artic/log.h"
Expand Down Expand Up @@ -50,6 +51,7 @@ struct Node : public Cast<Node> {
/// 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.
Expand Down Expand Up @@ -171,12 +173,16 @@ struct Path : public Node {
Identifier id;
PtrVector<Type> 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<const artic::Type*> inferred_args;

bool is_super() const { return id.name == "super"; }
bool is_wildcard() const { return id.name == "*"; }

Elem(const Loc& loc, Identifier&& id, PtrVector<Type>&& args)
: loc(loc), id(std::move(id)), args(std::move(args))
Expand All @@ -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<Elem>&& elems)
: Node(loc), elems(std::move(elems))
{}

const artic::Type* infer(TypeChecker&, Ptr<Expr>*);
const artic::Type* infer(TypeChecker&, bool, Ptr<Expr>* = nullptr);
const artic::Type* infer(TypeChecker& checker) override {
return infer(checker, false, nullptr);
return infer(checker, nullptr);
}

const thorin::Def* emit(Emitter&) const override;
Expand Down Expand Up @@ -1207,13 +1214,17 @@ 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.
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.
Expand Down Expand Up @@ -1463,6 +1474,8 @@ struct EnumDecl : public CtorDecl {
, options(std::move(options))
{}

std::optional<OptionDecl*> 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;
Expand Down Expand Up @@ -1499,8 +1512,6 @@ struct ModDecl : public NamedDecl {
PtrVector<Decl> decls;
ModDecl* super = nullptr;

std::vector<const NamedDecl*> 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.
Expand All @@ -1516,6 +1527,7 @@ struct ModDecl : public NamedDecl {
}

void set_super();
std::optional<NamedDecl*> find_member(const std::string_view& name) const;

const thorin::Def* emit(Emitter&) const override;
const artic::Type* infer(TypeChecker&) override;
Expand All @@ -1529,6 +1541,12 @@ struct ModDecl : public NamedDecl {
struct UseDecl : public NamedDecl {
Path path;

NamedDecl* bound_to;
PtrVector<UseDecl> 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))
{}
Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions include/artic/bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
6 changes: 3 additions & 3 deletions include/artic/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ class Parser : public Logger {
Ptr<ast::AttrList> parse_attr_list();
Ptr<ast::Attr> 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();
Expand Down
10 changes: 10 additions & 0 deletions src/ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 IdPtrn*>&) const {}
Expand Down
155 changes: 135 additions & 20 deletions src/bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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<ast::ModDecl>(); 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<UseDecl>()) {
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<ModDecl>()) {
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<ModDecl>()) {
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<EnumDecl>()) {
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);
}
Expand Down Expand Up @@ -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<UseDecl>())
use->bind_wildcard(binder);
}
for (auto& decl : decls) binder.bind(*decl);
std::swap(binder.scopes_, old_scopes);
binder.cur_mod = old_mod;
}

std::optional<NamedDecl*> ModDecl::find_member(const std::string_view& name) const {
for (const auto& decl : decls) {
if (auto named = decl->isa<NamedDecl>())
if (named->id.name == name)
return std::make_optional(named);
}
return std::nullopt;
}

std::optional<OptionDecl*> 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<ModDecl>();
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<NamedDecl>();
if (!member)
continue;
std::vector<Path::Elem> 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<Type>()));
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<UseDecl>(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&) {}
Expand Down
Loading