From 8304a3ef4202b9ee76de6ddcfe5bda81cdc62b3f Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 25 Nov 2022 12:17:01 +0100 Subject: [PATCH 01/22] added ImplicitDecl and SummonExpr --- include/artic/ast.h | 35 +++++++++++++++++++++++++++++++++++ include/artic/implicits.h | 0 src/bind.cpp | 9 +++++++++ src/check.cpp | 28 ++++++++++++++++++++++++++++ src/implicits.cpp | 0 src/print.cpp | 17 +++++++++++++++++ 6 files changed, 89 insertions(+) create mode 100644 include/artic/implicits.h create mode 100644 src/implicits.cpp diff --git a/include/artic/ast.h b/include/artic/ast.h index 2367e43a..6df23795 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -514,6 +514,20 @@ struct LiteralExpr : public Expr { void print(Printer&) const override; }; +/// Expression summoning an implicit value. +struct SummonExpr : public Expr { + Ptr type; + + SummonExpr(const Loc& loc, Ptr&& type) + : Expr(loc), type(std::move(type)) + {} + + const artic::Type* infer(TypeChecker&) override; + const artic::Type* check(TypeChecker&, const artic::Type*) override; + void bind(NameBinder&) override; + void print(Printer&) const override; +}; + /// Field expression, part of a record expression. struct FieldExpr : public Expr { Identifier id; @@ -1221,6 +1235,27 @@ struct LetDecl : public Decl { void print(Printer&) const override; }; +/// Declaration that introduces an implicit value, or implicit value generator in the scope +struct ImplicitDecl : public Decl { + Ptr type; + Ptr value; + bool is_generator; + + ImplicitDecl(const Loc& loc, + Ptr&& type, + Ptr&& value, + bool is_generator = false) + : Decl(loc) + , type(std::move(type)) + , value(std::move(value)) + , is_generator(is_generator) + {} + + const artic::Type* infer(TypeChecker&) override; + void bind(NameBinder&) override; + void print(Printer&) const override; +}; + /// Static (top-level) declaration. struct StaticDecl : public ValueDecl { Ptr type; diff --git a/include/artic/implicits.h b/include/artic/implicits.h new file mode 100644 index 00000000..e69de29b diff --git a/src/bind.cpp b/src/bind.cpp index e2ae8539..176b0f6b 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -151,6 +151,10 @@ void PathExpr::bind(NameBinder& binder) { void LiteralExpr::bind(NameBinder&) {} +void SummonExpr::bind(artic::NameBinder& binder) { + if (type) binder.bind(*type); +} + void FieldExpr::bind(NameBinder& binder) { binder.bind(*expr); } @@ -377,6 +381,11 @@ void LetDecl::bind(NameBinder& binder) { binder.bind(*ptrn); } +void ImplicitDecl::bind(artic::NameBinder& binder) { + if (type) type->bind(binder); + value->bind(binder); +} + void StaticDecl::bind_head(NameBinder& binder) { binder.insert_symbol(*this); } diff --git a/src/check.cpp b/src/check.cpp index 56abad7a..9bd13746 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -849,6 +849,22 @@ const artic::Type* LiteralExpr::check(TypeChecker& checker, const artic::Type* e return checker.check(loc, lit, expected); } +const artic::Type* SummonExpr::infer(artic::TypeChecker& checker) { + if (type) return checker.infer(*type); + checker.error(loc, "summoning a value without a type"); + return checker.type_table.type_error(); +} + +const artic::Type* SummonExpr::check(artic::TypeChecker& checker, const artic::Type* expected) { + if (type) { + auto got = checker.infer(*this); + if (!expected->subtype(got)) + return checker.incompatible_types(loc, got, expected); + return got; + } + return expected; +} + const artic::Type* FieldExpr::check(TypeChecker& checker, const artic::Type* expected) { return checker.coerce(expr, expected); } @@ -1491,6 +1507,18 @@ const artic::Type* LetDecl::infer(TypeChecker& checker) { return checker.type_table.unit_type(); } +const artic::Type* ImplicitDecl::infer(TypeChecker& checker) { + const artic::Type* t = nullptr; + assert(!is_generator && "TODO"); + if (type) { + t = checker.infer(*type); + checker.coerce(value, t); + } else { + t = checker.infer(*value); + } + return t; +} + const artic::Type* StaticDecl::infer(TypeChecker& checker) { if (!checker.enter_decl(this)) return checker.type_table.type_error(); diff --git a/src/implicits.cpp b/src/implicits.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/print.cpp b/src/print.cpp index 303620dd..0e2a6ec3 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -109,6 +109,12 @@ void LiteralExpr::print(Printer& p) const { p << std::showpoint << log::literal_style(lit); } +void SummonExpr::print(Printer& p) const { + p << log::keyword_style("summon") << "["; + if (type) type->print(p); + p << "]"; +} + void FieldExpr::print(Printer& p) const { p << id.name << " = "; expr->print(p); @@ -466,6 +472,17 @@ void LetDecl::print(Printer& p) const { p << ';'; } +void ImplicitDecl::print(Printer& p) const { + p << log::keyword_style("implicit") << ' '; + if (type) { + type->print(p); + p << ' '; + } + p << " = "; + value->print(p); + p << ';'; +} + void StaticDecl::print(Printer& p) const { if (attrs) attrs->print(p); p << log::keyword_style("static") << ' '; From 7c76d009d8210b026a3ac4629206b758a1ae703e Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 25 Nov 2022 12:54:41 +0100 Subject: [PATCH 02/22] added syntax for implicit and summon --- include/artic/parser.h | 2 ++ include/artic/token.h | 2 ++ src/lexer.cpp | 2 ++ src/parser.cpp | 41 +++++++++++++++++++++++++++++++++-------- src/print.cpp | 4 ++-- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/include/artic/parser.h b/include/artic/parser.h index 8d7b2bca..08f8323a 100644 --- a/include/artic/parser.h +++ b/include/artic/parser.h @@ -31,6 +31,7 @@ class Parser : public Logger { Ptr parse_struct_decl(); Ptr parse_option_decl(); Ptr parse_enum_decl(); + Ptr parse_implicit_decl(); Ptr parse_static_decl(); Ptr parse_type_decl(); Ptr parse_type_param(); @@ -58,6 +59,7 @@ class Parser : public Logger { Ptr parse_typed_expr(Ptr&&); Ptr parse_path_expr(); Ptr parse_literal_expr(); + Ptr parse_summon_expr(); Ptr parse_field_expr(); Ptr parse_record_expr(ast::Path &&path); Ptr parse_record_expr(Ptr &&expr); diff --git a/include/artic/token.h b/include/artic/token.h index f15d7475..7d08a9a3 100644 --- a/include/artic/token.h +++ b/include/artic/token.h @@ -30,6 +30,8 @@ namespace artic { f(Enum, "enum") \ f(Type, "type") \ f(Static, "static") \ + f(Implicit, "implicit") \ + f(Summon, "summon") \ f(Mod, "mod") \ f(Use, "use") \ f(Super, "super") \ diff --git a/src/lexer.cpp b/src/lexer.cpp index 34da540e..1f474765 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -23,6 +23,8 @@ std::unordered_map Lexer::keywords{ std::make_pair("struct", Token::Struct), std::make_pair("enum", Token::Enum), std::make_pair("type", Token::Type), + std::make_pair("implicit", Token::Implicit), + std::make_pair("summon", Token::Summon), std::make_pair("static", Token::Static), std::make_pair("mod", Token::Mod), std::make_pair("use", Token::Use), diff --git a/src/parser.cpp b/src/parser.cpp index 69e0d9c6..5b49d4ab 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -38,14 +38,15 @@ Ptr Parser::parse_decl(bool is_top_level) { note("use a static variable instead"); } break; - case Token::Fn: decl = parse_fn_decl(); break; - case Token::Struct: decl = parse_struct_decl(); break; - case Token::Enum: decl = parse_enum_decl(); break; - case Token::Type: decl = parse_type_decl(); break; - case Token::Static: decl = parse_static_decl(); break; - case Token::Mod: decl = parse_mod_decl(); break; - case Token::Use: decl = parse_use_decl(); break; - default: decl = parse_error_decl(); break; + case Token::Fn: decl = parse_fn_decl(); break; + case Token::Struct: decl = parse_struct_decl(); break; + case Token::Enum: decl = parse_enum_decl(); break; + case Token::Type: decl = parse_type_decl(); break; + case Token::Implicit: decl = parse_implicit_decl(); break; + case Token::Static: decl = parse_static_decl(); break; + case Token::Mod: decl = parse_mod_decl(); break; + case Token::Use: decl = parse_use_decl(); break; + default: decl = parse_error_decl(); break; } decl->attrs = std::move(attrs); decl->is_top_level = is_top_level; @@ -195,6 +196,20 @@ Ptr Parser::parse_type_decl() { return make_ptr(tracker(), std::move(id), std::move(type_params), std::move(aliased_type)); } +Ptr Parser::parse_implicit_decl() { + Tracker tracker(this); + eat(Token::Implicit); + + Ptr type = nullptr; + if (ahead().tag() != Token::Eq) + type = parse_type(); + + expect(Token::Eq); + auto value = parse_expr(true); + expect(Token::Semi); + return make_ptr(tracker(), std::move(type), std::move(value)); +} + Ptr Parser::parse_static_decl() { Tracker tracker(this); eat(Token::Static); @@ -473,6 +488,15 @@ Ptr Parser::parse_literal_expr() { return make_ptr(tracker(), lit); } +Ptr Parser::parse_summon_expr() { + Tracker tracker(this); + eat(Token::Summon); + expect(Token::LBracket); + auto t = parse_type(); + expect(Token::RBracket); + return make_ptr(tracker(), std::move(t)); +} + Ptr Parser::parse_field_expr() { Tracker tracker(this); auto id = parse_id(); @@ -828,6 +852,7 @@ Ptr Parser::parse_primary_expr(bool allow_structs, bool allow_casts) case Token::Continue: expr = parse_continue_expr(); break; case Token::Return: expr = parse_return_expr(); break; case Token::Asm: expr = parse_asm_expr(); break; + case Token::Summon: expr = parse_summon_expr(); break; default: expr = parse_error_expr(); break; diff --git a/src/print.cpp b/src/print.cpp index 0e2a6ec3..a841d56d 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -473,10 +473,10 @@ void LetDecl::print(Printer& p) const { } void ImplicitDecl::print(Printer& p) const { - p << log::keyword_style("implicit") << ' '; + p << log::keyword_style("implicit"); if (type) { - type->print(p); p << ' '; + type->print(p); } p << " = "; value->print(p); From e0c77705de5e194743a9a3e63760cb154d8009be Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 25 Nov 2022 14:42:17 +0100 Subject: [PATCH 03/22] first go at resolving imlicitis --- include/artic/ast.h | 65 ++++++++++ include/artic/implicits.h | 0 include/artic/summoner.h | 38 ++++++ src/CMakeLists.txt | 2 + src/emit.cpp | 14 ++- src/implicits.cpp | 0 src/print.cpp | 4 + src/summoner.cpp | 241 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 363 insertions(+), 1 deletion(-) delete mode 100644 include/artic/implicits.h create mode 100644 include/artic/summoner.h delete mode 100644 src/implicits.cpp create mode 100644 src/summoner.cpp diff --git a/include/artic/ast.h b/include/artic/ast.h index 6df23795..fc52831e 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -23,6 +23,7 @@ struct Printer; class NameBinder; class TypeChecker; class Emitter; +class Summoner; template using Ptr = std::unique_ptr; template using PtrVector = std::vector>; @@ -71,6 +72,8 @@ struct Node : public Cast { virtual const artic::Type* infer(TypeChecker&); /// Checks that the node types and has the given type. virtual const artic::Type* check(TypeChecker&, const artic::Type*); + /// Resolves any SummonExpr within itself + virtual void resolve_summons(Summoner&) = 0; /// Emits an IR definition for this node. virtual const thorin::Def* emit(Emitter&) const; /// Prints the node with the given formatting parameters. @@ -100,6 +103,7 @@ struct Type : public Node { Type(const Loc& loc) : Node(loc) {} bool is_tuple() const; + void resolve_summons(Summoner&) override {}; }; /// Base class for statements. @@ -197,6 +201,7 @@ struct Path : public Node { const thorin::Def* emit(Emitter&) const override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -213,6 +218,7 @@ struct Filter : public Node { const thorin::Def* emit(Emitter&) const override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -231,6 +237,7 @@ struct Attr : public Node { /// Finds the sub-attribute with the given name in this attribute. virtual const Attr* find(const std::string_view&) const; + void resolve_summons(Summoner&) override {}; void bind(NameBinder&) override; }; @@ -435,6 +442,7 @@ struct DeclStmt : public Stmt { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -454,6 +462,7 @@ struct ExprStmt : public Stmt { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -477,6 +486,7 @@ struct TypedExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -494,6 +504,7 @@ struct PathExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -511,6 +522,7 @@ struct LiteralExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -518,13 +530,17 @@ struct LiteralExpr : public Expr { struct SummonExpr : public Expr { Ptr type; + const Expr* resolved = nullptr; + SummonExpr(const Loc& loc, Ptr&& type) : Expr(loc), type(std::move(type)) {} + const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -553,6 +569,7 @@ struct FieldExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -593,6 +610,7 @@ struct RecordExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -612,6 +630,7 @@ struct TupleExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -632,6 +651,7 @@ struct ArrayExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -653,6 +673,7 @@ struct RepeatArrayExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -683,6 +704,7 @@ struct FnExpr : public Expr { const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&, bool); void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -702,6 +724,7 @@ struct BlockExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -724,6 +747,7 @@ struct CallExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -756,6 +780,7 @@ struct ProjExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -800,6 +825,7 @@ struct IfExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -818,6 +844,7 @@ struct CaseExpr : public Expr { bool has_side_effect() const override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -839,6 +866,7 @@ struct MatchExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -876,6 +904,7 @@ struct WhileExpr : public LoopExpr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -893,6 +922,7 @@ struct ForExpr : public LoopExpr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -907,6 +937,7 @@ struct BreakExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -921,6 +952,7 @@ struct ContinueExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -934,6 +966,7 @@ struct ReturnExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; + void resolve_summons(Summoner&) override {}; void bind(NameBinder&) override; void print(Printer&) const override; }; @@ -973,6 +1006,7 @@ struct UnaryExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; bool is_inc() const { return is_inc(tag); } @@ -1025,6 +1059,7 @@ struct BinaryExpr : public Expr { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; static Tag remove_eq(Tag); @@ -1058,6 +1093,7 @@ struct FilterExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1080,6 +1116,7 @@ struct CastExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1102,6 +1139,7 @@ struct ImplicitCastExpr : public Expr { const thorin::Def* emit(Emitter&) const override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1141,6 +1179,7 @@ struct AsmExpr : public Expr { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1151,6 +1190,7 @@ struct ErrorExpr : public Expr { {} void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1187,6 +1227,7 @@ struct TypeParam : public NamedDecl { const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1199,6 +1240,7 @@ struct TypeParamList : public Node { {} void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1215,6 +1257,7 @@ struct PtrnDecl : public ValueDecl { const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1232,6 +1275,7 @@ struct LetDecl : public Decl { const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1253,6 +1297,7 @@ struct ImplicitDecl : public Decl { const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1278,6 +1323,7 @@ struct StaticDecl : public ValueDecl { const artic::Type* infer(TypeChecker&) override; void bind_head(NameBinder&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1301,6 +1347,7 @@ struct FnDecl : public ValueDecl { const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind_head(NameBinder&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1321,6 +1368,7 @@ struct FieldDecl : public NamedDecl { const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1335,6 +1383,8 @@ struct RecordDecl : public CtorDecl { : CtorDecl(loc, std::move(id)) , fields(std::move(fields)) {} + + void resolve_summons(Summoner&) override; }; /// Structure type declarations. @@ -1387,6 +1437,7 @@ struct OptionDecl : public RecordDecl { const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1409,6 +1460,7 @@ struct EnumDecl : public CtorDecl { const artic::Type* infer(TypeChecker&) override; void bind_head(NameBinder&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1431,6 +1483,7 @@ struct TypeDecl : public NamedDecl { const artic::Type* infer(TypeChecker&) override; void bind_head(NameBinder&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1462,6 +1515,7 @@ struct ModDecl : public NamedDecl { void bind_head(NameBinder&) override; void bind(NameBinder&) override; void print(Printer&) const override; + void resolve_summons(Summoner&) override; }; /// Module use, with or without `as`. @@ -1476,6 +1530,7 @@ struct UseDecl : public NamedDecl { const artic::Type* infer(TypeChecker&) override; void bind_head(NameBinder&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1484,6 +1539,7 @@ struct ErrorDecl : public Decl { ErrorDecl(const Loc& loc) : Decl(loc) {} void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1504,6 +1560,7 @@ struct TypedPtrn : public Ptrn { void emit(Emitter&, const thorin::Def*) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1523,6 +1580,7 @@ struct IdPtrn : public Ptrn { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1539,6 +1597,7 @@ struct LiteralPtrn : public Ptrn { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1561,6 +1620,7 @@ struct FieldPtrn : public Ptrn { void emit(Emitter&, const thorin::Def*) const override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1584,6 +1644,7 @@ struct RecordPtrn : public Ptrn { void emit(Emitter&, const thorin::Def*) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1605,6 +1666,7 @@ struct CtorPtrn : public Ptrn { void emit(Emitter&, const thorin::Def*) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1623,6 +1685,7 @@ struct TuplePtrn : public Ptrn { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1642,6 +1705,7 @@ struct ArrayPtrn : public Ptrn { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1652,6 +1716,7 @@ struct ErrorPtrn : public Ptrn { bool is_trivial() const override; void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; diff --git a/include/artic/implicits.h b/include/artic/implicits.h deleted file mode 100644 index e69de29b..00000000 diff --git a/include/artic/summoner.h b/include/artic/summoner.h new file mode 100644 index 00000000..ad37dc6a --- /dev/null +++ b/include/artic/summoner.h @@ -0,0 +1,38 @@ +#ifndef ARTIC_SUMMONER_H +#define ARTIC_SUMMONER_H + +#include + +#include "artic/ast.h" +#include "artic/types.h" +#include "artic/log.h" + +namespace artic { + +class Summoner : public Logger { +public: + Summoner(Log& log) + : Logger(log) + {} + + /// Eliminates all SummonExpr from the program + /// Returns true on success, otherwise false. + bool run(ast::ModDecl&); +private: + void push_scope(); + void pop_scope(); + + void insert(const artic::Type*, const ast::Expr*); + const ast::Expr* resolve(const artic::Type*); + + bool error = false; + std::vector> scopes; + + friend ast::SummonExpr; + friend ast::ImplicitDecl; + friend ast::ModDecl; +}; + +} // namespace artic + +#endif // ARTIC_SUMMONER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84c7c4f0..9afc1cec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(libartic ../include/artic/log.h ../include/artic/parser.h ../include/artic/print.h + ../include/artic/summoner.h ../include/artic/symbol.h ../include/artic/token.h ../include/artic/types.h @@ -21,6 +22,7 @@ add_library(libartic log.cpp parser.cpp print.cpp + summoner.cpp types.cpp) set_target_properties(libartic PROPERTIES PREFIX "" CXX_STANDARD 17) diff --git a/src/emit.cpp b/src/emit.cpp index e283a328..badcbc2f 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -6,6 +6,7 @@ #include "artic/parser.h" #include "artic/bind.h" #include "artic/check.h" +#include "artic/summoner.h" #include #include @@ -1198,6 +1199,12 @@ const thorin::Def* LiteralExpr::emit(Emitter& emitter) const { return emitter.emit(*this, lit); } +const thorin::Def* SummonExpr::emit(Emitter& emitter) const { + if (resolved) return resolved->emit(emitter); + emitter.error("Emitted an unresolved SummonExpr, {} !", *this); + return emitter.world.bottom(Node::type->convert(emitter)); +} + const thorin::Def* ArrayExpr::emit(Emitter& emitter) const { thorin::Array ops(elems.size()); for (size_t i = 0, n = elems.size(); i < n; ++i) @@ -1735,6 +1742,9 @@ const thorin::Def* ModDecl::emit(Emitter& emitter) const { // the call site, where the type arguments are known. if (auto fn_decl = decl->isa(); fn_decl && fn_decl->type_params) continue; + // Likewise, we do not emit implicit declarations + if (auto implicit = decl->isa()) + continue; emitter.emit(*decl); } return nullptr; @@ -2052,7 +2062,9 @@ bool compile( TypeChecker type_checker(log, type_table); type_checker.warns_as_errors = warns_as_errors; - if (!name_binder.run(program) || !type_checker.run(program)) + Summoner summoner(log); + + if (!name_binder.run(program) || !type_checker.run(program) || !summoner.run(program)) return false; Emitter emitter(log, world); diff --git a/src/implicits.cpp b/src/implicits.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/print.cpp b/src/print.cpp index a841d56d..8693d763 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -110,6 +110,10 @@ void LiteralExpr::print(Printer& p) const { } void SummonExpr::print(Printer& p) const { + if (resolved) { + resolved->print(p); + return; + } p << log::keyword_style("summon") << "["; if (type) type->print(p); p << "]"; diff --git a/src/summoner.cpp b/src/summoner.cpp new file mode 100644 index 00000000..e63cd90c --- /dev/null +++ b/src/summoner.cpp @@ -0,0 +1,241 @@ +#include "artic/summoner.h" + +namespace artic { + +bool Summoner::run(ast::ModDecl& mod) { + push_scope(); + mod.resolve_summons(*this); + pop_scope(); + return !error; +} + +void Summoner::push_scope() { + scopes.emplace(scopes.begin()); +} + +void Summoner::pop_scope() { + assert(!scopes.empty()); + scopes.erase(scopes.begin()); +} + +void Summoner::insert(const artic::Type* t, const ast::Expr* e) { + assert(!scopes.empty()); + auto& scope = scopes[0]; + auto existing = scope.find(t); + if (existing != scope.end()) { + log::error("Ambiguity: two different implicit declarations for {} given in scope", *t); + return; + } + scope.emplace(t, e); +} + +const ast::Expr* Summoner::resolve(const artic::Type* t) { + for (auto& scope : scopes) { + auto found = scope.find(t); + if (found != scope.end()) + return found->second; + // TODO: use subtyping relations and generators + } + error = true; + log::error("Could not summon a {}", *t); + return nullptr; +} + +namespace ast { + +void Filter::resolve_summons(artic::Summoner& summoner) { + if (expr) expr->resolve_summons(summoner); +} + +void DeclStmt::resolve_summons(artic::Summoner& summoner) { + decl->resolve_summons(summoner); +} + +void ExprStmt::resolve_summons(artic::Summoner& summoner) { + expr->resolve_summons(summoner); +} + +void TypedExpr::resolve_summons(artic::Summoner& summoner) { + expr->resolve_summons(summoner); +} + +void SummonExpr::resolve_summons(artic::Summoner& summoner) { + resolved = summoner.resolve(Node::type); +} + +void FieldExpr::resolve_summons(artic::Summoner& summoner) { + expr->resolve_summons(summoner); +} + +void RecordExpr::resolve_summons(artic::Summoner& summoner) { + if (expr) expr->resolve_summons(summoner); + for (auto& field: fields) + field->resolve_summons(summoner); +} + +void TupleExpr::resolve_summons(artic::Summoner& summoner) { + for (auto& arg: args) + arg->resolve_summons(summoner); +} + +void ArrayExpr::resolve_summons(artic::Summoner& summoner) { + for (auto& elem: elems) + elem->resolve_summons(summoner); +} + +void RepeatArrayExpr::resolve_summons(artic::Summoner& summoner) { + elem->resolve_summons(summoner); +} + +void FnExpr::resolve_summons(artic::Summoner& summoner) { + param->resolve_summons(summoner); + if(body) body->resolve_summons(summoner); +} + +void BlockExpr::resolve_summons(artic::Summoner& summoner) { + for (auto& stmt: stmts) + stmt->resolve_summons(summoner); +} + +void CallExpr::resolve_summons(artic::Summoner& summoner) { + callee->resolve_summons(summoner); + arg->resolve_summons(summoner); +} + +void ProjExpr::resolve_summons(artic::Summoner& summoner) { + expr->resolve_summons(summoner); +} + +void IfExpr::resolve_summons(artic::Summoner& summoner) { + if (ptrn) { + ptrn->resolve_summons(summoner); + expr->resolve_summons(summoner); + } else cond->resolve_summons(summoner); + if_true->resolve_summons(summoner); + if (if_false) if_false->resolve_summons(summoner); +} + +void CaseExpr::resolve_summons(artic::Summoner& summoner) { + ptrn->resolve_summons(summoner); + expr->resolve_summons(summoner); +} + +void MatchExpr::resolve_summons(artic::Summoner& summoner) { + arg->resolve_summons(summoner); + for (auto& cas : cases) + cas->resolve_summons(summoner); +} + +void WhileExpr::resolve_summons(artic::Summoner& summoner) { + if (ptrn) { + ptrn->resolve_summons(summoner); + expr->resolve_summons(summoner); + } else cond->resolve_summons(summoner); + body->resolve_summons(summoner); +} + +void ForExpr::resolve_summons(artic::Summoner& summoner) { + call->resolve_summons(summoner); +} + +void UnaryExpr::resolve_summons(artic::Summoner& summoner) { + arg->resolve_summons(summoner); +} + +void BinaryExpr::resolve_summons(artic::Summoner& summoner) { + left->resolve_summons(summoner); + right->resolve_summons(summoner); +} + +void FilterExpr::resolve_summons(artic::Summoner& summoner) { + filter->resolve_summons(summoner); + expr->resolve_summons(summoner); +} + +void CastExpr::resolve_summons(artic::Summoner& summoner) { + expr->resolve_summons(summoner); +} + +void ImplicitCastExpr::resolve_summons(artic::Summoner& summoner) { + expr->resolve_summons(summoner); +} + +void AsmExpr::resolve_summons(artic::Summoner& summoner) { + for (auto& constr : ins) + if (constr.expr) constr.expr->resolve_summons(summoner); + for (auto& constr : outs) + if (constr.expr) constr.expr->resolve_summons(summoner); +} + +void LetDecl::resolve_summons(artic::Summoner& summoner) { + ptrn->resolve_summons(summoner); + if (init) init->resolve_summons(summoner); +} + +void ImplicitDecl::resolve_summons(artic::Summoner& summoner) { + if (!is_top_level) + summoner.insert(Node::type, value.get()); + value->resolve_summons(summoner); +} + +void StaticDecl::resolve_summons(artic::Summoner& summoner) { + if (init) init->resolve_summons(summoner); +} + +void FnDecl::resolve_summons(artic::Summoner& summoner) { + fn->resolve_summons(summoner); +} + +void FieldDecl::resolve_summons(artic::Summoner& summoner) { + if (init) init->resolve_summons(summoner); +} + +void RecordDecl::resolve_summons(artic::Summoner& summoner) { + for (auto& field : fields) + field->resolve_summons(summoner); +} + +void ModDecl::resolve_summons(artic::Summoner& summoner) { + for (auto& decl: decls) + if (auto impl_decl = decl->isa()) + summoner.insert(decl->type, impl_decl->value.get()); + + for (auto& decl: decls) + decl->resolve_summons(summoner); +} + +void TypedPtrn::resolve_summons(artic::Summoner& summoner) { + if (ptrn) ptrn->resolve_summons(summoner); +} + +void IdPtrn::resolve_summons(artic::Summoner& summoner) { + decl->resolve_summons(summoner); + if (sub_ptrn) sub_ptrn->resolve_summons(summoner); +} + +void FieldPtrn::resolve_summons(artic::Summoner& summoner) { + if (ptrn) ptrn->resolve_summons(summoner); +} + +void RecordPtrn::resolve_summons(artic::Summoner& summoner) { + for (auto& field : fields) + field->resolve_summons(summoner); +} + +void CtorPtrn::resolve_summons(artic::Summoner& summoner) { + if (arg) arg->resolve_summons(summoner); +} + +void TuplePtrn::resolve_summons(artic::Summoner& summoner) { + for (auto& arg : args) + arg->resolve_summons(summoner); +} + +void ArrayPtrn::resolve_summons(artic::Summoner& summoner) { + for (auto& elem : elems) + elem->resolve_summons(summoner); +} + +} + +} // namespace artic From 4240d285481300233a1d093248bcc462f5321ba3 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 25 Nov 2022 14:53:31 +0100 Subject: [PATCH 04/22] handle implicit in block scopes --- include/artic/ast.h | 1 + include/artic/summoner.h | 1 + src/emit.cpp | 4 ++++ src/parser.cpp | 3 ++- src/summoner.cpp | 2 ++ 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index fc52831e..e50ca714 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -1295,6 +1295,7 @@ struct ImplicitDecl : public Decl { , is_generator(is_generator) {} + const thorin::Def* emit(Emitter&) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; void resolve_summons(Summoner&) override; diff --git a/include/artic/summoner.h b/include/artic/summoner.h index ad37dc6a..6e67c6a9 100644 --- a/include/artic/summoner.h +++ b/include/artic/summoner.h @@ -31,6 +31,7 @@ class Summoner : public Logger { friend ast::SummonExpr; friend ast::ImplicitDecl; friend ast::ModDecl; + friend ast::BlockExpr; }; } // namespace artic diff --git a/src/emit.cpp b/src/emit.cpp index badcbc2f..2b5d8390 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1645,6 +1645,10 @@ const thorin::Def* LetDecl::emit(Emitter& emitter) const { return nullptr; } +const thorin::Def* ImplicitDecl::emit(artic::Emitter&) const { + return nullptr; +} + const thorin::Def* StaticDecl::emit(Emitter& emitter) const { auto value = init ? emitter.emit(*init) diff --git a/src/parser.cpp b/src/parser.cpp index 5b49d4ab..f5be9fc1 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -437,7 +437,7 @@ Ptr Parser::parse_error_ptrn() { // Statements ---------------------------------------------------------------------- Ptr Parser::parse_stmt() { - if (ahead().tag() == Token::Let || ahead().tag() == Token::Fn) + if (ahead().tag() == Token::Let || ahead().tag() == Token::Fn || ahead().tag() == Token::Implicit) return parse_decl_stmt(); Tracker tracker(this); Ptr expr; @@ -607,6 +607,7 @@ Ptr Parser::parse_block_expr() { case Token::Asm: case Token::Simd: case Token::Let: + case Token::Implicit: case Token::Fn: if (!last_semi && !stmts.empty() && stmts.back()->needs_semicolon()) error(ahead().loc(), "expected ';', but got '{}'", ahead().string()); diff --git a/src/summoner.cpp b/src/summoner.cpp index e63cd90c..cd80edb0 100644 --- a/src/summoner.cpp +++ b/src/summoner.cpp @@ -93,8 +93,10 @@ void FnExpr::resolve_summons(artic::Summoner& summoner) { } void BlockExpr::resolve_summons(artic::Summoner& summoner) { + summoner.push_scope(); for (auto& stmt: stmts) stmt->resolve_summons(summoner); + summoner.pop_scope(); } void CallExpr::resolve_summons(artic::Summoner& summoner) { From 01d5ba09c75baaa94fdcc7f8145ac3e1e5842d9d Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 25 Nov 2022 17:03:40 +0100 Subject: [PATCH 05/22] added ImplicitParamType node --- include/artic/types.h | 67 ++++++++++++++++++++++++++++++------------- src/emit.cpp | 8 ++++++ src/print.cpp | 5 ++++ src/types.cpp | 40 ++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/include/artic/types.h b/include/artic/types.h index 5b37eaee..f2e7cd8b 100644 --- a/include/artic/types.h +++ b/include/artic/types.h @@ -275,6 +275,32 @@ struct RefType : public AddrType { friend class TypeTable; }; +struct ImplicitParamType : public Type { + const Type* underlying; + + void print(Printer&) const override; + bool equals(const Type*) const override; + size_t hash() const override; + bool contains(const Type*) const override; + + const Type* replace(const ReplaceMap&) const override; + + const thorin::Type* convert(Emitter&) const override; + std::string stringify(Emitter&) const override; + + size_t order(std::unordered_set&) const override; + void variance(TypeVarMap&, bool) const override; + void bounds(TypeVarMap&, const Type*, bool) const override; + bool is_sized(std::unordered_set&) const override; +private: + ImplicitParamType(TypeTable& type_table, const Type* underlying) + : Type(type_table) + , underlying(underlying) + {} + + friend class TypeTable; +}; + /// Function type (can represent continuations when the codomain is a `NoRetType`). struct FnType : public Type { const Type* dom; @@ -639,26 +665,27 @@ class TypeTable { public: ~TypeTable(); - const PrimType* prim_type(ast::PrimType::Tag); - const PrimType* bool_type(); - const TupleType* unit_type(); - const TupleType* tuple_type(const ArrayRef&); - const SizedArrayType* sized_array_type(const Type*, size_t, bool); - const UnsizedArrayType* unsized_array_type(const Type*); - const PtrType* ptr_type(const Type*, bool, size_t); - const RefType* ref_type(const Type*, bool, size_t); - const FnType* fn_type(const Type*, const Type*); - const FnType* cn_type(const Type*); - const BottomType* bottom_type(); - const TopType* top_type(); - const NoRetType* no_ret_type(); - const TypeError* type_error(); - const TypeVar* type_var(const ast::TypeParam&); - const ForallType* forall_type(const ast::FnDecl&); - const StructType* struct_type(const ast::RecordDecl&); - const EnumType* enum_type(const ast::EnumDecl&); - const ModType* mod_type(const ast::ModDecl&); - const TypeAlias* type_alias(const ast::TypeDecl&); + const PrimType* prim_type(ast::PrimType::Tag); + const PrimType* bool_type(); + const TupleType* unit_type(); + const TupleType* tuple_type(const ArrayRef&); + const SizedArrayType* sized_array_type(const Type*, size_t, bool); + const UnsizedArrayType* unsized_array_type(const Type*); + const PtrType* ptr_type(const Type*, bool, size_t); + const RefType* ref_type(const Type*, bool, size_t); + const ImplicitParamType* implicit_param_type(const Type*); + const FnType* fn_type(const Type*, const Type*); + const FnType* cn_type(const Type*); + const BottomType* bottom_type(); + const TopType* top_type(); + const NoRetType* no_ret_type(); + const TypeError* type_error(); + const TypeVar* type_var(const ast::TypeParam&); + const ForallType* forall_type(const ast::FnDecl&); + const StructType* struct_type(const ast::RecordDecl&); + const EnumType* enum_type(const ast::EnumDecl&); + const ModType* mod_type(const ast::ModDecl&); + const TypeAlias* type_alias(const ast::TypeDecl&); /// Creates a type application for structures/enumeration types, /// or returns the type alias expanded with the given type arguments. diff --git a/src/emit.cpp b/src/emit.cpp index 2b5d8390..3467ba08 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1886,10 +1886,18 @@ const thorin::Type* PtrType::convert(Emitter& emitter) const { return emitter.world.ptr_type(pointee->convert(emitter), 1, -1, thorin::AddrSpace(addr_space)); } +std::string ImplicitParamType::stringify(Emitter& emitter) const { + return "implicit_" + underlying->stringify(emitter); +} + std::string FnType::stringify(Emitter& emitter) const { return "fn_" + dom->stringify(emitter) + "_" + codom->stringify(emitter); } +const thorin::Type* ImplicitParamType::convert(artic::Emitter& emitter) const { + return underlying->convert(emitter); +} + const thorin::Type* FnType::convert(Emitter& emitter) const { if (codom->isa()) return emitter.continuation_type_with_mem(dom->convert(emitter)); diff --git a/src/print.cpp b/src/print.cpp index 8693d763..4658958a 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -743,6 +743,11 @@ void RefType::print(Printer& p) const { pointee->print(p); } +void ImplicitParamType::print(artic::Printer& p) const { + p << "implicit "; + underlying->print(p); +} + void FnType::print(Printer& p) const { p << log::keyword_style("fn") << ' '; if (!dom->isa()) p << '('; diff --git a/src/types.cpp b/src/types.cpp index 7d2e2c58..e9cd87fe 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -52,6 +52,12 @@ bool AddrType::equals(const Type* other) const { other->as()->is_mut == is_mut; } +bool ImplicitParamType::equals(const artic::Type* other) const { + return + other->isa() && + other->as()->underlying == underlying; +} + bool FnType::equals(const Type* other) const { return other->isa() && @@ -108,6 +114,12 @@ size_t AddrType::hash() const { .combine(is_mut); } +size_t ImplicitParamType::hash() const { + return fnv::Hash() + .combine(typeid(*this).hash_code()) + .combine(underlying); +} + size_t FnType::hash() const { return fnv::Hash() .combine(typeid(*this).hash_code()) @@ -148,6 +160,10 @@ bool AddrType::contains(const Type* type) const { return type == this || pointee->contains(type); } +bool ImplicitParamType::contains(const artic::Type* type) const { + return type == this || underlying->contains(type); +} + bool FnType::contains(const Type* type) const { return type == this || dom->contains(type) || codom->contains(type); } @@ -186,6 +202,10 @@ const Type* RefType::replace(const std::unordered_mapreplace(map), is_mut, addr_space); } +const Type* ImplicitParamType::replace(const artic::ReplaceMap& map) const { + return type_table.implicit_param_type(underlying->replace(map)); +} + const Type* FnType::replace(const std::unordered_map& map) const { return type_table.fn_type(dom->replace(map), codom->replace(map)); } @@ -209,6 +229,10 @@ size_t Type::order(std::unordered_set&) const { return 0; } +size_t ImplicitParamType::order(std::unordered_set& seen) const { + return underlying->order(seen); +} + size_t FnType::order(std::unordered_set& seen) const { return 1 + std::max(dom->order(seen), codom->order(seen)); } @@ -266,6 +290,10 @@ void FnType::variance(std::unordered_map& vars, bo codom->variance(vars, dir); } +void ImplicitParamType::variance(TypeVarMap& vars, bool dir) const { + return underlying->variance(vars, dir); +} + void TypeVar::variance(std::unordered_map& vars, bool dir) const { if (auto it = vars.find(this); it != vars.end()) { bool var_dir = it->second == TypeVariance::Covariant ? true : false; @@ -301,6 +329,10 @@ void AddrType::bounds(std::unordered_map& bounds, co pointee->bounds(bounds, addr_type->pointee, dir); } +void ImplicitParamType::bounds(TypeVarMap& bounds, const artic::Type* type, bool dir) const { + underlying->bounds(bounds, type, dir); +} + void FnType::bounds(std::unordered_map& bounds, const Type* type, bool dir) const { if (auto fn_type = type->isa()) { dom->bounds(bounds, fn_type->dom, !dir); @@ -334,6 +366,10 @@ bool Type::is_sized(std::unordered_set&) const { return true; } +bool ImplicitParamType::is_sized(std::unordered_set& seen) const { + return underlying->is_sized(seen); +} + bool FnType::is_sized(std::unordered_set& seen) const { return dom->is_sized(seen) && codom->is_sized(seen); } @@ -629,6 +665,10 @@ const RefType* TypeTable::ref_type(const Type* pointee, bool is_mut, size_t addr return insert(pointee, is_mut, addr_space); } +const ImplicitParamType* TypeTable::implicit_param_type(const Type* underlying) { + return insert(underlying); +} + const FnType* TypeTable::fn_type(const Type* dom, const Type* codom) { return insert(dom, codom); } From 58d66056437a250d3905082dc62eabdb06229c31 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 11:51:44 +0100 Subject: [PATCH 06/22] added syntax for implicit parameters --- include/artic/ast.h | 17 +++++++++++++++++ include/artic/parser.h | 4 ++-- src/ast.cpp | 4 ++++ src/bind.cpp | 4 ++++ src/check.cpp | 8 ++++++++ src/emit.cpp | 4 ++++ src/parser.cpp | 34 +++++++++++++++++++++------------- src/print.cpp | 5 +++++ 8 files changed, 65 insertions(+), 15 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index e50ca714..a123015b 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -1602,6 +1602,23 @@ struct LiteralPtrn : public Ptrn { void print(Printer&) const override; }; +struct ImplicitParamPtrn : public Ptrn { + Ptr underlying; + + ImplicitParamPtrn(const Loc& loc, Ptr&& underlying) + : Ptrn(loc), underlying(std::move(underlying)) + {} + + bool is_trivial() const override; + + void emit(Emitter&, const thorin::Def*) const override; + const artic::Type* infer(TypeChecker&) override; + const artic::Type* check(TypeChecker&, const artic::Type*) override; + void bind(NameBinder&) override; + void resolve_summons(Summoner&) override {}; + void print(Printer&) const override; +}; + /// A pattern that matches against a structure field. struct FieldPtrn : public Ptrn { Identifier id; diff --git a/include/artic/parser.h b/include/artic/parser.h index 08f8323a..15b248f2 100644 --- a/include/artic/parser.h +++ b/include/artic/parser.h @@ -40,14 +40,14 @@ class Parser : public Logger { Ptr parse_use_decl(); Ptr parse_error_decl(); - Ptr parse_ptrn(bool = false); + Ptr parse_ptrn(bool = false, bool = false); Ptr parse_typed_ptrn(Ptr&&); Ptr parse_id_ptrn(ast::Identifier&&, bool); Ptr parse_literal_ptrn(); Ptr parse_field_ptrn(); Ptr parse_record_ptrn(ast::Path &&path); Ptr parse_ctor_ptrn(ast::Path&& path); - Ptr parse_tuple_ptrn(bool = false, Token::Tag = Token::LParen, Token::Tag = Token::RParen); + Ptr parse_tuple_ptrn(bool = false, bool = false, Token::Tag = Token::LParen, Token::Tag = Token::RParen); Ptr parse_array_ptrn(); Ptr parse_error_ptrn(); diff --git a/src/ast.cpp b/src/ast.cpp index 7d88c0d6..28b8a9be 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -633,6 +633,10 @@ bool LiteralPtrn::is_trivial() const { return false; } +bool ImplicitParamPtrn::is_trivial() const { + return underlying->is_trivial(); +} + void FieldPtrn::collect_bound_ptrns(std::vector& bound_ptrns) const { if (ptrn) ptrn->collect_bound_ptrns(bound_ptrns); diff --git a/src/bind.cpp b/src/bind.cpp index 176b0f6b..63f5efac 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -338,6 +338,10 @@ void IdPtrn::bind(NameBinder& binder) { void LiteralPtrn::bind(NameBinder&) {} +void ImplicitParamPtrn::bind(artic::NameBinder& binder) { + underlying->bind(binder); +} + void FieldPtrn::bind(NameBinder& binder) { if (ptrn) binder.bind(*ptrn); } diff --git a/src/check.cpp b/src/check.cpp index 9bd13746..eebe7666 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -1696,6 +1696,14 @@ const artic::Type* IdPtrn::check(TypeChecker& checker, const artic::Type* expect return expected; } +const artic::Type * ImplicitParamPtrn::infer(artic::TypeChecker& checker) { + return underlying->infer(checker); +} + +const artic::Type * ImplicitParamPtrn::check(artic::TypeChecker& checker, const artic::Type* expected) { + return underlying->check(checker, expected); +} + const artic::Type* FieldPtrn::check(TypeChecker& checker, const artic::Type* expected) { return checker.check(*ptrn, expected); } diff --git a/src/emit.cpp b/src/emit.cpp index 3467ba08..f10a390e 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1776,6 +1776,10 @@ void IdPtrn::emit(Emitter& emitter, const thorin::Def* value) const { emitter.emit(*sub_ptrn, value); } +void ImplicitParamPtrn::emit(artic::Emitter& emitter, const thorin::Def* value) const { + underlying->emit(emitter, value); +} + void FieldPtrn::emit(Emitter& emitter, const thorin::Def* value) const { emitter.emit(*ptrn, value); } diff --git a/src/parser.cpp b/src/parser.cpp index f5be9fc1..21597c79 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -80,7 +80,7 @@ Ptr Parser::parse_fn_decl() { Ptr param; if (ahead().tag() == Token::LParen) - param = parse_tuple_ptrn(true); + param = parse_tuple_ptrn(true, true); else error(ahead().loc(), "parameter list expected in function definition"); @@ -281,14 +281,14 @@ Ptr Parser::parse_error_decl() { // Patterns ------------------------------------------------------------------------ -Ptr Parser::parse_ptrn(bool is_fn_param) { +Ptr Parser::parse_ptrn(bool allow_types, bool allow_implicits) { Ptr ptrn; switch (ahead().tag()) { case Token::Super: case Token::Id: { if (auto tag = ast::PrimType::tag_from_token(ahead()); tag != ast::PrimType::Error) { - if (!is_fn_param) + if (!allow_types) return parse_error_ptrn(); auto type = parse_prim_type(tag); return make_ptr(type->loc, Ptr(), std::move(type)); @@ -298,11 +298,11 @@ Ptr Parser::parse_ptrn(bool is_fn_param) { ahead().tag() == Token::LBracket || ahead().tag() == Token::LParen || ahead().tag() == Token::LBrace || - (is_fn_param && ahead().tag() != Token::Colon && ahead().tag() != Token::As)) { + (allow_types && ahead().tag() != Token::Colon && ahead().tag() != Token::As)) { auto path = parse_path(std::move(id), true); if (ahead().tag() == Token::LBrace) ptrn = parse_record_ptrn(std::move(path)); - else if (is_fn_param) { + else if (allow_types) { auto type = make_ptr(path.loc, std::move(path)); return make_ptr(path.loc, Ptr(), std::move(type)); } else @@ -317,22 +317,30 @@ Ptr Parser::parse_ptrn(bool is_fn_param) { ptrn = parse_id_ptrn(parse_id(), true); } break; - case Token::LParen: ptrn = parse_tuple_ptrn(is_fn_param); break; - case Token::Lit: ptrn = parse_literal_ptrn(); break; + case Token::LParen: ptrn = parse_tuple_ptrn(allow_types, false); break; + case Token::Lit: ptrn = parse_literal_ptrn(); break; case Token::Simd: case Token::LBracket: - if (!is_fn_param || (ahead(1).tag() == Token::Id && ahead(2).tag() == Token::Colon)) { + if (!allow_types || (ahead(1).tag() == Token::Id && ahead(2).tag() == Token::Colon)) { ptrn = parse_array_ptrn(); break; } [[fallthrough]]; case Token::And: case Token::Fn: - if (is_fn_param) { + if (allow_types) { auto type = parse_type(); return make_ptr(type->loc, Ptr(), std::move(type)); } [[fallthrough]]; + case Token::Implicit: + { + if (!allow_implicits) + return parse_error_ptrn(); + eat(Token::Implicit); + auto underlying = parse_ptrn(); + return make_ptr(underlying->loc, std::move(underlying)); + } default: ptrn = parse_error_ptrn(); break; @@ -402,12 +410,12 @@ Ptr Parser::parse_ctor_ptrn(ast::Path&& path) { return make_ptr(tracker(), std::move(path), std::move(arg)); } -Ptr Parser::parse_tuple_ptrn(bool is_fn_param, Token::Tag beg, Token::Tag end) { +Ptr Parser::parse_tuple_ptrn(bool allow_types, bool allow_implicits, Token::Tag beg, Token::Tag end) { Tracker tracker(this); eat(beg); PtrVector args; parse_list(end, Token::Comma, [&] { - args.emplace_back(parse_ptrn(is_fn_param)); + args.emplace_back(parse_ptrn(allow_types, allow_implicits)); }); if (args.size() == 1) { args[0]->loc = tracker(); @@ -636,7 +644,7 @@ Ptr Parser::parse_fn_expr(Ptr&& filter, bool nested) { parse_nested = parse_list( std::array{ Token::Or, Token::LogicOr }, std::array{ Token::Comma }, [&] { - args.emplace_back(parse_ptrn(false)); + args.emplace_back(parse_ptrn(false, true)); }) == 1; if (args.size() == 1) { ptrn = std::move(args.front()); @@ -761,7 +769,7 @@ Ptr Parser::parse_for_expr() { ahead(2).tag() == Token::Mut || ahead(2).tag() == Token::Comma || ahead(2).tag() == Token::Colon) - ptrn = parse_tuple_ptrn(false, Token::For, Token::In); + ptrn = parse_tuple_ptrn(false, false, Token::For, Token::In); else { eat(Token::For); ptrn = make_ptr(tracker(), PtrVector{}); diff --git a/src/print.cpp b/src/print.cpp index 4658958a..1cbb17c7 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -397,6 +397,11 @@ void LiteralPtrn::print(Printer& p) const { p << std::showpoint << log::literal_style(lit); } +void ImplicitParamPtrn::print(Printer& p) const { + p << log::keyword_style("implicit") << ' '; + underlying->print(p); +} + void FieldPtrn::print(Printer& p) const { if (is_etc()) { p << "..."; From 0ad5bbd20a929b28fcf7e1b357bf0557644bb61a Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 12:03:15 +0100 Subject: [PATCH 07/22] implicit params now type as implicit --- src/check.cpp | 8 +++++--- src/emit.cpp | 3 +++ src/types.cpp | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/check.cpp b/src/check.cpp index eebe7666..23792ab3 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -1696,12 +1696,14 @@ const artic::Type* IdPtrn::check(TypeChecker& checker, const artic::Type* expect return expected; } -const artic::Type * ImplicitParamPtrn::infer(artic::TypeChecker& checker) { - return underlying->infer(checker); +const artic::Type* ImplicitParamPtrn::infer(artic::TypeChecker& checker) { + checker.infer(*underlying); + return checker.type_table.implicit_param_type(underlying->type); } const artic::Type * ImplicitParamPtrn::check(artic::TypeChecker& checker, const artic::Type* expected) { - return underlying->check(checker, expected); + checker.check(*underlying, expected); + return checker.type_table.implicit_param_type(underlying->type); } const artic::Type* FieldPtrn::check(TypeChecker& checker, const artic::Type* expected) { diff --git a/src/emit.cpp b/src/emit.cpp index f10a390e..aad82ec6 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -702,6 +702,9 @@ const thorin::Def* Emitter::down_cast(const thorin::Def* def, const Type* from, if (from->isa()) return world.bottom(to->convert(*this)); + if (to->isa()) + return def; + auto to_ptr_type = to->isa(); // Casting a value to a pointer to the type of the value effectively creates an allocation if (to_ptr_type && diff --git a/src/types.cpp b/src/types.cpp index e9cd87fe..94a1c7a7 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -482,6 +482,9 @@ bool Type::subtype(const Type* other) const { if (this == other || isa() || other->isa()) return true; + if (auto implicit = other->isa()) + return this->subtype(implicit->underlying); + auto other_ptr_type = other->isa(); // Take the address of values automatically: From 1e3fd1c1269895d589b40cbe70e42277428fb210 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 12:53:32 +0100 Subject: [PATCH 08/22] made it so () can be coerced into a summon under implicit constraints --- include/artic/ast.h | 6 +++--- src/bind.cpp | 2 +- src/check.cpp | 15 +++++++++++++-- src/emit.cpp | 2 +- src/print.cpp | 1 + src/summoner.cpp | 2 +- src/types.cpp | 2 +- 7 files changed, 21 insertions(+), 9 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index a123015b..4b4d6851 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -528,12 +528,12 @@ struct LiteralExpr : public Expr { /// Expression summoning an implicit value. struct SummonExpr : public Expr { - Ptr type; + Ptr type_expr; const Expr* resolved = nullptr; - SummonExpr(const Loc& loc, Ptr&& type) - : Expr(loc), type(std::move(type)) + SummonExpr(const Loc& loc, Ptr&& type_expr) + : Expr(loc), type_expr(std::move(type_expr)) {} const thorin::Def* emit(Emitter&) const override; diff --git a/src/bind.cpp b/src/bind.cpp index 63f5efac..aa5747c6 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -152,7 +152,7 @@ void PathExpr::bind(NameBinder& binder) { void LiteralExpr::bind(NameBinder&) {} void SummonExpr::bind(artic::NameBinder& binder) { - if (type) binder.bind(*type); + if (type_expr) binder.bind(*type_expr); } void FieldExpr::bind(NameBinder& binder) { diff --git a/src/check.cpp b/src/check.cpp index 23792ab3..6cda9dd5 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -157,7 +157,12 @@ const Type* TypeChecker::deref(Ptr& expr) { const Type* TypeChecker::coerce(Ptr& expr, const Type* expected) { auto type = expr->type ? expr->type : check(*expr, expected); if (type != expected) { - if (type->subtype(expected)) { + if (auto implicit = expected->isa()) { + Ptr summoned = make_ptr(expr->loc, Ptr()); + summoned->type = implicit->underlying; + expr.swap(summoned); + return implicit->underlying; + } else if (type->subtype(expected)) { expr = make_ptr(expr->loc, std::move(expr), expected); return expected; } else @@ -850,7 +855,7 @@ const artic::Type* LiteralExpr::check(TypeChecker& checker, const artic::Type* e } const artic::Type* SummonExpr::infer(artic::TypeChecker& checker) { - if (type) return checker.infer(*type); + if (type_expr) return checker.infer(*type_expr); checker.error(loc, "summoning a value without a type"); return checker.type_table.type_error(); } @@ -895,6 +900,12 @@ const artic::Type* TupleExpr::check(TypeChecker& checker, const artic::Type* exp checker.coerce(args[i], tuple_type->args[i]); return expected; } + // Allow the empty tuple () to type as an implicit param + if (auto implicit = expected->isa()) { + auto inferred = infer(checker); + if (is_unit_type(inferred)) + return inferred; + } return checker.incompatible_type(loc, "tuple expression", expected); } diff --git a/src/emit.cpp b/src/emit.cpp index aad82ec6..06e9e59e 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1205,7 +1205,7 @@ const thorin::Def* LiteralExpr::emit(Emitter& emitter) const { const thorin::Def* SummonExpr::emit(Emitter& emitter) const { if (resolved) return resolved->emit(emitter); emitter.error("Emitted an unresolved SummonExpr, {} !", *this); - return emitter.world.bottom(Node::type->convert(emitter)); + return emitter.world.bottom(type->convert(emitter)); } const thorin::Def* ArrayExpr::emit(Emitter& emitter) const { diff --git a/src/print.cpp b/src/print.cpp index 1cbb17c7..acecc941 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -116,6 +116,7 @@ void SummonExpr::print(Printer& p) const { } p << log::keyword_style("summon") << "["; if (type) type->print(p); + else if (type_expr) type_expr->print(p); p << "]"; } diff --git a/src/summoner.cpp b/src/summoner.cpp index cd80edb0..3f6d4f8e 100644 --- a/src/summoner.cpp +++ b/src/summoner.cpp @@ -60,7 +60,7 @@ void TypedExpr::resolve_summons(artic::Summoner& summoner) { } void SummonExpr::resolve_summons(artic::Summoner& summoner) { - resolved = summoner.resolve(Node::type); + resolved = summoner.resolve(type); } void FieldExpr::resolve_summons(artic::Summoner& summoner) { diff --git a/src/types.cpp b/src/types.cpp index 94a1c7a7..fc481067 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -483,7 +483,7 @@ bool Type::subtype(const Type* other) const { return true; if (auto implicit = other->isa()) - return this->subtype(implicit->underlying); + return this->subtype(implicit->underlying) || is_unit_type(this); auto other_ptr_type = other->isa(); From a6bd2ca48df0d70703e709d0201b2d12af32c0a8 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 12:58:17 +0100 Subject: [PATCH 09/22] fix summons not parsing in statement positions --- src/parser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/parser.cpp b/src/parser.cpp index 21597c79..83bf6a88 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -616,6 +616,7 @@ Ptr Parser::parse_block_expr() { case Token::Simd: case Token::Let: case Token::Implicit: + case Token::Summon: case Token::Fn: if (!last_semi && !stmts.empty() && stmts.back()->needs_semicolon()) error(ahead().loc(), "expected ';', but got '{}'", ahead().string()); From c3fbfa2d730d30c97a2e01a6a55b37653d0d3740 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 13:01:12 +0100 Subject: [PATCH 10/22] added some basic tests for implicits --- test/CMakeLists.txt | 4 ++++ test/failure/implicit1.art | 1 + test/failure/implicit2.art | 3 +++ test/simple/implicit1.art | 3 +++ test/simple/implicit2.art | 4 ++++ 5 files changed, 15 insertions(+) create mode 100644 test/failure/implicit1.art create mode 100644 test/failure/implicit2.art create mode 100644 test/simple/implicit1.art create mode 100644 test/simple/implicit2.art diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 365bea46..bfa475d0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,6 +24,8 @@ add_test(NAME simple_match3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC add_test(NAME simple_match4 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/match4.art) add_test(NAME simple_if COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/if.art) add_test(NAME simple_if_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/if_let.art) +add_test(NAME simple_implicit1 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit1.art) +add_test(NAME simple_implicit2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit2.art) add_test(NAME simple_while COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while.art) add_test(NAME simple_while_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while_let.art) add_test(NAME simple_for COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/for.art) @@ -96,6 +98,8 @@ add_failure_test(NAME failure_arrays1 COMMAND artic ${CMAKE_CURRENT_SOURC add_failure_test(NAME failure_arrays2 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/arrays2.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) +add_failure_test(NAME failure_implicit2 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/implicit2.art) add_failure_test(NAME failure_while_let COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/while_let.art) add_failure_test(NAME failure_structs1 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/structs1.art) add_failure_test(NAME failure_structs2 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/structs2.art) diff --git a/test/failure/implicit1.art b/test/failure/implicit1.art new file mode 100644 index 00000000..dd915d90 --- /dev/null +++ b/test/failure/implicit1.art @@ -0,0 +1 @@ +let implicit i = 0; diff --git a/test/failure/implicit2.art b/test/failure/implicit2.art new file mode 100644 index 00000000..2f53b583 --- /dev/null +++ b/test/failure/implicit2.art @@ -0,0 +1,3 @@ +implicit i64 = 42; + +fn foo() -> i32 { summon[i32] } diff --git a/test/simple/implicit1.art b/test/simple/implicit1.art new file mode 100644 index 00000000..b00a990c --- /dev/null +++ b/test/simple/implicit1.art @@ -0,0 +1,3 @@ +implicit i32 = 42; + +fn foo() -> i32 { summon[i32] } diff --git a/test/simple/implicit2.art b/test/simple/implicit2.art new file mode 100644 index 00000000..21829008 --- /dev/null +++ b/test/simple/implicit2.art @@ -0,0 +1,4 @@ +implicit i32 = 42; + +fn foo(implicit i: i32) {} +fn bar() { foo() } From a0fbf90272217e728490692050b1ebab20edc1a5 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 13:29:49 +0100 Subject: [PATCH 11/22] added support for summoning multiple implicits --- src/check.cpp | 45 +++++++++++++++++++++++++++++---------- test/CMakeLists.txt | 1 + test/simple/implicit3.art | 5 +++++ 3 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 test/simple/implicit3.art diff --git a/src/check.cpp b/src/check.cpp index 6cda9dd5..cbf8cb45 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -154,14 +154,39 @@ const Type* TypeChecker::deref(Ptr& expr) { return type; } +static bool contains_implicit(const artic::Type* type) { + if (type->isa()) + return true; + else if(auto tuple_t = type->isa(); tuple_t && !is_unit_type(tuple_t)) { + return std::any_of(tuple_t->args.begin(), tuple_t->args.end(), [&](auto arg){ return arg->template isa(); }); + } + return false; +} + const Type* TypeChecker::coerce(Ptr& expr, const Type* expected) { auto type = expr->type ? expr->type : check(*expr, expected); if (type != expected) { - if (auto implicit = expected->isa()) { - Ptr summoned = make_ptr(expr->loc, Ptr()); - summoned->type = implicit->underlying; - expr.swap(summoned); - return implicit->underlying; + if (contains_implicit(expected)) { + if (auto implicit = expected->isa()) { + Ptr summoned = make_ptr(expr->loc, Ptr()); + summoned->type = implicit->underlying; + expr.swap(summoned); + return implicit->underlying; + } else { + // auto original = expr->isa(); + auto tuple_t = expected->as(); + PtrVector args; + for (auto& arg : tuple_t->args) { + if (auto implicit = arg->isa()) { + Ptr summoned = make_ptr(expr->loc, Ptr()); + summoned->type = implicit->underlying; + args.push_back(std::move(summoned)); + } else { + assert(false); + } + } + expr = make_ptr(expr->loc, std::move(args)); + } } else if (type->subtype(expected)) { expr = make_ptr(expr->loc, std::move(expr), expected); return expected; @@ -893,6 +918,10 @@ const artic::Type* TupleExpr::infer(TypeChecker& checker) { } const artic::Type* TupleExpr::check(TypeChecker& checker, const artic::Type* expected) { + // Allow the empty tuple () when expecting an implicit param, or a tuple of implicit params + if (contains_implicit(expected) && args.empty()) { + return nullptr; + } if (auto tuple_type = expected->isa()) { if (args.size() != tuple_type->args.size()) return checker.bad_arguments(loc, "tuple expression", args.size(), tuple_type->args.size()); @@ -900,12 +929,6 @@ const artic::Type* TupleExpr::check(TypeChecker& checker, const artic::Type* exp checker.coerce(args[i], tuple_type->args[i]); return expected; } - // Allow the empty tuple () to type as an implicit param - if (auto implicit = expected->isa()) { - auto inferred = infer(checker); - if (is_unit_type(inferred)) - return inferred; - } return checker.incompatible_type(loc, "tuple expression", expected); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bfa475d0..f81cff4e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -26,6 +26,7 @@ add_test(NAME simple_if COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC add_test(NAME simple_if_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/if_let.art) add_test(NAME simple_implicit1 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit1.art) add_test(NAME simple_implicit2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit2.art) +add_test(NAME simple_implicit3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit3.art) add_test(NAME simple_while COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while.art) add_test(NAME simple_while_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while_let.art) add_test(NAME simple_for COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/for.art) diff --git a/test/simple/implicit3.art b/test/simple/implicit3.art new file mode 100644 index 00000000..35a324e9 --- /dev/null +++ b/test/simple/implicit3.art @@ -0,0 +1,5 @@ +implicit i32 = 42; +implicit f32 = 0.001; + +fn foo(implicit i: i32, implicit f: f32) {} +fn bar() { foo() } From bdfba09cce94349d79f6dbce2743f93318c2191c Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 13:58:17 +0100 Subject: [PATCH 12/22] made argument synthesis more robust and general --- src/check.cpp | 72 +++++++++++++++++++++++++++------------------ test/CMakeLists.txt | 2 ++ 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/check.cpp b/src/check.cpp index cbf8cb45..38e81701 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -154,40 +154,58 @@ const Type* TypeChecker::deref(Ptr& expr) { return type; } -static bool contains_implicit(const artic::Type* type) { - if (type->isa()) - return true; - else if(auto tuple_t = type->isa(); tuple_t && !is_unit_type(tuple_t)) { +static bool is_unit(Ptr& expr) { + auto tuple_expr = expr->isa(); + return tuple_expr && tuple_expr->args.empty(); +} + +static bool is_tuple_type_with_implicits(const artic::Type* type) { + if(auto tuple_t = type->isa(); tuple_t && !is_unit_type(tuple_t)) return std::any_of(tuple_t->args.begin(), tuple_t->args.end(), [&](auto arg){ return arg->template isa(); }); - } return false; } const Type* TypeChecker::coerce(Ptr& expr, const Type* expected) { - auto type = expr->type ? expr->type : check(*expr, expected); - if (type != expected) { - if (contains_implicit(expected)) { - if (auto implicit = expected->isa()) { - Ptr summoned = make_ptr(expr->loc, Ptr()); + if (auto implicit = expected->isa()) { + // Only the empty tuple () can be coerced into a Summon[T] + if (is_unit(expr)) { + Ptr summoned = make_ptr(expr->loc, Ptr()); + summoned->type = implicit->underlying; + expr.swap(summoned); + return implicit->underlying; + } + } else if (is_tuple_type_with_implicits(expected)) { + auto loc = expr->loc; + auto deconstructed = expr->isa(); + auto tuple_t = expected->as(); + PtrVector args; + size_t i = 0; + for (auto& arg : tuple_t->args) { + if (auto implicit = arg->isa()) { + Ptr summoned = make_ptr(loc, Ptr()); summoned->type = implicit->underlying; - expr.swap(summoned); - return implicit->underlying; + args.push_back(std::move(summoned)); } else { - // auto original = expr->isa(); - auto tuple_t = expected->as(); - PtrVector args; - for (auto& arg : tuple_t->args) { - if (auto implicit = arg->isa()) { - Ptr summoned = make_ptr(expr->loc, Ptr()); - summoned->type = implicit->underlying; - args.push_back(std::move(summoned)); - } else { - assert(false); - } + // we just had _one_ non-implicit argument, so there is no list to deconstruct, we use the expr + if (!deconstructed) { + if (i > 0) + return bad_arguments(loc, "fn parameters", 1, i); // TODO these error messages are bad + args.push_back(std::move(expr)); + } else { + // otherwise we + if (i >= deconstructed->args.size()) + return bad_arguments(loc, "fn parameters", deconstructed->args.size(), i); + args.push_back(std::move(deconstructed->args[i])); } - expr = make_ptr(expr->loc, std::move(args)); } - } else if (type->subtype(expected)) { + i++; + } + expr = make_ptr(loc, std::move(args)); + } + + auto type = expr->type ? expr->type : check(*expr, expected); + if (type != expected) { + if (type->subtype(expected)) { expr = make_ptr(expr->loc, std::move(expr), expected); return expected; } else @@ -918,10 +936,6 @@ const artic::Type* TupleExpr::infer(TypeChecker& checker) { } const artic::Type* TupleExpr::check(TypeChecker& checker, const artic::Type* expected) { - // Allow the empty tuple () when expecting an implicit param, or a tuple of implicit params - if (contains_implicit(expected) && args.empty()) { - return nullptr; - } if (auto tuple_type = expected->isa()) { if (args.size() != tuple_type->args.size()) return checker.bad_arguments(loc, "tuple expression", args.size(), tuple_type->args.size()); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f81cff4e..f246c51b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,6 +27,8 @@ add_test(NAME simple_if_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC add_test(NAME simple_implicit1 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit1.art) add_test(NAME simple_implicit2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit2.art) add_test(NAME simple_implicit3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit3.art) +add_test(NAME simple_implicit4 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit4.art) +add_test(NAME simple_implicit5 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit5.art) add_test(NAME simple_while COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while.art) add_test(NAME simple_while_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while_let.art) add_test(NAME simple_for COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/for.art) From 6c951062c00f44cf6ddfc9731c1cef454d0a707e Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 14:00:27 +0100 Subject: [PATCH 13/22] add missing tests --- test/simple/implicit4.art | 5 +++++ test/simple/implicit5.art | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 test/simple/implicit4.art create mode 100644 test/simple/implicit5.art diff --git a/test/simple/implicit4.art b/test/simple/implicit4.art new file mode 100644 index 00000000..93f07af9 --- /dev/null +++ b/test/simple/implicit4.art @@ -0,0 +1,5 @@ +implicit i32 = 42; +implicit f32 = 0.001; + +fn foo(explicit: bool, implicit i: i32, implicit f: f32) {} +fn bar() { foo(true) } diff --git a/test/simple/implicit5.art b/test/simple/implicit5.art new file mode 100644 index 00000000..4d149be4 --- /dev/null +++ b/test/simple/implicit5.art @@ -0,0 +1,5 @@ +implicit i32 = 42; +implicit f32 = 0.001; + +fn foo(explicit: bool, too: bool, implicit i: i32, implicit f: f32) {} +fn bar() { foo(false, true) } \ No newline at end of file From a607a36f53ef200a546c7e985c75c031d53dd5d0 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Sat, 26 Nov 2022 16:15:42 +0100 Subject: [PATCH 14/22] bugfix: implicit arguments should be overridable --- src/check.cpp | 33 +++++++++++++++++---------------- test/CMakeLists.txt | 1 + test/failure/implicit3.art | 4 ++++ 3 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 test/failure/implicit3.art diff --git a/src/check.cpp b/src/check.cpp index 38e81701..5dd5f2f8 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -179,26 +179,27 @@ const Type* TypeChecker::coerce(Ptr& expr, const Type* expected) { auto deconstructed = expr->isa(); auto tuple_t = expected->as(); PtrVector args; - size_t i = 0; - for (auto& arg : tuple_t->args) { - if (auto implicit = arg->isa()) { - Ptr summoned = make_ptr(loc, Ptr()); - summoned->type = implicit->underlying; - args.push_back(std::move(summoned)); - } else { - // we just had _one_ non-implicit argument, so there is no list to deconstruct, we use the expr - if (!deconstructed) { - if (i > 0) - return bad_arguments(loc, "fn parameters", 1, i); // TODO these error messages are bad + for (size_t i = 0; i < tuple_t->args.size(); i++) { + if (!deconstructed) { + if (i == 0 && !is_unit(expr)) { args.push_back(std::move(expr)); - } else { - // otherwise we - if (i >= deconstructed->args.size()) - return bad_arguments(loc, "fn parameters", deconstructed->args.size(), i); + continue; + } + } else { + if (i < deconstructed->args.size()) { args.push_back(std::move(deconstructed->args[i])); + continue; } } - i++; + + if (auto implicit = tuple_t->args[i]->isa()) { + Ptr summoned = make_ptr(loc, Ptr()); + summoned->type = implicit->underlying; + args.push_back(std::move(summoned)); + continue; + } + + bad_arguments(loc, "non-implicit arguments", i, tuple_t->args.size()); } expr = make_ptr(loc, std::move(args)); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f246c51b..5461088b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -103,6 +103,7 @@ add_failure_test(NAME failure_if COMMAND artic ${CMAKE_CURRENT_SOURC 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) add_failure_test(NAME failure_implicit2 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/implicit2.art) +add_failure_test(NAME failure_implicit3 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/implicit3.art) add_failure_test(NAME failure_while_let COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/while_let.art) add_failure_test(NAME failure_structs1 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/structs1.art) add_failure_test(NAME failure_structs2 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/structs2.art) diff --git a/test/failure/implicit3.art b/test/failure/implicit3.art new file mode 100644 index 00000000..ad201e47 --- /dev/null +++ b/test/failure/implicit3.art @@ -0,0 +1,4 @@ +implicit i32 = 42; + +fn foo(explicit: bool, implicit i: i32) {} +fn bar() { foo(false, false) } From 259284eb7676637639f798e984a98ac4e3e55d64 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 13 Jan 2023 20:13:53 +0100 Subject: [PATCH 15/22] feature: implicit params are made implicitly available --- include/artic/ast.h | 9 ++++++++- include/artic/summoner.h | 4 +++- src/ast.cpp | 26 ++++++++++++++++++++++++++ src/summoner.cpp | 12 +++++++++--- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/include/artic/ast.h b/include/artic/ast.h index 4b4d6851..5465c7e7 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -146,12 +146,16 @@ struct IdPtrn; struct Ptrn : public Node { Ptrn(const Loc& loc) : Node(loc) {} + Ptr as_expr; + bool is_tuple() const; const artic::Type* check(TypeChecker&, const artic::Type*) override; /// Collect patterns that bind an identifier to a value in this pattern. virtual void collect_bound_ptrns(std::vector&) const; + /// Rewrites the pattern into an expression + virtual const Expr* to_expr() { return as_expr.get(); } /// Returns true when the pattern is trivial (e.g. always matches). virtual bool is_trivial() const = 0; /// Emits IR for the pattern, given a value to bind it to. @@ -1561,6 +1565,7 @@ struct TypedPtrn : public Ptrn { void emit(Emitter&, const thorin::Def*) const override; const artic::Type* infer(TypeChecker&) override; void bind(NameBinder&) override; + const Expr* to_expr() override; void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1581,6 +1586,7 @@ struct IdPtrn : public Ptrn { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + const Expr* to_expr() override; void resolve_summons(Summoner&) override; void print(Printer&) const override; }; @@ -1598,6 +1604,7 @@ struct LiteralPtrn : public Ptrn { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; + const Expr* to_expr() override; void resolve_summons(Summoner&) override {}; void print(Printer&) const override; }; @@ -1615,7 +1622,7 @@ struct ImplicitParamPtrn : public Ptrn { const artic::Type* infer(TypeChecker&) override; const artic::Type* check(TypeChecker&, const artic::Type*) override; void bind(NameBinder&) override; - void resolve_summons(Summoner&) override {}; + void resolve_summons(Summoner&) override; void print(Printer&) const override; }; diff --git a/include/artic/summoner.h b/include/artic/summoner.h index 6e67c6a9..1c35de52 100644 --- a/include/artic/summoner.h +++ b/include/artic/summoner.h @@ -23,7 +23,7 @@ class Summoner : public Logger { void pop_scope(); void insert(const artic::Type*, const ast::Expr*); - const ast::Expr* resolve(const artic::Type*); + const ast::Expr* resolve(const artic::Type*, const artic::Loc& at); bool error = false; std::vector> scopes; @@ -32,6 +32,8 @@ class Summoner : public Logger { friend ast::ImplicitDecl; friend ast::ModDecl; friend ast::BlockExpr; + friend ast::FnExpr; + friend ast::ImplicitParamPtrn; }; } // namespace artic diff --git a/src/ast.cpp b/src/ast.cpp index 28b8a9be..725a8a2c 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -619,6 +619,12 @@ bool TypedPtrn::is_trivial() const { return !ptrn || ptrn->is_trivial(); } +const Expr* TypedPtrn::to_expr() { + if (!ptrn) + return nullptr; + return ptrn->to_expr(); +} + void IdPtrn::collect_bound_ptrns(std::vector& bound_ptrns) const { bound_ptrns.emplace_back(this); if (sub_ptrn) @@ -629,10 +635,30 @@ bool IdPtrn::is_trivial() const { return !sub_ptrn || sub_ptrn->is_trivial(); } +const Expr* IdPtrn::to_expr() { + if (as_expr) + return as_expr.get(); + Identifier id = decl->id; + std::vector elems; + elems.push_back(Path::Elem( loc, std::move(id), {} )); + Path path = Path(loc, std::move(elems)); + path.start_decl = decl.get(); + path.is_value = true; + as_expr = make_ptr(std::move(path)); + return as_expr.get(); +} + bool LiteralPtrn::is_trivial() const { return false; } +const Expr* LiteralPtrn::to_expr() { + if (as_expr) + return as_expr.get(); + as_expr = make_ptr(loc, lit); + return as_expr.get(); +} + bool ImplicitParamPtrn::is_trivial() const { return underlying->is_trivial(); } diff --git a/src/summoner.cpp b/src/summoner.cpp index 3f6d4f8e..46ff1a6e 100644 --- a/src/summoner.cpp +++ b/src/summoner.cpp @@ -29,7 +29,7 @@ void Summoner::insert(const artic::Type* t, const ast::Expr* e) { scope.emplace(t, e); } -const ast::Expr* Summoner::resolve(const artic::Type* t) { +const ast::Expr* Summoner::resolve(const artic::Type* t, const artic::Loc& at) { for (auto& scope : scopes) { auto found = scope.find(t); if (found != scope.end()) @@ -37,7 +37,7 @@ const ast::Expr* Summoner::resolve(const artic::Type* t) { // TODO: use subtyping relations and generators } error = true; - log::error("Could not summon a {}", *t); + log::error("Could not summon an implicit value of type {} at {}", *t, at); return nullptr; } @@ -60,7 +60,7 @@ void TypedExpr::resolve_summons(artic::Summoner& summoner) { } void SummonExpr::resolve_summons(artic::Summoner& summoner) { - resolved = summoner.resolve(type); + resolved = summoner.resolve(type, loc); } void FieldExpr::resolve_summons(artic::Summoner& summoner) { @@ -88,8 +88,10 @@ void RepeatArrayExpr::resolve_summons(artic::Summoner& summoner) { } void FnExpr::resolve_summons(artic::Summoner& summoner) { + summoner.push_scope(); param->resolve_summons(summoner); if(body) body->resolve_summons(summoner); + summoner.pop_scope(); } void BlockExpr::resolve_summons(artic::Summoner& summoner) { @@ -215,6 +217,10 @@ void IdPtrn::resolve_summons(artic::Summoner& summoner) { if (sub_ptrn) sub_ptrn->resolve_summons(summoner); } +void ImplicitParamPtrn::resolve_summons(artic::Summoner& summoner) { + summoner.insert(underlying->type, underlying->to_expr()); +} + void FieldPtrn::resolve_summons(artic::Summoner& summoner) { if (ptrn) ptrn->resolve_summons(summoner); } From 6edefe95ef9c0720ac121748d769b7ddbe2c3422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ars=C3=A8ne=20P=C3=A9rard-Gayot?= Date: Mon, 10 Jul 2023 19:16:15 +0200 Subject: [PATCH 16/22] Fix bug when accessing a member through a reference to a pointer Fixes issue #14 --- src/check.cpp | 8 +++++++- test/simple/address.art | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/check.cpp b/src/check.cpp index 56abad7a..aec97556 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -1019,8 +1019,14 @@ const artic::Type* CallExpr::infer(TypeChecker& checker) { const artic::Type* ProjExpr::infer(TypeChecker& checker) { auto [ref_type, expr_type] = remove_ref(checker.infer(*expr)); auto ptr_type = expr_type->isa(); - if (ptr_type) + if (ptr_type) { + // Must dereference references to pointers, such that the pointer offset is computed on the + // pointer, not on the reference to the pointer (references and pointers are both emitted as + // pointers). + if (ref_type) + checker.deref(expr); expr_type = ptr_type->pointee; + } const artic::Type* result_type = nullptr; auto [type_app, struct_type] = match_app(expr_type); diff --git a/test/simple/address.art b/test/simple/address.art index 7f3ffa88..cab492bd 100644 --- a/test/simple/address.art +++ b/test/simple/address.art @@ -12,3 +12,5 @@ fn test3(i: i32) = s(i); fn test4(i: i32) = (&[1, 2, 3, 4])(i); #[export] fn test5(mut x: [i32 * 4], i: i32) = &mut x(i); +#[export] +fn test6(mut x: &mut (i32, i32)) = &mut x.0; From e40f4d94df27187ed8294cd8e8ca99c6bc38ddfc Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Mon, 4 Sep 2023 14:52:58 +0200 Subject: [PATCH 17/22] fixed `super` not parsing as part of type paths --- src/parser.cpp | 1 + test/simple/mod3.art | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/parser.cpp b/src/parser.cpp index 83bf6a88..2f3634e2 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1019,6 +1019,7 @@ Ptr Parser::parse_type() { Ptr type; switch (ahead().tag()) { case Token::Fn: return parse_fn_type(); + case Token::Super: case Token::Id: return parse_named_type(); case Token::LParen: return parse_tuple_type(); case Token::LogicAnd: diff --git a/test/simple/mod3.art b/test/simple/mod3.art index cce93ed6..8c2b471e 100644 --- a/test/simple/mod3.art +++ b/test/simple/mod3.art @@ -1,4 +1,6 @@ mod foo { fn bar() = super::baz(); + static t = super::T { x = 0 }; } fn baz() {} +struct T { x: i32 } From 3fc65097f98d1982bf1175151e271a9f5babe6c2 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Mon, 4 Sep 2023 14:54:29 +0200 Subject: [PATCH 18/22] made implicit-summoning aware of modules --- src/summoner.cpp | 2 ++ test/CMakeLists.txt | 1 + test/simple/implicit6.art | 25 +++++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 test/simple/implicit6.art diff --git a/src/summoner.cpp b/src/summoner.cpp index 46ff1a6e..60aba0a1 100644 --- a/src/summoner.cpp +++ b/src/summoner.cpp @@ -200,12 +200,14 @@ void RecordDecl::resolve_summons(artic::Summoner& summoner) { } void ModDecl::resolve_summons(artic::Summoner& summoner) { + summoner.push_scope(); for (auto& decl: decls) if (auto impl_decl = decl->isa()) summoner.insert(decl->type, impl_decl->value.get()); for (auto& decl: decls) decl->resolve_summons(summoner); + summoner.pop_scope(); } void TypedPtrn::resolve_summons(artic::Summoner& summoner) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5461088b..304592e3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,6 +29,7 @@ add_test(NAME simple_implicit2 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC add_test(NAME simple_implicit3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit3.art) add_test(NAME simple_implicit4 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit4.art) add_test(NAME simple_implicit5 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit5.art) +add_test(NAME simple_implicit6 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit6.art) add_test(NAME simple_while COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while.art) add_test(NAME simple_while_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while_let.art) add_test(NAME simple_for COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/for.art) diff --git a/test/simple/implicit6.art b/test/simple/implicit6.art new file mode 100644 index 00000000..befccd27 --- /dev/null +++ b/test/simple/implicit6.art @@ -0,0 +1,25 @@ +struct Emergency { + number: i32 +} + +fn @call_help(implicit e: Emergency) = e.number; + +mod usa { + implicit super::Emergency = super::Emergency { number = 911 }; + + #[export] + fn fire() = super::call_help(); +} + +mod eu { + implicit super::Emergency = super::Emergency { number = 112 }; + + #[export] + fn fire() = super::call_help(); + + #[export] + fn fire_but_in_belgium() -> i32 { + implicit super::Emergency = super::Emergency { number = 100 }; + return (super::call_help()) + } +} From 545b5e3ddc4d17fd2595494b310a45125a645226 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Mon, 4 Sep 2023 14:55:58 +0200 Subject: [PATCH 19/22] added trait-like test for implicits --- test/CMakeLists.txt | 1 + test/simple/implicit7.art | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 test/simple/implicit7.art diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 304592e3..c393d452 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,6 +30,7 @@ add_test(NAME simple_implicit3 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC add_test(NAME simple_implicit4 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit4.art) add_test(NAME simple_implicit5 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit5.art) add_test(NAME simple_implicit6 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit6.art) +add_test(NAME simple_implicit7 COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/implicit7.art) add_test(NAME simple_while COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while.art) add_test(NAME simple_while_let COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/while_let.art) add_test(NAME simple_for COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/for.art) diff --git a/test/simple/implicit7.art b/test/simple/implicit7.art new file mode 100644 index 00000000..1199a730 --- /dev/null +++ b/test/simple/implicit7.art @@ -0,0 +1,12 @@ +struct Zero[T] { value: T } +struct Cmp[T] { cmp: fn(T, T) -> bool } + +implicit = Zero[i32] { value = 0 }; +implicit = Cmp[i32] { cmp = |x, y| x == y }; + +fn is_zero[T](value: T, implicit zero: Zero[T], implicit cmp: Cmp[T]) -> bool { + cmp.cmp(value, zero.value) +} + +#[export] +fn foo(i: i32) = if (is_zero[i32](i)) { 1 } else { 0 }; \ No newline at end of file From a9c49708de6cbad2bd05c92738e3e142484cbb32 Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Mon, 4 Sep 2023 15:07:11 +0200 Subject: [PATCH 20/22] Added documentation about implicits and introduce them in README.md --- README.md | 14 +++++ doc/implicits.md | 159 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 doc/implicits.md diff --git a/README.md b/README.md index ebaea536..0da11500 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,20 @@ fn select[T](cond: bool, a: T, b: T) -> T { fn main() -> i32 { S[i32] { elem = select[i32](true, 0, 1) }.elem } +``` + - __Experimental__: [Implicits](doc/implicits.md) provide a form of ad-hoc polymorphism, similar to Rust traits: +```rust +struct Vec3f { + x: f32, y: f32, z: f32 +} +struct Len[T] { + len: fn(T) -> f32 +} + +implicit = Len[Vec3f] { + len = | v | sqrt(v.x * v.x + v.y * v.y + v.z * v.z) +}; +fn longest(a: Vec3f, b: Vec3f, implicit l: Len[Vec3f]) = if (l.len(a) > l.len(b)) { a } else { b }; ``` - The type inference algorithm is now bidirectional type checking, which means that type information is propagated _locally_, not globally. This gives improved diff --git a/doc/implicits.md b/doc/implicits.md new file mode 100644 index 00000000..c5fb316a --- /dev/null +++ b/doc/implicits.md @@ -0,0 +1,159 @@ +# Implicits + +Artic experimentally supports ad-hoc polymorphism through implicit value declarations. +The keyword `implicit` is used in front of function parameters to indicate the parameter value may be omitted when calling the function. +When no value for such a parameter is provided, Artic will search for a suitable `implicit` declaration in the enclosing scopes. + +Here is a simple example: + +```rust +implicit i32 = 42; + +#[export] +fn the_answer(implicit i: i32) = i; +``` + +Calling `the_answer()` will yield `42`, because of the `implicit i32 = 42;` declaration on the first line. +Note that implicit declarations are not named, but instead use the type as a unique identifier. + +## Syntax Guide + +Implicit parameters should always be placed at the end of a function parameter list. + +For shorter implicit declarations, it is possible to omit the type on the left-hand side of the assignment. +Be mindful however, in such cases type inference will be invoked to type the implicit, and there is a risk the type it comes up with is not the expected one. + +```rust +struct T { + x: i32, + y: i32 +} + +implicit = T { x = 4, y = 2 }; +// instead of +implicit T = T { x = 4, y = 2 }; +``` + +Implicits are resolved in a scoping-sensitive manner, which includes modules and function bodies, +allowing for contextual availability and some degree of specialization: + +```rust +struct Emergency { + number: i32 +} + +fn @call_help(implicit e: Emergency) = e.number; + +mod usa { + implicit super::Emergency = super::Emergency { number = 911 }; + + #[export] + fn fire() = super::call_help(); +} + +mod eu { + implicit super::Emergency = super::Emergency { number = 112 }; + + #[export] + fn fire() = super::call_help(); + + #[export] + fn fire_but_in_belgium() -> i32 { + implicit super::Emergency = super::Emergency { number = 100 }; + return (super::call_help()) + } +} +``` + +It is possible to directly invoke the implicit solver through the `summon` keyword. +This is mostly useful for debugging the compiler and helps understanding how implicits are lowered, +however it is not recommended to use it in programs and it will likely be made unaccessible to user code in future updates. + +```rust +implicit i32 = 42; + +fn foo() -> i32 { summon[i32] } +``` + +## In practice + +Implicits can be used like Rust or Haskell traits, to implement functionality only available on certain types: + +```rust +struct Zero[T] { value: T } +struct Cmp[T] { cmp: fn(T, T) -> bool } + +implicit = Zero[i32] { value = 0 }; +implicit = Cmp[i32] { cmp = |x, y| x == y }; + +fn is_zero[T](value: T, implicit zero: Zero[T], implicit cmp: Cmp[T]) -> bool { + cmp.cmp(value, zero.value) +} + +#[export] +fn foo(i: i32) = if (is_zero[i32](i)) { 1 } else { 0 }; +``` + +Implicit parameters serve as an implicit declaration of their own: this means that within the scope of a function that +requires an implicit parameter, we can call other functions that require some or all of those parameters without needing +to bring in any implicit declaration: + +```rust +fn is_default_or_zero[T](value: T, default: T, implicit zero: Zero[T], implicit cmp: Cmp[T]) -> bool { + cmp.cmp(value, default) || is_zero(value) +} +``` + +Dummy wrapper types can be used to disambiguate between different implicit values that otherwise are the same: + +```rust +struct TheAnswer { + number: i32 +} + +implicit TheAnswer = TheAnswer { number = 42 }; + +struct NiceNumber { + number: i32 +} + +implicit NiceNumber = NiceNumber { number = 69 }; + +fn foo(implicit a: TheAnswer, implicit f: NiceNumber) {} +``` + +## TODO / Future features + +Currently implicit declarations only support non-generic values. +The implicits feature is considered a work in progress, and current syntax, and the design at large is susceptible to change. +In the future, we'll introduce support for both generic declarations, and derived implicit declarations. +Please express feedback to us loudly and clearly in GitHub issues if you'd like to shape the future of this feature. + +Generic declarations are just that, available for any type: + +``` +#[import(cc = "builtin")] fn undef[T]() -> T; + +implicit Zero[T] = Zero[T] { value = undef[T]() /* technically correct, the best kind of correct */ } +``` + +While derived implicit declarations are like functions that get invoked in order to generate the appropriate implicit. +Those functions can have implicit parameters of their own, which allows building up interesting abstractions: + +```rust +struct IsZero[T] { + is_zero: fn (T) -> bool +} + +implicit IsZero[i32](implicit cmp: Cmp[i32]) { + is_zero = | v | cmp.cmp(0, v) +}; +``` + +The real fun begins however, when we merge the two, allowing to define rich abstract interfaces: + +```rust +implicit IsZero[T](implicit zero: Zero[T], implicit cmp: Cmp[T]) { + is_zero = | v | cmp.cmp(zero.zero, v) +}; +``` \ No newline at end of file From 491d6775ef9d8717fd920f6a54b89fa702d9bb1a Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Mon, 4 Sep 2023 22:46:05 +0200 Subject: [PATCH 21/22] whitespace --- src/check.cpp | 2 +- src/summoner.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check.cpp b/src/check.cpp index 5dd5f2f8..81674536 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -160,7 +160,7 @@ static bool is_unit(Ptr& expr) { } static bool is_tuple_type_with_implicits(const artic::Type* type) { - if(auto tuple_t = type->isa(); tuple_t && !is_unit_type(tuple_t)) + if (auto tuple_t = type->isa(); tuple_t && !is_unit_type(tuple_t)) return std::any_of(tuple_t->args.begin(), tuple_t->args.end(), [&](auto arg){ return arg->template isa(); }); return false; } diff --git a/src/summoner.cpp b/src/summoner.cpp index 60aba0a1..bb6ce9ed 100644 --- a/src/summoner.cpp +++ b/src/summoner.cpp @@ -90,7 +90,7 @@ void RepeatArrayExpr::resolve_summons(artic::Summoner& summoner) { void FnExpr::resolve_summons(artic::Summoner& summoner) { summoner.push_scope(); param->resolve_summons(summoner); - if(body) body->resolve_summons(summoner); + if (body) body->resolve_summons(summoner); summoner.pop_scope(); } From ed0aeb86545608a68d1be7877340aebdbd121b0f Mon Sep 17 00:00:00 2001 From: Hugo Devillers Date: Fri, 13 Oct 2023 13:28:05 +0200 Subject: [PATCH 22/22] fix: allow constant expressions to include implicit casts --- src/ast.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast.cpp b/src/ast.cpp index 725a8a2c..f11eb046 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -600,7 +600,7 @@ bool ImplicitCastExpr::is_constant() const { return !static_decl->is_mut; } } - return false; + return expr->is_constant(); } bool AsmExpr::has_side_effect() const {