Skip to content

Commit

Permalink
Merge pull request #47 from BastianBlokland/feature/instance-calls
Browse files Browse the repository at this point in the history
Add support for instance calls
  • Loading branch information
BastianBlokland committed Jan 9, 2020
2 parents 155b59f + e8d2158 commit 27d6b3c
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 2 deletions.
21 changes: 20 additions & 1 deletion src/frontend/internal/get_expr.cpp
Expand Up @@ -135,6 +135,7 @@ auto GetExpr::visit(const parse::BinaryExprNode& n) -> void {
auto GetExpr::visit(const parse::CallExprNode& n) -> void {
auto getIdVisitor = GetIdentifier{};
n[0].accept(&getIdVisitor);
auto* instance = getIdVisitor.getInstance();
auto identifier = getIdVisitor.getIdentifier();
auto typeParams = getIdVisitor.getTypeParams();

Expand All @@ -146,7 +147,7 @@ auto GetExpr::visit(const parse::CallExprNode& n) -> void {
}

auto nameToken = *identifier;
auto args = getChildExprs(n, 1);
auto args = getChildExprs(n, instance, 1);
if (!args) {
assert(m_context->hasErrors());
return;
Expand Down Expand Up @@ -557,9 +558,27 @@ auto GetExpr::visit(const parse::UnionDeclStmtNode & /*unused*/) -> void {

auto GetExpr::getChildExprs(const parse::Node& n, unsigned int skipAmount)
-> std::optional<std::pair<std::vector<prog::expr::NodePtr>, prog::sym::TypeSet>> {
return getChildExprs(n, nullptr, skipAmount);
}

auto GetExpr::getChildExprs(
const parse::Node& n, const parse::Node* additionalNode, unsigned int skipAmount)
-> std::optional<std::pair<std::vector<prog::expr::NodePtr>, prog::sym::TypeSet>> {
auto isValid = true;
auto argTypes = std::vector<prog::sym::TypeId>{};
auto args = std::vector<prog::expr::NodePtr>{};
if (additionalNode) {
auto arg = getSubExpr(*additionalNode, prog::sym::TypeId::inferType());
if (arg) {
const auto argType = arg->getType();
if (argType.isConcrete()) {
argTypes.push_back(argType);
args.push_back(std::move(arg));
} else {
isValid = false;
}
}
}
for (auto i = skipAmount; i < n.getChildCount(); ++i) {
auto arg = getSubExpr(n[i], prog::sym::TypeId::inferType());
if (arg) {
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/internal/get_expr.hpp
Expand Up @@ -55,6 +55,10 @@ class GetExpr final : public parse::NodeVisitor {
[[nodiscard]] auto getChildExprs(const parse::Node& n, unsigned int skipAmount = 0U)
-> std::optional<std::pair<std::vector<prog::expr::NodePtr>, prog::sym::TypeSet>>;

[[nodiscard]] auto getChildExprs(
const parse::Node& n, const parse::Node* additionalNode, unsigned int skipAmount = 0U)
-> std::optional<std::pair<std::vector<prog::expr::NodePtr>, prog::sym::TypeSet>>;

[[nodiscard]] auto
getSubExpr(const parse::Node& n, prog::sym::TypeId typeHint, bool checkedConstsAccess = false)
-> prog::expr::NodePtr;
Expand Down
11 changes: 11 additions & 0 deletions src/frontend/internal/get_identifier.cpp
@@ -1,8 +1,14 @@
#include "internal/get_identifier.hpp"
#include "parse/node_expr_field.hpp"
#include "parse/node_expr_id.hpp"

namespace frontend::internal {

GetIdentifier::GetIdentifier() :
m_instance{nullptr}, m_identifier{std::nullopt}, m_typeParams{std::nullopt} {}

auto GetIdentifier::getInstance() const noexcept -> const parse::Node* { return m_instance; }

auto GetIdentifier::getIdentifier() const noexcept -> const std::optional<lex::Token>& {
return m_identifier;
}
Expand All @@ -16,4 +22,9 @@ auto GetIdentifier::visit(const parse::IdExprNode& n) -> void {
m_typeParams = n.getTypeParams();
}

auto GetIdentifier::visit(const parse::FieldExprNode& n) -> void {
m_instance = &n[0];
m_identifier = n.getId();
}

} // namespace frontend::internal
8 changes: 7 additions & 1 deletion src/frontend/internal/get_identifier.hpp
@@ -1,21 +1,27 @@
#pragma once
#include "lex/token.hpp"
#include "parse/node.hpp"
#include "parse/node_visitor_optional.hpp"
#include "parse/type_param_list.hpp"
#include "prog/program.hpp"
#include <optional>

namespace frontend::internal {

class GetIdentifier final : public parse::OptionalNodeVisitor {
public:
GetIdentifier() = default;
GetIdentifier();

[[nodiscard]] auto getInstance() const noexcept -> const parse::Node*;
[[nodiscard]] auto getIdentifier() const noexcept -> const std::optional<lex::Token>&;
[[nodiscard]] auto getTypeParams() const noexcept -> const std::optional<parse::TypeParamList>&;

auto visit(const parse::IdExprNode& n) -> void override;

auto visit(const parse::FieldExprNode& n) -> void override;

private:
const parse::Node* m_instance;
std::optional<lex::Token> m_identifier;
std::optional<parse::TypeParamList> m_typeParams;
};
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/internal/typeinfer_expr.cpp
Expand Up @@ -71,6 +71,7 @@ auto TypeInferExpr::visit(const parse::BinaryExprNode& n) -> void {
auto TypeInferExpr::visit(const parse::CallExprNode& n) -> void {
auto getIdVisitor = GetIdentifier{};
n[0].accept(&getIdVisitor);
auto* instance = getIdVisitor.getInstance();
auto identifier = getIdVisitor.getIdentifier();
auto typeParams = getIdVisitor.getTypeParams();

Expand All @@ -84,6 +85,9 @@ auto TypeInferExpr::visit(const parse::CallExprNode& n) -> void {

auto argTypes = std::vector<prog::sym::TypeId>{};
argTypes.reserve(n.getChildCount());
if (instance) {
argTypes.push_back(inferSubExpr(*instance));
}
for (auto i = 1U; i < n.getChildCount(); ++i) {
argTypes.push_back(inferSubExpr(n[i]));
}
Expand Down
40 changes: 40 additions & 0 deletions tests/frontend/get_call_expr_test.cpp
Expand Up @@ -3,6 +3,7 @@
#include "helpers.hpp"
#include "prog/expr/node_const.hpp"
#include "prog/expr/node_lit_int.hpp"
#include "prog/expr/node_lit_string.hpp"

namespace frontend {

Expand Down Expand Up @@ -105,6 +106,43 @@ TEST_CASE("Analyzing call expressions", "[frontend]") {
std::move(args)));
}

SECTION("Get instance call") {
const auto& output = ANALYZE("fun f1(int i) -> int i "
"fun f2(int i) -> int i.f1()");
REQUIRE(output.isSuccess());

const auto& fDef = GET_FUNC_DEF(output, "f2", GET_TYPE_ID(output, "int"));
const auto& consts = fDef.getConsts();

auto args = std::vector<prog::expr::NodePtr>{};
args.push_back(prog::expr::constExprNode(consts, *consts.lookup("i")));
CHECK(
fDef.getExpr() ==
*prog::expr::callExprNode(
output.getProg(),
GET_FUNC_ID(output, "f1", GET_TYPE_ID(output, "int")),
std::move(args)));
}

SECTION("Get instance call with args") {
const auto& output = ANALYZE("fun f1(int i, string v) -> string i.string() + v "
"fun f2(int i) -> string i.f1(\"test\")");
REQUIRE(output.isSuccess());

const auto& fDef = GET_FUNC_DEF(output, "f2", GET_TYPE_ID(output, "int"));
const auto& consts = fDef.getConsts();

auto args = std::vector<prog::expr::NodePtr>{};
args.push_back(prog::expr::constExprNode(consts, *consts.lookup("i")));
args.push_back(prog::expr::litStringNode(output.getProg(), "test"));
CHECK(
fDef.getExpr() ==
*prog::expr::callExprNode(
output.getProg(),
GET_FUNC_ID(output, "f1", GET_TYPE_ID(output, "int"), GET_TYPE_ID(output, "string")),
std::move(args)));
}

SECTION("Diagnostics") {
CHECK_DIAG(
"fun f1() -> int 1 "
Expand All @@ -117,6 +155,8 @@ TEST_CASE("Analyzing call expressions", "[frontend]") {
CHECK_DIAG("fun f() -> int 1()", errUndeclaredCallOperator(src, {"int"}, input::Span{15, 17}));
CHECK_DIAG(
"fun f(int i) -> int i()", errUndeclaredCallOperator(src, {"int"}, input::Span{20, 22}));
CHECK_DIAG(
"fun f1(int i) -> int i.f2()", errUndeclaredFunc(src, "f2", {"int"}, input::Span{21, 26}));
}
}

Expand Down
14 changes: 14 additions & 0 deletions tests/frontend/typeinfer_user_funcs_test.cpp
Expand Up @@ -201,6 +201,20 @@ TEST_CASE("Infer return type of user functions", "[frontend]") {
CHECK(GET_FUNC_DECL(output, "f").getOutput() == GET_TYPE_ID(output, "bool"));
}

SECTION("Instance function call") {
const auto& output = ANALYZE("fun double(int i) i * 2 "
"fun f() (42).double()");
REQUIRE(output.isSuccess());
CHECK(GET_FUNC_DECL(output, "f").getOutput() == GET_TYPE_ID(output, "int"));
}

SECTION("Instance function call with arguments") {
const auto& output = ANALYZE("fun test(int a, string b) a.string() + b "
"fun f() (42).test(\"test\")");
REQUIRE(output.isSuccess());
CHECK(GET_FUNC_DECL(output, "f").getOutput() == GET_TYPE_ID(output, "string"));
}

SECTION("Dynamic call") {
const auto& output = ANALYZE("fun f(delegate{int, int} op) op(1)");
REQUIRE(output.isSuccess());
Expand Down

0 comments on commit 27d6b3c

Please sign in to comment.