From 8cb1e371158e533b41de6627960a5db0490d0f14 Mon Sep 17 00:00:00 2001 From: writemorecode Date: Mon, 9 Feb 2026 19:56:28 +0100 Subject: [PATCH 1/2] Fix type-checking propagation and class allocation validation Bugs fixed: - Control statement type checking only validated the condition type and ignored nested statement failures. - MethodWithoutParametersNode reported return-type mismatches but still returned success. - ClassAllocationNode accepted undeclared class names as valid types. How this commit fixes them: - ControlStatementNode::checkTypes now checks both condition and statement branch results, propagating failure when either side is invalid. - MethodWithoutParametersNode::checkTypes now returns failure after emitting a return-type mismatch diagnostic. - ClassAllocationNode::checkTypes now verifies the allocated class exists in the symbol table and reports an error otherwise. Tests added: - ControlStatementPropagatesStatementTypeFailure - MethodWithoutParametersReturnMismatchFailsTypeCheck - ClassAllocationRequiresDeclaredClassType Validation: - ctest --test-dir build --output-on-failure (35/35 passing). --- src/ast/ClassAllocationNode.cpp | 6 ++++ src/ast/ControlStatementNode.cpp | 16 ++++++++- src/ast/MethodWithoutParametersNode.cpp | 1 + tests/symbol_table_test.cpp | 48 +++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/ast/ClassAllocationNode.cpp b/src/ast/ClassAllocationNode.cpp index 2f447d5..366dc28 100644 --- a/src/ast/ClassAllocationNode.cpp +++ b/src/ast/ClassAllocationNode.cpp @@ -2,6 +2,12 @@ #include "ir/Tac.hpp" std::string ClassAllocationNode::checkTypes(SymbolTable &st) const { + auto *classLookup = st.lookupClass(id); + if (classLookup == nullptr) { + std::cerr << "Error: (line " << lineno << ") Unknown class '" << id + << "'.\n"; + return ""; + } return id; } diff --git a/src/ast/ControlStatementNode.cpp b/src/ast/ControlStatementNode.cpp index 72fdad0..90000df 100644 --- a/src/ast/ControlStatementNode.cpp +++ b/src/ast/ControlStatementNode.cpp @@ -3,11 +3,25 @@ std::string ControlStatementNode::checkTypes(SymbolTable &st) const { const auto condType = cond->checkTypes(st); - if (!condType.empty() && condType != "boolean") { + const auto stmtType = stmts->checkTypes(st); + + bool valid = true; + + if (condType.empty()) { + valid = false; + } else if (condType != "boolean") { std::cerr << "Error: "; std::cerr << "(line " << lineno << ") "; std::cerr << "Condition for " << type << "-statement of invalid type " << condType << ".\n"; + valid = false; + } + + if (stmtType.empty()) { + valid = false; + } + + if (!valid) { return ""; } return "void"; diff --git a/src/ast/MethodWithoutParametersNode.cpp b/src/ast/MethodWithoutParametersNode.cpp index 0f9cf71..b518dbf 100644 --- a/src/ast/MethodWithoutParametersNode.cpp +++ b/src/ast/MethodWithoutParametersNode.cpp @@ -21,6 +21,7 @@ std::string MethodWithoutParametersNode::checkTypes(SymbolTable &st) const { << signatureReturnType << "' in method '" << id->value << "' does not match returned type '" << bodyReturnType << "'.\n"; + return ""; } return type->value; diff --git a/tests/symbol_table_test.cpp b/tests/symbol_table_test.cpp index 8f6c859..45c4111 100644 --- a/tests/symbol_table_test.cpp +++ b/tests/symbol_table_test.cpp @@ -7,6 +7,12 @@ #include #include +#include "ast/BooleanNode.hpp" +#include "ast/ClassAllocationNode.hpp" +#include "ast/ControlStatementNode.hpp" +#include "ast/IdentifierNode.hpp" +#include "ast/MethodBodyNode.hpp" +#include "ast/MethodWithoutParametersNode.hpp" #include "ast/Node.h" #include "lexing/Diagnostics.hpp" #include "lexing/Lexer.hpp" @@ -17,6 +23,7 @@ #include "semantic/Scope.hpp" #include "semantic/SymbolTable.hpp" #include "semantic/SymbolTableVisitor.hpp" +#include "semantic/TypeNode.hpp" namespace { @@ -83,6 +90,16 @@ const Scope *find_child_scope(const Scope *parent, std::string_view name) { return nullptr; } +class FailingTypeNode final : public Node { + public: + explicit FailingTypeNode(int line) : Node("Failing type node", line) {} + + std::string checkTypes(SymbolTable &st) const override { + (void)st; + return ""; + } +}; + [[maybe_unused]] constexpr std::string_view kGoldenProgram2Source = R"(public class Main { public static void main(String[] args) { @@ -636,3 +653,34 @@ class Foo { EXPECT_NE(error->message.find("Variable 'x' already declared"), std::string::npos); } + +TEST(SymbolTable, ControlStatementPropagatesStatementTypeFailure) { + SymbolTable st; + + IfNode if_node(std::make_unique(1), + std::make_unique(1), 1); + + EXPECT_EQ(if_node.checkTypes(st), ""); +} + +TEST(SymbolTable, MethodWithoutParametersReturnMismatchFailsTypeCheck) { + SymbolTable st; + + MethodWithoutParametersNode method( + std::make_unique("int", 1), + std::make_unique("foo", 1), + std::make_unique( + std::make_unique(1), 1), + 1); + + EXPECT_EQ(method.checkTypes(st), ""); +} + +TEST(SymbolTable, ClassAllocationRequiresDeclaredClassType) { + SymbolTable st; + + ClassAllocationNode allocation( + std::make_unique("MissingClass", 1), 1); + + EXPECT_EQ(allocation.checkTypes(st), ""); +} From a395c167dab7f892bb15738276a17d9cd03976f9 Mon Sep 17 00:00:00 2001 From: writemorecode Date: Mon, 9 Feb 2026 21:02:07 +0100 Subject: [PATCH 2/2] Refactor: move type checking to TypeCheckVisitor pass --- src/ast/ArithmeticExpressionNode.cpp | 16 - src/ast/ArithmeticExpressionNode.hpp | 5 +- src/ast/ArrayAccessNode.cpp | 22 - src/ast/ArrayAccessNode.hpp | 1 - src/ast/ArrayLengthNode.cpp | 12 - src/ast/ArrayLengthNode.hpp | 2 +- src/ast/BooleanExpressionNode.cpp | 15 - src/ast/BooleanExpressionNode.hpp | 1 - src/ast/BooleanNode.cpp | 3 - src/ast/BooleanNode.hpp | 2 - src/ast/ClassAllocationNode.cpp | 10 - src/ast/ClassAllocationNode.hpp | 1 - src/ast/ClassNode.cpp | 10 - src/ast/ClassNode.hpp | 1 - src/ast/ControlStatementNode.cpp | 26 - src/ast/ControlStatementNode.hpp | 1 - src/ast/IdentifierNode.cpp | 20 - src/ast/IdentifierNode.hpp | 2 - src/ast/IntegerArrayAllocationNode.cpp | 12 - src/ast/IntegerArrayAllocationNode.hpp | 2 +- src/ast/IntegerNode.cpp | 2 - src/ast/IntegerNode.hpp | 2 - src/ast/LogicalExpressionNode.cpp | 35 - src/ast/LogicalExpressionNode.hpp | 7 +- src/ast/MainClassNode.cpp | 7 - src/ast/MainClassNode.hpp | 1 - src/ast/MethodBodyNode.cpp | 7 - src/ast/MethodBodyNode.hpp | 2 - src/ast/MethodCallNode.cpp | 68 +- src/ast/MethodCallNode.hpp | 1 - src/ast/MethodCallWithoutArgumentsNode.cpp | 44 +- src/ast/MethodCallWithoutArgumentsNode.hpp | 1 - src/ast/MethodNode.cpp | 19 - src/ast/MethodNode.hpp | 9 +- src/ast/MethodParameterNode.hpp | 1 - src/ast/MethodWithoutParametersNode.cpp | 21 - src/ast/MethodWithoutParametersNode.hpp | 5 +- src/ast/Node.cpp | 14 - src/ast/Node.h | 5 +- src/ast/NotNode.cpp | 12 - src/ast/NotNode.hpp | 1 - src/ast/StatementNode.cpp | 45 - src/ast/StatementNode.hpp | 3 +- src/ast/ThisNode.cpp | 12 - src/ast/ThisNode.hpp | 1 - src/ast/VariableNode.cpp | 15 - src/ast/VariableNode.hpp | 2 - src/ir/CFG.cpp | 10 +- src/ir/CFG.hpp | 7 + src/main.cpp | 8 +- src/semantic/SymbolTableVisitor.cpp | 42 +- src/semantic/TypeCheckVisitor.cpp | 912 +++++++++++++++++++++ src/semantic/TypeCheckVisitor.hpp | 31 + src/semantic/TypeNode.cpp | 2 - src/semantic/TypeNode.hpp | 2 - tests/symbol_table_test.cpp | 111 ++- 56 files changed, 1101 insertions(+), 530 deletions(-) create mode 100644 src/semantic/TypeCheckVisitor.cpp create mode 100644 src/semantic/TypeCheckVisitor.hpp diff --git a/src/ast/ArithmeticExpressionNode.cpp b/src/ast/ArithmeticExpressionNode.cpp index a3192fc..a0bb44a 100644 --- a/src/ast/ArithmeticExpressionNode.cpp +++ b/src/ast/ArithmeticExpressionNode.cpp @@ -2,22 +2,6 @@ #include "ir/ArithmeticTac.hpp" -std::string ArithmeticExpressionNode::checkTypes(SymbolTable &st) const { - const auto lhsType = left->checkTypes(st); - const auto rhsType = right->checkTypes(st); - - if (!lhsType.empty() && !rhsType.empty()) { - if (lhsType == "int" && rhsType == "int") { - return "int"; - } - } - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << type << " operation does not support operands of types "; - std::cerr << "'" << lhsType << "' and '" << rhsType << "'.\n"; - return ""; -} - Operand PlusNode::generateIR(CFG &graph, SymbolTable &st) { auto lhs_name = left->generateIR(graph, st); auto rhs_name = right->generateIR(graph, st); diff --git a/src/ast/ArithmeticExpressionNode.hpp b/src/ast/ArithmeticExpressionNode.hpp index 5ceceab..66108b1 100644 --- a/src/ast/ArithmeticExpressionNode.hpp +++ b/src/ast/ArithmeticExpressionNode.hpp @@ -8,15 +8,12 @@ class ArithmeticExpressionNode : public Node { Node *left, *right; public: - ArithmeticExpressionNode(const std::string &t, - std::unique_ptr left_, + ArithmeticExpressionNode(const std::string &t, std::unique_ptr left_, std::unique_ptr right_, int l) : Node(t, l) { left = append_child(std::move(left_)); right = append_child(std::move(right_)); } - - std::string checkTypes(SymbolTable &st) const override; }; class PlusNode : public ArithmeticExpressionNode { diff --git a/src/ast/ArrayAccessNode.cpp b/src/ast/ArrayAccessNode.cpp index 64376af..1300de6 100644 --- a/src/ast/ArrayAccessNode.cpp +++ b/src/ast/ArrayAccessNode.cpp @@ -1,28 +1,6 @@ #include "ast/ArrayAccessNode.hpp" #include "ir/Tac.hpp" -std::string ArrayAccessNode::checkTypes(SymbolTable &st) const { - const auto arrayType = array->checkTypes(st); - const auto indexType = index->checkTypes(st); - - if (indexType != "int") { - std::cerr << "Error: (line " << lineno << ") "; - std::cerr << "Invalid array index type "; - std::cerr << "'" << indexType << "', "; - std::cerr << "expected type 'int'.\n"; - return ""; - } - if (arrayType != "int[]") { - std::cerr << "Error: (line " << lineno << ") "; - std::cerr << "Invalid array type "; - std::cerr << "'" << indexType << "', "; - std::cerr << "expected type 'int[]'.\n"; - return ""; - } - - return "int"; -} - Operand ArrayAccessNode::generateIR(CFG &graph, SymbolTable &st) { auto indexName = index->generateIR(graph, st); auto arrayName = array->value; diff --git a/src/ast/ArrayAccessNode.hpp b/src/ast/ArrayAccessNode.hpp index b7b44b6..1953e12 100644 --- a/src/ast/ArrayAccessNode.hpp +++ b/src/ast/ArrayAccessNode.hpp @@ -13,7 +13,6 @@ class ArrayAccessNode : public Node { array = append_child(std::move(array_)); index = append_child(std::move(index_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/ArrayLengthNode.cpp b/src/ast/ArrayLengthNode.cpp index e491a9a..9659ff2 100644 --- a/src/ast/ArrayLengthNode.cpp +++ b/src/ast/ArrayLengthNode.cpp @@ -1,18 +1,6 @@ #include "ast/ArrayLengthNode.hpp" #include "ir/Tac.hpp" -std::string ArrayLengthNode::checkTypes(SymbolTable &st) const { - const auto arrayType = array->checkTypes(st); - if (arrayType != "int[]") { - std::cerr << "Error: (line " << lineno << ") Invalid type '" - << arrayType - << "' for array length, " - "expected type 'int[]'.\n"; - return ""; - } - return "int"; -} - Operand ArrayLengthNode::generateIR(CFG &graph, SymbolTable &st) { auto name = graph.getTemporaryName(); st.addIntegerVariable(name); diff --git a/src/ast/ArrayLengthNode.hpp b/src/ast/ArrayLengthNode.hpp index 7671711..bd07564 100644 --- a/src/ast/ArrayLengthNode.hpp +++ b/src/ast/ArrayLengthNode.hpp @@ -9,7 +9,7 @@ class ArrayLengthNode : public Node { public: ArrayLengthNode(std::unique_ptr array_, int l) : Node("Array length", l), array(std::move(array_)) {} - std::string checkTypes(SymbolTable &st) const override; + [[nodiscard]] const Node &getArrayNode() const { return *array; } Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/BooleanExpressionNode.cpp b/src/ast/BooleanExpressionNode.cpp index ac84f8c..dd394a9 100644 --- a/src/ast/BooleanExpressionNode.cpp +++ b/src/ast/BooleanExpressionNode.cpp @@ -1,21 +1,6 @@ #include "ast/BooleanExpressionNode.hpp" #include "ir/BooleanTac.hpp" -std::string BooleanExpressionNode::checkTypes(SymbolTable &st) const { - const auto lhsType = left->checkTypes(st); - const auto rhsType = right->checkTypes(st); - - if (lhsType == "boolean" && rhsType == "boolean") { - return "boolean"; - } - - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << type << " operation does not support operands of types "; - std::cerr << lhsType << " and " << rhsType << ".\n"; - return ""; -} - Operand AndNode::generateIR(CFG &graph, SymbolTable &st) { auto lhs_name = left->generateIR(graph, st); auto rhs_name = right->generateIR(graph, st); diff --git a/src/ast/BooleanExpressionNode.hpp b/src/ast/BooleanExpressionNode.hpp index bf7a622..5cd50e9 100644 --- a/src/ast/BooleanExpressionNode.hpp +++ b/src/ast/BooleanExpressionNode.hpp @@ -14,7 +14,6 @@ class BooleanExpressionNode : public Node { left = append_child(std::move(left_)); right = append_child(std::move(right_)); } - std::string checkTypes(SymbolTable &st) const override; }; class AndNode : public BooleanExpressionNode { diff --git a/src/ast/BooleanNode.cpp b/src/ast/BooleanNode.cpp index 26c4bce..3891a09 100644 --- a/src/ast/BooleanNode.cpp +++ b/src/ast/BooleanNode.cpp @@ -1,7 +1,4 @@ #include "ast/BooleanNode.hpp" #include "ir/Tac.hpp" - -std::string TrueNode::checkTypes(SymbolTable &st) const { return "boolean"; } -std::string FalseNode::checkTypes(SymbolTable &st) const { return "boolean"; } Operand TrueNode::generateIR(CFG &graph, SymbolTable &st) { return true; } Operand FalseNode::generateIR(CFG &graph, SymbolTable &st) { return false; } diff --git a/src/ast/BooleanNode.hpp b/src/ast/BooleanNode.hpp index 65b7c62..0a2da69 100644 --- a/src/ast/BooleanNode.hpp +++ b/src/ast/BooleanNode.hpp @@ -6,13 +6,11 @@ class TrueNode : public Node { public: TrueNode(int l) : Node("TRUE", l) {}; - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; class FalseNode : public Node { public: FalseNode(int l) : Node("FALSE", l) {}; - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/ClassAllocationNode.cpp b/src/ast/ClassAllocationNode.cpp index 366dc28..7f7802d 100644 --- a/src/ast/ClassAllocationNode.cpp +++ b/src/ast/ClassAllocationNode.cpp @@ -1,16 +1,6 @@ #include "ast/ClassAllocationNode.hpp" #include "ir/Tac.hpp" -std::string ClassAllocationNode::checkTypes(SymbolTable &st) const { - auto *classLookup = st.lookupClass(id); - if (classLookup == nullptr) { - std::cerr << "Error: (line " << lineno << ") Unknown class '" << id - << "'.\n"; - return ""; - } - return id; -} - Operand ClassAllocationNode::generateIR(CFG &graph, SymbolTable &st) { auto name = graph.getTemporaryName(); st.addVariable(id, name); diff --git a/src/ast/ClassAllocationNode.hpp b/src/ast/ClassAllocationNode.hpp index 1209833..afdf845 100644 --- a/src/ast/ClassAllocationNode.hpp +++ b/src/ast/ClassAllocationNode.hpp @@ -9,7 +9,6 @@ class ClassAllocationNode : public Node { public: ClassAllocationNode(std::unique_ptr object, int l) : Node("Class allocation", object->value, l), id{object->value} {} - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/ClassNode.cpp b/src/ast/ClassNode.cpp index afd7b49..fb7059d 100644 --- a/src/ast/ClassNode.cpp +++ b/src/ast/ClassNode.cpp @@ -4,16 +4,6 @@ void ClassNode::accept(AstVisitor &visitor) const { visitor.visit(*this); } -std::string ClassNode::checkTypes(SymbolTable &st) const { - st.enterClassScope(className); - auto type = body->checkTypes(st); - st.exitScope(); - if (type.empty()) { - return ""; - } - return "void"; -} - Operand ClassNode::generateIR(CFG &graph, SymbolTable &st) { auto *currentClass = st.lookupClass(className); st.enterClassScope(currentClass); diff --git a/src/ast/ClassNode.hpp b/src/ast/ClassNode.hpp index 259da4d..a246ed2 100644 --- a/src/ast/ClassNode.hpp +++ b/src/ast/ClassNode.hpp @@ -19,7 +19,6 @@ class ClassNode : public Node { [[nodiscard]] const std::string &getClassName() const { return className; } [[nodiscard]] const Node &getBodyNode() const { return *body; } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/ControlStatementNode.cpp b/src/ast/ControlStatementNode.cpp index 90000df..101888e 100644 --- a/src/ast/ControlStatementNode.cpp +++ b/src/ast/ControlStatementNode.cpp @@ -1,32 +1,6 @@ #include "ast/ControlStatementNode.hpp" #include "ir/Tac.hpp" -std::string ControlStatementNode::checkTypes(SymbolTable &st) const { - const auto condType = cond->checkTypes(st); - const auto stmtType = stmts->checkTypes(st); - - bool valid = true; - - if (condType.empty()) { - valid = false; - } else if (condType != "boolean") { - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << "Condition for " << type << "-statement of invalid type " - << condType << ".\n"; - valid = false; - } - - if (stmtType.empty()) { - valid = false; - } - - if (!valid) { - return ""; - } - return "void"; -} - Operand IfNode::generateIR(CFG &graph, SymbolTable &st) { auto *trueBlock = graph.newBlock(); auto *joinBlock = graph.newBlock(); diff --git a/src/ast/ControlStatementNode.hpp b/src/ast/ControlStatementNode.hpp index 4d06174..e84fb33 100644 --- a/src/ast/ControlStatementNode.hpp +++ b/src/ast/ControlStatementNode.hpp @@ -13,7 +13,6 @@ class ControlStatementNode : public Node { cond = append_child(std::move(cond_)); stmts = append_child(std::move(stmts_)); } - std::string checkTypes(SymbolTable &st) const override; }; class IfNode : public ControlStatementNode { Node *cond, *stmt; diff --git a/src/ast/IdentifierNode.cpp b/src/ast/IdentifierNode.cpp index c9def8d..caf0aff 100644 --- a/src/ast/IdentifierNode.cpp +++ b/src/ast/IdentifierNode.cpp @@ -1,25 +1,5 @@ #include "ast/IdentifierNode.hpp" -std::string IdentifierNode::checkTypes(SymbolTable &st) const { - auto variableLookup = st.lookupVariable(value); - if (variableLookup) { - return variableLookup->getType(); - } - auto classLookup = st.lookupClass(value); - if (classLookup) { - return classLookup->getType(); - } - auto methodLookup = st.lookupMethod(value); - if (methodLookup) { - return methodLookup->getType(); - } - - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << "Undeclared identifier " << value << ".\n"; - return ""; -} - Operand IdentifierNode::generateIR(CFG &graph, SymbolTable &st) { return value; } diff --git a/src/ast/IdentifierNode.hpp b/src/ast/IdentifierNode.hpp index f5b6e24..afd1e35 100644 --- a/src/ast/IdentifierNode.hpp +++ b/src/ast/IdentifierNode.hpp @@ -11,8 +11,6 @@ class IdentifierNode : public Node { IdentifierNode(const std::string &value_, int l) : Node("Identifier", value_, l), value{value_} {} - std::string checkTypes(SymbolTable &st) const override; - Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/IntegerArrayAllocationNode.cpp b/src/ast/IntegerArrayAllocationNode.cpp index 7171d12..d2c3dfc 100644 --- a/src/ast/IntegerArrayAllocationNode.cpp +++ b/src/ast/IntegerArrayAllocationNode.cpp @@ -1,18 +1,6 @@ #include "ast/IntegerArrayAllocationNode.hpp" #include "ir/Tac.hpp" -std::string IntegerArrayAllocationNode::checkTypes(SymbolTable &st) const { - const auto lengthType = length->checkTypes(st); - if (lengthType != "int") { - std::cerr << "Error: (line " << lineno << ") Invalid type '" - << lengthType - << "' for array length" - ", expected type 'int'.\n"; - return ""; - } - return "int[]"; -} - Operand IntegerArrayAllocationNode::generateIR(CFG &graph, SymbolTable &st) { auto name = graph.getTemporaryName(); st.addVariable("int[]", name); diff --git a/src/ast/IntegerArrayAllocationNode.hpp b/src/ast/IntegerArrayAllocationNode.hpp index 93d7903..1a662f2 100644 --- a/src/ast/IntegerArrayAllocationNode.hpp +++ b/src/ast/IntegerArrayAllocationNode.hpp @@ -9,7 +9,7 @@ class IntegerArrayAllocationNode : public Node { public: IntegerArrayAllocationNode(std::unique_ptr length_, int l) : Node("Integer array allocation", l), length(std::move(length_)) {} - std::string checkTypes(SymbolTable &st) const override; + [[nodiscard]] const Node &getLengthNode() const { return *length; } Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/IntegerNode.cpp b/src/ast/IntegerNode.cpp index 47959d8..fa90831 100644 --- a/src/ast/IntegerNode.cpp +++ b/src/ast/IntegerNode.cpp @@ -1,5 +1,3 @@ #include "ast/IntegerNode.hpp" -std::string IntegerNode::checkTypes(SymbolTable &st) const { return "int"; } - Operand IntegerNode::generateIR(CFG &graph, SymbolTable &st) { return value; } diff --git a/src/ast/IntegerNode.hpp b/src/ast/IntegerNode.hpp index bbbd8bd..81c755b 100644 --- a/src/ast/IntegerNode.hpp +++ b/src/ast/IntegerNode.hpp @@ -13,8 +13,6 @@ class IntegerNode : public Node { IntegerNode(const std::string &value_, int l) : Node("Integer", value_, l), value{std::stoi(value_)} {} - std::string checkTypes(SymbolTable &st) const override; - Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/LogicalExpressionNode.cpp b/src/ast/LogicalExpressionNode.cpp index c54e4e6..5231d8a 100644 --- a/src/ast/LogicalExpressionNode.cpp +++ b/src/ast/LogicalExpressionNode.cpp @@ -1,26 +1,6 @@ #include "ast/LogicalExpressionNode.hpp" #include "ir/LogicalTac.hpp" -std::string LogicalExpressionNode::checkTypes(SymbolTable &st) const { - const auto lhsType = left->checkTypes(st); - const auto rhsType = right->checkTypes(st); - - if (lhsType == "int" && rhsType == "int") { - return "boolean"; - } - - if (!lhsType.empty() && !rhsType.empty()) { - std::cerr << "Error: (line " << lineno << ") " << type << " " - << "operation does not " - "support operands of types '" - << lhsType - << "' and " - "'" - << rhsType << "'.\n"; - } - return ""; -} - Operand LessThanNode::generateIR(CFG &graph, SymbolTable &st) { auto lhs_name = left->generateIR(graph, st); auto rhs_name = right->generateIR(graph, st); @@ -37,21 +17,6 @@ Operand GreaterThanNode::generateIR(CFG &graph, SymbolTable &st) { graph.addInstruction(new GreaterThanTac(name, lhs_name, rhs_name)); return name; } -std::string EqualToNode::checkTypes(SymbolTable &st) const { - const auto lhsType = left->checkTypes(st); - const auto rhsType = right->checkTypes(st); - - if ((lhsType == "boolean" && rhsType == "boolean") || - (lhsType == "int" && rhsType == "int")) { - return "boolean"; - } - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << "Operator '==' does not support operands of types "; - std::cerr << "'" << lhsType << "' and '" << rhsType << "'.\n"; - return ""; -} - Operand EqualToNode::generateIR(CFG &graph, SymbolTable &st) { auto lhs_name = left->generateIR(graph, st); auto rhs_name = right->generateIR(graph, st); diff --git a/src/ast/LogicalExpressionNode.hpp b/src/ast/LogicalExpressionNode.hpp index 039bfb7..89a6b11 100644 --- a/src/ast/LogicalExpressionNode.hpp +++ b/src/ast/LogicalExpressionNode.hpp @@ -14,15 +14,13 @@ class LogicalExpressionNode : public Node { left = append_child(std::move(left_)); right = append_child(std::move(right_)); } - - std::string checkTypes(SymbolTable &st) const override; }; class LessThanNode : public LogicalExpressionNode { public: LessThanNode(std::unique_ptr left, std::unique_ptr right, int l) - : LogicalExpressionNode("Less-than", std::move(left), - std::move(right), l) {} + : LogicalExpressionNode("Less-than", std::move(left), std::move(right), + l) {} Operand generateIR(CFG &graph, SymbolTable &st) override; }; @@ -45,7 +43,6 @@ class EqualToNode : public Node { left = append_child(std::move(left_)); right = append_child(std::move(right_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/MainClassNode.cpp b/src/ast/MainClassNode.cpp index ac10baa..8cddf9f 100644 --- a/src/ast/MainClassNode.cpp +++ b/src/ast/MainClassNode.cpp @@ -4,13 +4,6 @@ void MainClassNode::accept(AstVisitor &visitor) const { visitor.visit(*this); } -std::string MainClassNode::checkTypes(SymbolTable &st) const { - st.enterClassScope(mainClassName); - body->checkTypes(st); - st.exitScope(); - return "void"; -} - Operand MainClassNode::generateIR(CFG &graph, SymbolTable &st) { st.enterClassScope(mainClassName); st.enterMethodScope("main"); diff --git a/src/ast/MainClassNode.hpp b/src/ast/MainClassNode.hpp index 24e8dcb..b2072b5 100644 --- a/src/ast/MainClassNode.hpp +++ b/src/ast/MainClassNode.hpp @@ -28,7 +28,6 @@ class MainClassNode : public Node { } [[nodiscard]] const Node &getBodyNode() const { return *body; } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/MethodBodyNode.cpp b/src/ast/MethodBodyNode.cpp index 975c58b..1f88612 100644 --- a/src/ast/MethodBodyNode.cpp +++ b/src/ast/MethodBodyNode.cpp @@ -1,9 +1,5 @@ #include "ast/MethodBodyNode.hpp" -std::string MethodBodyNode::checkTypes(SymbolTable &st) const { - body->checkTypes(st); - return returnValue->checkTypes(st); -} Operand MethodBodyNode::generateIR(CFG &graph, SymbolTable &st) { body->generateIR(graph, st); const auto &name = returnValue->generateIR(graph, st); @@ -11,9 +7,6 @@ Operand MethodBodyNode::generateIR(CFG &graph, SymbolTable &st) { return name; } -std::string ReturnOnlyMethodBodyNode::checkTypes(SymbolTable &st) const { - return returnValue->checkTypes(st); -} Operand ReturnOnlyMethodBodyNode::generateIR(CFG &graph, SymbolTable &st) { const auto &name = returnValue->generateIR(graph, st); graph.addInstruction(new ReturnTac(name)); diff --git a/src/ast/MethodBodyNode.hpp b/src/ast/MethodBodyNode.hpp index 2962c2c..c281214 100644 --- a/src/ast/MethodBodyNode.hpp +++ b/src/ast/MethodBodyNode.hpp @@ -13,7 +13,6 @@ class MethodBodyNode : public Node { body = append_child(std::move(body_)); returnValue = append_child(std::move(returnValue_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; class ReturnOnlyMethodBodyNode : public Node { @@ -24,7 +23,6 @@ class ReturnOnlyMethodBodyNode : public Node { : Node("Method body", l) { returnValue = append_child(std::move(returnValue_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; #endif diff --git a/src/ast/MethodCallNode.cpp b/src/ast/MethodCallNode.cpp index a7a86c0..abc4e26 100644 --- a/src/ast/MethodCallNode.cpp +++ b/src/ast/MethodCallNode.cpp @@ -1,69 +1,18 @@ #include "ast/MethodCallNode.hpp" -#include "ir/Tac.hpp" -std::string MethodCallNode::checkTypes(SymbolTable &st) const { - const auto caller = object->checkTypes(st); - if (caller.empty()) { - return ""; - } - auto *callingClass = st.lookupClass(caller); - if (!callingClass) { - std::cerr << "Error: (line " << lineno << ") Method '" << id->value - << "' not declared for class '" << caller << "'.\n"; - return ""; - } - - auto *const method = callingClass->lookupMethod(id->value); - if (method == nullptr) { - std::cerr << "Error: (line " << lineno << ") Method '" << id->value - << "' not declared for class '" << callingClass->getID() - << "'.\n"; - return ""; - } +#include - const auto numExpectedArguments = method->getParameterCount(); - const auto numPassedArguments = exprList->children.size(); - - if (numPassedArguments != numExpectedArguments) { - std::cerr << "Error: (line " << lineno << ") Method '" - << method->getID() << "' expects " << numExpectedArguments - << " arguments, " << numPassedArguments - << " arguments given.\n"; - return ""; - } - - const auto ¶ms = method->getParameters(); - const auto &args = exprList->children; - auto paramsIter = params.begin(); - auto argsIter = args.begin(); - - while (paramsIter != params.end() && argsIter != args.end()) { - const auto paramType = (*paramsIter)->getType(); - const auto argType = (*argsIter)->checkTypes(st); - if (argType.empty()) { - return ""; - } - const auto argNumber = 1 + std::distance(args.cbegin(), argsIter); - if (paramType != argType) { - std::cerr << "Error: (line " << lineno << ") Argument " << argNumber - << " of type '" << argType - << "' does not match parameter " << argNumber - << " of type '" << paramType << "'.\n"; - return ""; - } - ++paramsIter; - ++argsIter; - } - return method->getType(); -} +#include "ir/Tac.hpp" Operand MethodCallNode::generateIR(CFG &graph, SymbolTable &st) { - const auto caller = object->checkTypes(st); - if (caller.empty()) { + const auto *caller_type = graph.typeOf(*object); + assert(caller_type != nullptr && + "Type information missing for method call receiver"); + if (caller_type == nullptr || caller_type->empty()) { return ""; } - auto *callingClass = st.lookupClass(caller); + auto *callingClass = st.lookupClass(*caller_type); if (callingClass == nullptr) { return ""; } @@ -85,6 +34,7 @@ Operand MethodCallNode::generateIR(CFG &graph, SymbolTable &st) { const auto &methodName = id->value; const auto argCount = std::to_string(exprList->children.size() + 1); - graph.addInstruction(new MethodCallTac(name, methodName, caller, argCount)); + graph.addInstruction( + new MethodCallTac(name, methodName, *caller_type, argCount)); return name; } diff --git a/src/ast/MethodCallNode.hpp b/src/ast/MethodCallNode.hpp index 82b85ae..c90a133 100644 --- a/src/ast/MethodCallNode.hpp +++ b/src/ast/MethodCallNode.hpp @@ -16,7 +16,6 @@ class MethodCallNode : public Node { id = append_child(std::move(id_)); exprList = append_child(std::move(exprList_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/MethodCallWithoutArgumentsNode.cpp b/src/ast/MethodCallWithoutArgumentsNode.cpp index b95d10f..218e47c 100644 --- a/src/ast/MethodCallWithoutArgumentsNode.cpp +++ b/src/ast/MethodCallWithoutArgumentsNode.cpp @@ -1,45 +1,16 @@ #include "ast/MethodCallWithoutArgumentsNode.hpp" - -std::string MethodCallWithoutArgumentsNode::checkTypes(SymbolTable &st) const { - const auto caller = object->checkTypes(st); - if (caller == "") { - return ""; - } - const auto callingClass = st.lookupClass(caller); - if (!callingClass) { - std::cerr << "Error: (line " << lineno << ") Method '" << id->value - << "' not declared for class '" << caller << "'.\n"; - return ""; - } - - const auto method = callingClass->lookupMethod(id->value); - if (method == nullptr) { - std::cerr << "Error: (line " << lineno << ") Method '" << id->value - << "' not declared for class '" << callingClass->getID() - << "'.\n"; - return ""; - } - - const auto numExpectedArguments = method->getParameterCount(); - if (numExpectedArguments != 0) { - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << "Method '" << method->getID(); - std::cerr << "' expects " << numExpectedArguments << " arguments, "; - std::cerr << "no arguments passed.\n"; - return ""; - } - return method->getType(); -} +#include Operand MethodCallWithoutArgumentsNode::generateIR(CFG &graph, SymbolTable &st) { - const auto caller = object->checkTypes(st); - if (caller.empty()) { + const auto *caller_type = graph.typeOf(*object); + assert(caller_type != nullptr && + "Type information missing for method call receiver"); + if (caller_type == nullptr || caller_type->empty()) { return ""; } - auto *callingClass = st.lookupClass(caller); + auto *callingClass = st.lookupClass(*caller_type); if (callingClass == nullptr) { return ""; } @@ -54,6 +25,7 @@ Operand MethodCallWithoutArgumentsNode::generateIR(CFG &graph, const auto &name = graph.getTemporaryName(); st.addVariable(methodType, name); const auto &methodName = id->value; - graph.addInstruction(new MethodCallTac(name, methodName, caller, "1")); + graph.addInstruction( + new MethodCallTac(name, methodName, *caller_type, "1")); return name; } diff --git a/src/ast/MethodCallWithoutArgumentsNode.hpp b/src/ast/MethodCallWithoutArgumentsNode.hpp index b31da31..c4064d4 100644 --- a/src/ast/MethodCallWithoutArgumentsNode.hpp +++ b/src/ast/MethodCallWithoutArgumentsNode.hpp @@ -12,7 +12,6 @@ class MethodCallWithoutArgumentsNode : public Node { object = append_child(std::move(object_)); id = append_child(std::move(id_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/MethodNode.cpp b/src/ast/MethodNode.cpp index f2825a8..a5cd3ec 100644 --- a/src/ast/MethodNode.cpp +++ b/src/ast/MethodNode.cpp @@ -4,25 +4,6 @@ void MethodNode::accept(AstVisitor &visitor) const { visitor.visit(*this); } -std::string MethodNode::checkTypes(SymbolTable &st) const { - st.enterMethodScope(methodName); - const auto signatureReturnType = type->checkTypes(st); - const auto bodyReturnType = body->checkTypes(st); - st.exitScope(); - - if (signatureReturnType != bodyReturnType) { - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << "Return type '" << signatureReturnType << "' "; - std::cerr << "in method '" << methodName << "' "; - std::cerr << "does not match returned type '"; - std::cerr << bodyReturnType << "'.\n"; - return ""; - } - - return methodType; -} - Operand MethodNode::generateIR(CFG &graph, SymbolTable &st) { auto *currentClass = dynamic_cast(st.getCurrentRecord()); auto *currentMethod = st.lookupMethod(methodName); diff --git a/src/ast/MethodNode.hpp b/src/ast/MethodNode.hpp index 5c5d008..7fc6727 100644 --- a/src/ast/MethodNode.hpp +++ b/src/ast/MethodNode.hpp @@ -22,12 +22,15 @@ class MethodNode : public Node { void accept(AstVisitor &visitor) const override; - [[nodiscard]] const std::string &getMethodName() const { return methodName; } - [[nodiscard]] const std::string &getMethodType() const { return methodType; } + [[nodiscard]] const std::string &getMethodName() const { + return methodName; + } + [[nodiscard]] const std::string &getMethodType() const { + return methodType; + } [[nodiscard]] const Node &getParametersNode() const { return *params; } [[nodiscard]] const Node &getBodyNode() const { return *body; } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/MethodParameterNode.hpp b/src/ast/MethodParameterNode.hpp index 6f6ae01..7d9f15e 100644 --- a/src/ast/MethodParameterNode.hpp +++ b/src/ast/MethodParameterNode.hpp @@ -22,7 +22,6 @@ class MethodParameterNode : public Node { [[nodiscard]] const std::string &getParameterName() const { return id->value; } - }; #endif diff --git a/src/ast/MethodWithoutParametersNode.cpp b/src/ast/MethodWithoutParametersNode.cpp index b518dbf..d01e271 100644 --- a/src/ast/MethodWithoutParametersNode.cpp +++ b/src/ast/MethodWithoutParametersNode.cpp @@ -6,27 +6,6 @@ void MethodWithoutParametersNode::accept(AstVisitor &visitor) const { visitor.visit(*this); } -std::string MethodWithoutParametersNode::checkTypes(SymbolTable &st) const { - st.enterMethodScope(id->value); - const auto signatureReturnType = type->checkTypes(st); - const auto bodyReturnType = body->checkTypes(st); - st.exitScope(); - - if (bodyReturnType.empty()) { - return ""; - } - - if (signatureReturnType != bodyReturnType) { - std::cerr << "Error: (line " << lineno << ") Return type '" - << signatureReturnType << "' in method '" << id->value - << "' does not match returned type '" << bodyReturnType - << "'.\n"; - return ""; - } - - return type->value; -} - Operand MethodWithoutParametersNode::generateIR(CFG &graph, SymbolTable &st) { auto *currentClass = dynamic_cast(st.getCurrentRecord()); st.enterMethodScope(id->value); diff --git a/src/ast/MethodWithoutParametersNode.hpp b/src/ast/MethodWithoutParametersNode.hpp index d90d663..ea81ed7 100644 --- a/src/ast/MethodWithoutParametersNode.hpp +++ b/src/ast/MethodWithoutParametersNode.hpp @@ -19,10 +19,11 @@ class MethodWithoutParametersNode : public Node { void accept(AstVisitor &visitor) const override; [[nodiscard]] const std::string &getMethodName() const { return id->value; } - [[nodiscard]] const std::string &getMethodType() const { return type->value; } + [[nodiscard]] const std::string &getMethodType() const { + return type->value; + } [[nodiscard]] const Node &getBodyNode() const { return *body; } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/Node.cpp b/src/ast/Node.cpp index b58d78f..b4d55e9 100644 --- a/src/ast/Node.cpp +++ b/src/ast/Node.cpp @@ -8,20 +8,6 @@ bool Node::buildTable(SymbolTable &st) const { return build_symbol_table(*this, st).ok(); } -std::string Node::checkTypes(SymbolTable &st) const { - bool valid = true; - for (const auto &child : children) { - auto type = child->checkTypes(st); - if (type.empty()) { - valid = false; - } - } - if (valid) { - return "void"; - } - return ""; -} - Operand Node::generateIR(CFG &graph, SymbolTable &st) { for (auto &child : children) { child->generateIR(graph, st); diff --git a/src/ast/Node.h b/src/ast/Node.h index 0e2b0e9..aa18358 100644 --- a/src/ast/Node.h +++ b/src/ast/Node.h @@ -23,14 +23,13 @@ class Node { Node(const std::string &t, int l) : type(t), lineno(l) {} Node(const std::string &t, const std::string &v, int l) : type(t), value(v), lineno(l) {} - Node(const std::string &t, int l, std::list> children_) + Node(const std::string &t, int l, + std::list> children_) : type(t), lineno(l), children(std::move(children_)) {} virtual ~Node() = default; virtual bool buildTable(SymbolTable &st) const; - virtual std::string checkTypes(SymbolTable &st) const; - virtual Operand generateIR(CFG &graph, SymbolTable &st); virtual void accept(AstVisitor &visitor) const; diff --git a/src/ast/NotNode.cpp b/src/ast/NotNode.cpp index 8139be1..cb42f20 100644 --- a/src/ast/NotNode.cpp +++ b/src/ast/NotNode.cpp @@ -1,18 +1,6 @@ #include "ast/NotNode.hpp" #include "ir/Tac.hpp" -std::string NotNode::checkTypes(SymbolTable &st) const { - const auto exprType = expr->checkTypes(st); - if (exprType == "boolean") { - return "boolean"; - } - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << "Invalid type '" << exprType - << "' for negation operator, expected type 'boolean'.\n"; - return ""; -} - Operand NotNode::generateIR(CFG &graph, SymbolTable &st) { auto rhsName = expr->generateIR(graph, st); auto name = graph.getTemporaryName(); diff --git a/src/ast/NotNode.hpp b/src/ast/NotNode.hpp index 9750742..4928b0a 100644 --- a/src/ast/NotNode.hpp +++ b/src/ast/NotNode.hpp @@ -11,7 +11,6 @@ class NotNode : public Node { : Node("Negated expression", l) { expr = append_child(std::move(expr_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/StatementNode.cpp b/src/ast/StatementNode.cpp index b22f763..a32031e 100644 --- a/src/ast/StatementNode.cpp +++ b/src/ast/StatementNode.cpp @@ -1,19 +1,6 @@ #include "ast/StatementNode.hpp" #include "ir/Tac.hpp" -std::string AssignNode::checkTypes(SymbolTable &st) const { - const auto lhsType = id->checkTypes(st); - const auto rhsType = expr->checkTypes(st); - if (lhsType == rhsType) { - return lhsType; - } - if (lhsType != "" && rhsType != "") { - std::cerr << "Error: (line " << lineno << ") Cannot assign type '" - << rhsType << "' to type '" << lhsType << "'.\n"; - } - return ""; -} - Operand AssignNode::generateIR(CFG &graph, SymbolTable &st) { auto rhsName = expr->generateIR(graph, st); auto lhsName = id->value; @@ -21,38 +8,6 @@ Operand AssignNode::generateIR(CFG &graph, SymbolTable &st) { return lhsName; } -std::string ArrayAssignNode::checkTypes(SymbolTable &st) const { - const auto indexType = indexExpr->checkTypes(st); - - if (indexType != "int") { - std::cerr << "Error: (line " << lineno << ") "; - std::cerr << "Invalid array index type "; - std::cerr << "'" << indexType << "', "; - std::cerr << "expected type 'int'.\n"; - return ""; - } - - const auto lhsType = id->checkTypes(st); - if (lhsType != "int[]") { - std::cerr << "Error: (line " << lineno - << ") Invalid array type " - "'" - << lhsType << "', expected type 'int[]'.\n"; - return ""; - } - - const auto rhsType = rightExpr->checkTypes(st); - if (!rhsType.empty() && rhsType != "int") { - std::cerr << "Error: (line " << lineno - << ") Cannot assign value of type " - "'" - << rhsType << "', to array of type '" << lhsType << "'.\n"; - return ""; - } - - return ""; -} - Operand ArrayAssignNode::generateIR(CFG &graph, SymbolTable &st) { auto indexName = indexExpr->generateIR(graph, st); auto rhsName = rightExpr->generateIR(graph, st); diff --git a/src/ast/StatementNode.hpp b/src/ast/StatementNode.hpp index 620c27b..b55177f 100644 --- a/src/ast/StatementNode.hpp +++ b/src/ast/StatementNode.hpp @@ -13,7 +13,6 @@ class AssignNode : public Node { id = append_child(std::move(id_)); expr = append_child(std::move(expr_)); } - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; @@ -29,7 +28,7 @@ class ArrayAssignNode : public Node { id = append_child(std::move(id_)); indexExpr = append_child(std::move(indexExpr_)); } - std::string checkTypes(SymbolTable &st) const override; + [[nodiscard]] const Node &getRightExprNode() const { return *rightExpr; } Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/ThisNode.cpp b/src/ast/ThisNode.cpp index dd1536e..dd94062 100644 --- a/src/ast/ThisNode.cpp +++ b/src/ast/ThisNode.cpp @@ -1,16 +1,4 @@ #include "ast/ThisNode.hpp" #include "ir/Tac.hpp" -std::string ThisNode::checkTypes(SymbolTable &st) const { - auto const *lookup = st.lookupVariable(type); - if (!lookup) { - std::cerr << "Error: "; - std::cerr << "(line " << lineno << ") "; - std::cerr << "Undeclared identifier (type " << type << ")" << value - << ".\n"; - return ""; - } - return lookup->getType(); -} - Operand ThisNode::generateIR(CFG &graph, SymbolTable &st) { return value; } diff --git a/src/ast/ThisNode.hpp b/src/ast/ThisNode.hpp index 7fca0b5..27c401b 100644 --- a/src/ast/ThisNode.hpp +++ b/src/ast/ThisNode.hpp @@ -8,7 +8,6 @@ class ThisNode : public Node { public: ThisNode(int l) : Node("this", l) {} - std::string checkTypes(SymbolTable &st) const override; Operand generateIR(CFG &graph, SymbolTable &st) override; }; diff --git a/src/ast/VariableNode.cpp b/src/ast/VariableNode.cpp index ea99317..fe4b1f2 100644 --- a/src/ast/VariableNode.cpp +++ b/src/ast/VariableNode.cpp @@ -3,18 +3,3 @@ #include "ast/AstVisitor.hpp" void VariableNode::accept(AstVisitor &visitor) const { visitor.visit(*this); } - -std::string VariableNode::checkTypes(SymbolTable &st) const { - const auto &variableType = type->value; - const bool hasBaseType = - (variableType == "int" || variableType == "int[]" || - variableType == "boolean"); - auto *classLookup = st.lookupClass(variableType); - if (!hasBaseType && !classLookup) { - std::cout << "Error: (line " << lineno << ") " - << "Unknown type '" << variableType << "' " - << "for identifier '" << name->value << "'.\n"; - return ""; - } - return variableType; -} diff --git a/src/ast/VariableNode.hpp b/src/ast/VariableNode.hpp index 4f1e9be..b0ce69a 100644 --- a/src/ast/VariableNode.hpp +++ b/src/ast/VariableNode.hpp @@ -22,8 +22,6 @@ class VariableNode : public Node { [[nodiscard]] const std::string &getVariableName() const { return name->value; } - - std::string checkTypes(SymbolTable &st) const override; }; #endif diff --git a/src/ir/CFG.cpp b/src/ir/CFG.cpp index 446d468..5513d43 100644 --- a/src/ir/CFG.cpp +++ b/src/ir/CFG.cpp @@ -4,6 +4,7 @@ #include #include "ir/CFG.hpp" +#include "semantic/TypeCheckVisitor.hpp" std::string CFG::getTemporaryName() { auto name = "_t" + std::to_string(temporaryIndex); @@ -44,6 +45,13 @@ BBlock *CFG::addMethodRootBlock(const std::string &className, return ptr; } +const std::string *CFG::typeOf(const Node &node) const { + if (type_info_ == nullptr) { + return nullptr; + } + return type_info_->get(node); +} + void CFG::generateBytecode(BytecodeProgram &program, SymbolTable &st) { for (auto *basicBlock : methodBlocks) { const auto &className = basicBlock->getClassName(); @@ -56,7 +64,7 @@ void CFG::generateBytecode(BytecodeProgram &program, SymbolTable &st) { const auto &blockName = basicBlock->getName(); const auto variableNames = methodScope->getVariableNames(); std::vector variables(variableNames.begin(), - variableNames.end()); + variableNames.end()); auto &bytecodeMethod = program.addBytecodeMethod(blockName, std::move(variables)); auto &bytecodeBlock = bytecodeMethod.addBytecodeMethodBlock(blockName); diff --git a/src/ir/CFG.hpp b/src/ir/CFG.hpp index 52060f3..8d08c68 100644 --- a/src/ir/CFG.hpp +++ b/src/ir/CFG.hpp @@ -7,12 +7,16 @@ #include "ir/BBlock.hpp" #include "semantic/SymbolTable.hpp" +class Node; +class TypeInfo; + class CFG { private: BBlock *currentBlock = nullptr; std::vector methodBlocks; int temporaryIndex = 0; int blockIndex = 0; + const TypeInfo *type_info_ = nullptr; public: std::string getTemporaryName(); @@ -30,6 +34,9 @@ class CFG { [[nodiscard]] BBlock *addMethodRootBlock(const std::string &className, const std::string &methodName); + void setTypeInfo(const TypeInfo *info) { type_info_ = info; } + [[nodiscard]] const std::string *typeOf(const Node &node) const; + void generateBytecode(BytecodeProgram &program, SymbolTable &st); }; diff --git a/src/main.cpp b/src/main.cpp index debeace..cb7ddc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ namespace fs = std::filesystem; #include "lexing/StringViewStream.hpp" #include "parsing/Parser.hpp" #include "semantic/SymbolTableVisitor.hpp" +#include "semantic/TypeCheckVisitor.hpp" std::unique_ptr root; int lexical_errors = 0; @@ -282,9 +283,11 @@ int main(int argc, char **argv) { } bool typeCheckSuccess = true; + TypeInfo type_info; if (symbolTableSuccess) { - auto rootType = root->checkTypes(st); - typeCheckSuccess = !rootType.empty(); + const auto type_check_result = + check_types(*root, st, &type_info, &semantic_diag); + typeCheckSuccess = type_check_result.ok(); if (!typeCheckSuccess) { std::cout << "Type checking failed.\n"; } @@ -298,6 +301,7 @@ int main(int argc, char **argv) { generateGraphviz(root.get(), outStream); CFG graph; + graph.setTypeInfo(&type_info); std::ofstream controlFlowGraph(outputDirectory / "cfg.dot"); if (!controlFlowGraph.is_open()) { diff --git a/src/semantic/SymbolTableVisitor.cpp b/src/semantic/SymbolTableVisitor.cpp index 8d522cb..4915038 100644 --- a/src/semantic/SymbolTableVisitor.cpp +++ b/src/semantic/SymbolTableVisitor.cpp @@ -67,8 +67,8 @@ void SymbolTableVisitor::visit(const ClassNode &node) { if (table_.lookupClass(class_name) != nullptr) { emit_error(node.lineno, "Error: (line " + std::to_string(node.lineno) + - ") Class '" + class_name + - "' already declared.\n"); + ") Class '" + class_name + + "' already declared.\n"); return; } @@ -88,10 +88,9 @@ void SymbolTableVisitor::visit(const MainClassNode &node) { const auto &main_class_name = node.getMainClassName(); if (table_.lookupClass(main_class_name) != nullptr) { - emit_error(node.lineno, - "Error: (line " + std::to_string(node.lineno) + - ") Class '" + main_class_name + - "' already declared.\n"); + emit_error(node.lineno, "Error: (line " + std::to_string(node.lineno) + + ") Class '" + main_class_name + + "' already declared.\n"); return; } @@ -122,9 +121,9 @@ void SymbolTableVisitor::visit(const MethodNode &node) { const auto &method_name = node.getMethodName(); if (current_class != nullptr && current_class->lookupMethod(method_name)) { - emit_error(node.lineno, - "Error: (line " + std::to_string(node.lineno) + - ") Method '" + method_name + "' already declared.\n"); + emit_error(node.lineno, "Error: (line " + std::to_string(node.lineno) + + ") Method '" + method_name + + "' already declared.\n"); return; } @@ -147,9 +146,9 @@ void SymbolTableVisitor::visit(const MethodWithoutParametersNode &node) { const auto &method_name = node.getMethodName(); if (current_class != nullptr && current_class->lookupMethod(method_name)) { - emit_error(node.lineno, - "Error: (line " + std::to_string(node.lineno) + - ") Method '" + method_name + "' already declared.\n"); + emit_error(node.lineno, "Error: (line " + std::to_string(node.lineno) + + ") Method '" + method_name + + "' already declared.\n"); return; } @@ -170,10 +169,9 @@ void SymbolTableVisitor::visit(const MethodParameterNode &node) { const auto ¶meter_name = node.getParameterName(); if (table_.lookupVariableInScope(parameter_name) != nullptr) { - emit_error(node.lineno, - "Error: (line " + std::to_string(node.lineno) + - ") Parameter '" + parameter_name + - "' already declared.\n"); + emit_error(node.lineno, "Error: (line " + std::to_string(node.lineno) + + ") Parameter '" + parameter_name + + "' already declared.\n"); return; } @@ -181,9 +179,8 @@ void SymbolTableVisitor::visit(const MethodParameterNode &node) { auto *parameter = table_.lookupVariable(parameter_name); auto *current_scope = table_.getCurrentScope(); - auto *current_method = - dynamic_cast(current_scope != nullptr ? current_scope->getRecord() - : nullptr); + auto *current_method = dynamic_cast( + current_scope != nullptr ? current_scope->getRecord() : nullptr); if (current_method != nullptr) { current_method->addParameter(parameter); } @@ -193,10 +190,9 @@ void SymbolTableVisitor::visit(const VariableNode &node) { const auto &variable_name = node.getVariableName(); if (table_.lookupVariableInScope(variable_name) != nullptr) { - emit_error(node.lineno, - "Error: (line " + std::to_string(node.lineno) + - ") Variable '" + variable_name + - "' already declared.\n"); + emit_error(node.lineno, "Error: (line " + std::to_string(node.lineno) + + ") Variable '" + variable_name + + "' already declared.\n"); return; } diff --git a/src/semantic/TypeCheckVisitor.cpp b/src/semantic/TypeCheckVisitor.cpp new file mode 100644 index 0000000..19e5191 --- /dev/null +++ b/src/semantic/TypeCheckVisitor.cpp @@ -0,0 +1,912 @@ +#include "semantic/TypeCheckVisitor.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ast/ArithmeticExpressionNode.hpp" +#include "ast/ArrayAccessNode.hpp" +#include "ast/ArrayLengthNode.hpp" +#include "ast/BooleanExpressionNode.hpp" +#include "ast/BooleanNode.hpp" +#include "ast/ClassAllocationNode.hpp" +#include "ast/ClassNode.hpp" +#include "ast/ControlStatementNode.hpp" +#include "ast/IdentifierNode.hpp" +#include "ast/IntegerArrayAllocationNode.hpp" +#include "ast/IntegerNode.hpp" +#include "ast/LogicalExpressionNode.hpp" +#include "ast/MainClassNode.hpp" +#include "ast/MethodBodyNode.hpp" +#include "ast/MethodCallNode.hpp" +#include "ast/MethodCallWithoutArgumentsNode.hpp" +#include "ast/MethodNode.hpp" +#include "ast/MethodParameterNode.hpp" +#include "ast/MethodWithoutParametersNode.hpp" +#include "ast/Node.h" +#include "ast/NotNode.hpp" +#include "ast/StatementNode.hpp" +#include "ast/ThisNode.hpp" +#include "ast/VariableNode.hpp" +#include "semantic/Class.hpp" +#include "semantic/Method.hpp" +#include "semantic/SymbolTable.hpp" +#include "semantic/TypeNode.hpp" + +namespace { + +constexpr std::string_view kErrorType = ""; + +[[nodiscard]] bool is_error_type(std::string_view type_name) { + return type_name == kErrorType; +} + +[[nodiscard]] std::string error_type() { return std::string{kErrorType}; } + +[[nodiscard]] bool is_builtin_type(std::string_view type_name) { + return type_name == "int" || type_name == "boolean" || type_name == "int[]"; +} + +[[nodiscard]] const Node *child_at(const Node &node, std::size_t index) { + if (index >= node.children.size()) { + return nullptr; + } + auto it = node.children.cbegin(); + std::advance(it, static_cast(index)); + return it->get(); +} + +class ScopeExit { + public: + explicit ScopeExit(SymbolTable &table) : table_(&table) {} + + ~ScopeExit() { + if (table_ != nullptr) { + table_->exitScope(); + } + } + + ScopeExit(const ScopeExit &) = delete; + ScopeExit &operator=(const ScopeExit &) = delete; + + private: + SymbolTable *table_ = nullptr; +}; + +class TypeCheckVisitor { + public: + TypeCheckVisitor(SymbolTable &table, TypeInfo &type_info, + lexing::DiagnosticSink *sink) + : table_(table), type_info_(type_info), sink_(sink) {} + + [[nodiscard]] TypeCheckResult run(const Node &root) { + (void)visit(root); + return {.error_count = error_count_}; + } + + private: + SymbolTable &table_; + TypeInfo &type_info_; + lexing::DiagnosticSink *sink_ = nullptr; + int error_count_ = 0; + + [[nodiscard]] std::string remember(const Node &node, + std::string inferred_type) { + type_info_.set(node, inferred_type); + return inferred_type; + } + + void emit_error(int line, std::string message) { + error_count_ += 1; + + if (sink_ == nullptr) { + return; + } + + const auto line_no = static_cast(std::max(line, 1)); + const lexing::SourceSpan span{ + .begin = {.offset = 0, .line = line_no, .column = 1}, + .end = {.offset = 0, .line = line_no, .column = 1}, + }; + + sink_->emit({.severity = lexing::Severity::Error, + .message = std::move(message), + .span = span}); + } + + [[nodiscard]] std::string visit(const ClassNode &node) { + table_.enterClassScope(node.getClassName()); + ScopeExit exit_scope(table_); + + const auto body_type = visit(node.getBodyNode()); + if (is_error_type(body_type)) { + return remember(node, error_type()); + } + return remember(node, "void"); + } + + [[nodiscard]] std::string visit(const MainClassNode &node) { + table_.enterClassScope(node.getMainClassName()); + ScopeExit exit_class_scope(table_); + + table_.enterMethodScope("main"); + ScopeExit exit_method_scope(table_); + + const auto body_type = visit(node.getBodyNode()); + if (is_error_type(body_type)) { + return remember(node, error_type()); + } + return remember(node, "void"); + } + + [[nodiscard]] std::string visit(const MethodNode &node) { + table_.enterMethodScope(node.getMethodName()); + ScopeExit exit_scope(table_); + + const auto params_type = visit(node.getParametersNode()); + const auto body_return_type = visit(node.getBodyNode()); + + bool valid = true; + if (is_error_type(params_type) || is_error_type(body_return_type)) { + valid = false; + } + + const auto &signature_return_type = node.getMethodType(); + if (valid && signature_return_type != body_return_type) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Return type '" + signature_return_type + + "' in method '" + node.getMethodName() + + "' does not match returned type '" + + body_return_type + "'.\n"); + valid = false; + } + + if (!valid) { + return remember(node, error_type()); + } + return remember(node, signature_return_type); + } + + [[nodiscard]] std::string visit(const MethodWithoutParametersNode &node) { + table_.enterMethodScope(node.getMethodName()); + ScopeExit exit_scope(table_); + + const auto body_return_type = visit(node.getBodyNode()); + bool valid = !is_error_type(body_return_type); + + const auto &signature_return_type = node.getMethodType(); + if (valid && signature_return_type != body_return_type) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Return type '" + signature_return_type + + "' in method '" + node.getMethodName() + + "' does not match returned type '" + + body_return_type + "'.\n"); + valid = false; + } + + if (!valid) { + return remember(node, error_type()); + } + return remember(node, signature_return_type); + } + + [[nodiscard]] std::string visit(const MethodParameterNode &node) { + return remember(node, node.getParameterType()); + } + + [[nodiscard]] std::string visit(const VariableNode &node) { + const auto &variable_type = node.getVariableType(); + if (!is_builtin_type(variable_type) && + table_.lookupClass(variable_type) == nullptr) { + emit_error(node.lineno, "Error: (line " + + std::to_string(node.lineno) + + ") Unknown type '" + variable_type + + "' for identifier '" + + node.getVariableName() + "'.\n"); + return remember(node, error_type()); + } + + return remember(node, variable_type); + } + + [[nodiscard]] std::string visit_type_node(const TypeNode &node) { + return remember(node, static_cast(node).value); + } + + [[nodiscard]] std::string visit_integer_node(const IntegerNode &node) { + return remember(node, "int"); + } + + [[nodiscard]] std::string visit_boolean_node(const Node &node) { + return remember(node, "boolean"); + } + + [[nodiscard]] std::string + visit_identifier_node(const IdentifierNode &node) { + const auto &identifier = static_cast(node).value; + if (auto *variable = table_.lookupVariable(identifier); + variable != nullptr) { + return remember(node, variable->getType()); + } + if (auto *klass = table_.lookupClass(identifier); klass != nullptr) { + return remember(node, klass->getType()); + } + if (auto *method = table_.lookupMethod(identifier); method != nullptr) { + return remember(node, method->getType()); + } + + emit_error(node.lineno, "Error: (line " + std::to_string(node.lineno) + + ") Undeclared identifier " + identifier + + ".\n"); + return remember(node, error_type()); + } + + [[nodiscard]] std::string visit_this_node(const ThisNode &node) { + auto *lookup = table_.lookupVariable("this"); + if (lookup == nullptr) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Undeclared identifier (type this)this.\n"); + return remember(node, error_type()); + } + + return remember(node, lookup->getType()); + } + + [[nodiscard]] std::string visit_arithmetic_expression(const Node &node) { + const auto *lhs = child_at(node, 0); + const auto *rhs = child_at(node, 1); + if (lhs == nullptr || rhs == nullptr) { + return remember(node, error_type()); + } + + const auto lhs_type = visit(*lhs); + const auto rhs_type = visit(*rhs); + + if (!is_error_type(lhs_type) && !is_error_type(rhs_type) && + lhs_type == "int" && rhs_type == "int") { + return remember(node, "int"); + } + + if (!is_error_type(lhs_type) && !is_error_type(rhs_type)) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + ") " + + node.type + + " operation does not support operands of types '" + + lhs_type + "' and '" + rhs_type + "'.\n"); + } + + return remember(node, error_type()); + } + + [[nodiscard]] std::string visit_boolean_expression(const Node &node) { + const auto *lhs = child_at(node, 0); + const auto *rhs = child_at(node, 1); + if (lhs == nullptr || rhs == nullptr) { + return remember(node, error_type()); + } + + const auto lhs_type = visit(*lhs); + const auto rhs_type = visit(*rhs); + + if (!is_error_type(lhs_type) && !is_error_type(rhs_type) && + lhs_type == "boolean" && rhs_type == "boolean") { + return remember(node, "boolean"); + } + + if (!is_error_type(lhs_type) && !is_error_type(rhs_type)) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + ") " + + node.type + + " operation does not support operands of types " + + lhs_type + " and " + rhs_type + ".\n"); + } + + return remember(node, error_type()); + } + + [[nodiscard]] std::string visit_logical_expression(const Node &node) { + const auto *lhs = child_at(node, 0); + const auto *rhs = child_at(node, 1); + if (lhs == nullptr || rhs == nullptr) { + return remember(node, error_type()); + } + + const auto lhs_type = visit(*lhs); + const auto rhs_type = visit(*rhs); + + if (!is_error_type(lhs_type) && !is_error_type(rhs_type) && + lhs_type == "int" && rhs_type == "int") { + return remember(node, "boolean"); + } + + if (!is_error_type(lhs_type) && !is_error_type(rhs_type)) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + ") " + + node.type + + " operation does not support operands of types '" + + lhs_type + "' and '" + rhs_type + "'.\n"); + } + + return remember(node, error_type()); + } + + [[nodiscard]] std::string + visit_equal_to_expression(const EqualToNode &node) { + const auto *lhs = child_at(node, 0); + const auto *rhs = child_at(node, 1); + if (lhs == nullptr || rhs == nullptr) { + return remember(node, error_type()); + } + + const auto lhs_type = visit(*lhs); + const auto rhs_type = visit(*rhs); + + const bool same_boolean = + lhs_type == "boolean" && rhs_type == "boolean"; + const bool same_integer = lhs_type == "int" && rhs_type == "int"; + if (!is_error_type(lhs_type) && !is_error_type(rhs_type) && + (same_boolean || same_integer)) { + return remember(node, "boolean"); + } + + if (!is_error_type(lhs_type) && !is_error_type(rhs_type)) { + emit_error( + node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Operator '==' does not support operands of types '" + + lhs_type + "' and '" + rhs_type + "'.\n"); + } + + return remember(node, error_type()); + } + + [[nodiscard]] std::string visit_not_expression(const NotNode &node) { + const auto *expr = child_at(node, 0); + if (expr == nullptr) { + return remember(node, error_type()); + } + + const auto expr_type = visit(*expr); + if (!is_error_type(expr_type) && expr_type == "boolean") { + return remember(node, "boolean"); + } + + if (!is_error_type(expr_type)) { + emit_error( + node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Invalid type '" + expr_type + + "' for negation operator, expected type 'boolean'.\n"); + } + + return remember(node, error_type()); + } + + [[nodiscard]] std::string visit_array_access(const ArrayAccessNode &node) { + const auto *array = child_at(node, 0); + const auto *index = child_at(node, 1); + if (array == nullptr || index == nullptr) { + return remember(node, error_type()); + } + + const auto array_type = visit(*array); + const auto index_type = visit(*index); + + if (is_error_type(index_type) || is_error_type(array_type)) { + return remember(node, error_type()); + } + + if (index_type != "int") { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Invalid array index type '" + index_type + + "', expected type 'int'.\n"); + return remember(node, error_type()); + } + + if (array_type != "int[]") { + emit_error(node.lineno, "Error: (line " + + std::to_string(node.lineno) + + ") Invalid array type '" + array_type + + "', expected type 'int[]'.\n"); + return remember(node, error_type()); + } + + return remember(node, "int"); + } + + [[nodiscard]] std::string visit_array_length(const ArrayLengthNode &node) { + const auto array_type = visit(node.getArrayNode()); + if (!is_error_type(array_type) && array_type == "int[]") { + return remember(node, "int"); + } + + if (!is_error_type(array_type)) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Invalid type '" + array_type + + "' for array length, expected type 'int[]'.\n"); + } + + return remember(node, error_type()); + } + + [[nodiscard]] std::string + visit_integer_array_allocation(const IntegerArrayAllocationNode &node) { + const auto length_type = visit(node.getLengthNode()); + if (!is_error_type(length_type) && length_type == "int") { + return remember(node, "int[]"); + } + + if (!is_error_type(length_type)) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Invalid type '" + length_type + + "' for array length, expected type 'int'.\n"); + } + + return remember(node, error_type()); + } + + [[nodiscard]] std::string + visit_class_allocation(const ClassAllocationNode &node) { + if (table_.lookupClass(node.value) == nullptr) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Unknown class '" + node.value + "'.\n"); + return remember(node, error_type()); + } + + return remember(node, node.value); + } + + [[nodiscard]] std::string visit_method_call(const MethodCallNode &node) { + const auto *object = child_at(node, 0); + const auto *method_identifier = child_at(node, 1); + const auto *expr_list = child_at(node, 2); + if (object == nullptr || method_identifier == nullptr || + expr_list == nullptr) { + return remember(node, error_type()); + } + + const auto caller_type = visit(*object); + if (is_error_type(caller_type)) { + return remember(node, error_type()); + } + + auto *calling_class = table_.lookupClass(caller_type); + const auto &method_name = method_identifier->value; + if (calling_class == nullptr) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Method '" + method_name + + "' not declared for class '" + caller_type + "'.\n"); + return remember(node, error_type()); + } + + auto *method = calling_class->lookupMethod(method_name); + if (method == nullptr) { + emit_error(node.lineno, "Error: (line " + + std::to_string(node.lineno) + + ") Method '" + method_name + + "' not declared for class '" + + calling_class->getID() + "'.\n"); + return remember(node, error_type()); + } + + bool valid = true; + std::vector argument_types; + argument_types.reserve(expr_list->children.size()); + for (const auto &arg : expr_list->children) { + argument_types.push_back(visit(*arg)); + } + + const auto expected_arguments = method->getParameterCount(); + const auto passed_arguments = argument_types.size(); + if (expected_arguments != passed_arguments) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Method '" + method->getID() + "' expects " + + std::to_string(expected_arguments) + " arguments, " + + std::to_string(passed_arguments) + + " arguments given.\n"); + valid = false; + } + + const auto ¶ms = method->getParameters(); + const auto comparable_count = + std::min(params.size(), argument_types.size()); + for (std::size_t i = 0; i < comparable_count; ++i) { + const auto &arg_type = argument_types[i]; + if (is_error_type(arg_type)) { + valid = false; + continue; + } + + const auto ¶m_type = params[i]->getType(); + if (param_type != arg_type) { + const auto arg_number = i + 1; + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Argument " + std::to_string(arg_number) + + " of type '" + arg_type + + "' does not match parameter " + + std::to_string(arg_number) + " of type '" + + param_type + "'.\n"); + valid = false; + } + } + + if (!valid) { + return remember(node, error_type()); + } + return remember(node, method->getType()); + } + + [[nodiscard]] std::string visit_method_call_without_arguments( + const MethodCallWithoutArgumentsNode &node) { + const auto *object = child_at(node, 0); + const auto *method_identifier = child_at(node, 1); + if (object == nullptr || method_identifier == nullptr) { + return remember(node, error_type()); + } + + const auto caller_type = visit(*object); + if (is_error_type(caller_type)) { + return remember(node, error_type()); + } + + auto *calling_class = table_.lookupClass(caller_type); + const auto &method_name = method_identifier->value; + if (calling_class == nullptr) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Method '" + method_name + + "' not declared for class '" + caller_type + "'.\n"); + return remember(node, error_type()); + } + + auto *method = calling_class->lookupMethod(method_name); + if (method == nullptr) { + emit_error(node.lineno, "Error: (line " + + std::to_string(node.lineno) + + ") Method '" + method_name + + "' not declared for class '" + + calling_class->getID() + "'.\n"); + return remember(node, error_type()); + } + + const auto expected_arguments = method->getParameterCount(); + if (expected_arguments != 0) { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Method '" + method->getID() + "' expects " + + std::to_string(expected_arguments) + + " arguments, no arguments passed.\n"); + return remember(node, error_type()); + } + + return remember(node, method->getType()); + } + + [[nodiscard]] std::string visit_assign_statement(const AssignNode &node) { + const auto *lhs = child_at(node, 0); + const auto *rhs = child_at(node, 1); + if (lhs == nullptr || rhs == nullptr) { + return remember(node, error_type()); + } + + const auto lhs_type = visit(*lhs); + const auto rhs_type = visit(*rhs); + + bool valid = true; + if (is_error_type(lhs_type) || is_error_type(rhs_type)) { + valid = false; + } else if (lhs_type != rhs_type) { + emit_error(node.lineno, "Error: (line " + + std::to_string(node.lineno) + + ") Cannot assign type '" + rhs_type + + "' to type '" + lhs_type + "'.\n"); + valid = false; + } + + if (!valid) { + return remember(node, error_type()); + } + return remember(node, "void"); + } + + [[nodiscard]] std::string + visit_array_assign_statement(const ArrayAssignNode &node) { + const auto *lhs = child_at(node, 0); + const auto *index = child_at(node, 1); + if (lhs == nullptr || index == nullptr) { + return remember(node, error_type()); + } + + const auto index_type = visit(*index); + const auto lhs_type = visit(*lhs); + const auto rhs_type = visit(node.getRightExprNode()); + + bool valid = true; + if (!is_error_type(index_type) && index_type != "int") { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Invalid array index type '" + index_type + + "', expected type 'int'.\n"); + valid = false; + } else if (is_error_type(index_type)) { + valid = false; + } + + if (!is_error_type(lhs_type) && lhs_type != "int[]") { + emit_error(node.lineno, "Error: (line " + + std::to_string(node.lineno) + + ") Invalid array type '" + lhs_type + + "', expected type 'int[]'.\n"); + valid = false; + } else if (is_error_type(lhs_type)) { + valid = false; + } + + if (!is_error_type(rhs_type) && rhs_type != "int") { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Cannot assign value of type '" + rhs_type + + "', to array of type '" + lhs_type + "'.\n"); + valid = false; + } else if (is_error_type(rhs_type)) { + valid = false; + } + + if (!valid) { + return remember(node, error_type()); + } + return remember(node, "void"); + } + + [[nodiscard]] std::string visit_print_statement(const PrintNode &node) { + const auto *expr = child_at(node, 0); + if (expr == nullptr) { + return remember(node, error_type()); + } + + const auto expr_type = visit(*expr); + if (is_error_type(expr_type)) { + return remember(node, error_type()); + } + return remember(node, "void"); + } + + [[nodiscard]] std::string + visit_control_statement(const ControlStatementNode &node) { + const auto *cond = child_at(node, 0); + if (cond == nullptr) { + return remember(node, error_type()); + } + + bool valid = true; + const auto cond_type = visit(*cond); + if (is_error_type(cond_type)) { + valid = false; + } else if (cond_type != "boolean") { + emit_error(node.lineno, + "Error: (line " + std::to_string(node.lineno) + + ") Condition for " + node.type + + "-statement of invalid type " + cond_type + ".\n"); + valid = false; + } + + std::size_t index = 1; + while (const auto *statement = child_at(node, index)) { + const auto stmt_type = visit(*statement); + if (is_error_type(stmt_type)) { + valid = false; + } + index += 1; + } + + if (!valid) { + return remember(node, error_type()); + } + return remember(node, "void"); + } + + [[nodiscard]] std::string visit_method_body(const MethodBodyNode &node) { + const auto *body = child_at(node, 0); + const auto *return_value = child_at(node, 1); + if (body == nullptr || return_value == nullptr) { + return remember(node, error_type()); + } + + const auto body_type = visit(*body); + const auto return_type = visit(*return_value); + if (is_error_type(body_type) || is_error_type(return_type)) { + return remember(node, error_type()); + } + + return remember(node, return_type); + } + + [[nodiscard]] std::string + visit_return_only_method_body(const ReturnOnlyMethodBodyNode &node) { + const auto *return_value = child_at(node, 0); + if (return_value == nullptr) { + return remember(node, error_type()); + } + + const auto return_type = visit(*return_value); + if (is_error_type(return_type)) { + return remember(node, error_type()); + } + + return remember(node, return_type); + } + + [[nodiscard]] std::string visit_generic_node(const Node &node) { + bool valid = true; + for (const auto &child : node.children) { + const auto child_type = visit(*child); + if (is_error_type(child_type)) { + valid = false; + } + } + + if (!valid) { + return remember(node, error_type()); + } + return remember(node, "void"); + } + + [[nodiscard]] std::string visit(const Node &node) { + if (const auto *class_node = dynamic_cast(&node)) { + return visit(*class_node); + } + if (const auto *main_class_node = + dynamic_cast(&node)) { + return visit(*main_class_node); + } + if (const auto *method_node = dynamic_cast(&node)) { + return visit(*method_node); + } + if (const auto *method_without_parameters_node = + dynamic_cast(&node)) { + return visit(*method_without_parameters_node); + } + if (const auto *method_parameter_node = + dynamic_cast(&node)) { + return visit(*method_parameter_node); + } + if (const auto *variable_node = + dynamic_cast(&node)) { + return visit(*variable_node); + } + + if (const auto *type_node = dynamic_cast(&node)) { + return visit_type_node(*type_node); + } + if (const auto *integer_node = + dynamic_cast(&node)) { + return visit_integer_node(*integer_node); + } + if (dynamic_cast(&node) != nullptr || + dynamic_cast(&node) != nullptr) { + return visit_boolean_node(node); + } + if (const auto *identifier_node = + dynamic_cast(&node)) { + return visit_identifier_node(*identifier_node); + } + if (const auto *this_node = dynamic_cast(&node)) { + return visit_this_node(*this_node); + } + + if (dynamic_cast(&node) != nullptr) { + return visit_arithmetic_expression(node); + } + if (dynamic_cast(&node) != nullptr) { + return visit_boolean_expression(node); + } + if (dynamic_cast(&node) != nullptr) { + return visit_logical_expression(node); + } + if (const auto *equal_to_node = + dynamic_cast(&node)) { + return visit_equal_to_expression(*equal_to_node); + } + if (const auto *not_node = dynamic_cast(&node)) { + return visit_not_expression(*not_node); + } + + if (const auto *array_access_node = + dynamic_cast(&node)) { + return visit_array_access(*array_access_node); + } + if (const auto *array_length_node = + dynamic_cast(&node)) { + return visit_array_length(*array_length_node); + } + if (const auto *integer_array_allocation_node = + dynamic_cast(&node)) { + return visit_integer_array_allocation( + *integer_array_allocation_node); + } + if (const auto *class_allocation_node = + dynamic_cast(&node)) { + return visit_class_allocation(*class_allocation_node); + } + + if (const auto *method_call_node = + dynamic_cast(&node)) { + return visit_method_call(*method_call_node); + } + if (const auto *method_call_without_arguments_node = + dynamic_cast(&node)) { + return visit_method_call_without_arguments( + *method_call_without_arguments_node); + } + + if (const auto *assign_node = dynamic_cast(&node)) { + return visit_assign_statement(*assign_node); + } + if (const auto *array_assign_node = + dynamic_cast(&node)) { + return visit_array_assign_statement(*array_assign_node); + } + if (const auto *print_node = dynamic_cast(&node)) { + return visit_print_statement(*print_node); + } + if (const auto *control_statement_node = + dynamic_cast(&node)) { + return visit_control_statement(*control_statement_node); + } + + if (const auto *method_body_node = + dynamic_cast(&node)) { + return visit_method_body(*method_body_node); + } + if (const auto *return_only_method_body_node = + dynamic_cast(&node)) { + return visit_return_only_method_body(*return_only_method_body_node); + } + + return visit_generic_node(node); + } +}; + +} // namespace + +void TypeInfo::set(const Node &node, std::string type_name) { + map_[&node] = std::move(type_name); +} + +const std::string *TypeInfo::get(const Node &node) const { + if (const auto it = map_.find(&node); it != map_.end()) { + return &it->second; + } + return nullptr; +} + +TypeCheckResult check_types(const Node &root, SymbolTable &table, + TypeInfo *type_info, lexing::DiagnosticSink *sink) { + assert(type_info != nullptr); + if (type_info == nullptr) { + return {.error_count = 1}; + } + *type_info = TypeInfo{}; + + while (table.getParentScope() != nullptr) { + table.exitScope(); + } + + TypeCheckVisitor visitor(table, *type_info, sink); + return visitor.run(root); +} diff --git a/src/semantic/TypeCheckVisitor.hpp b/src/semantic/TypeCheckVisitor.hpp new file mode 100644 index 0000000..7988972 --- /dev/null +++ b/src/semantic/TypeCheckVisitor.hpp @@ -0,0 +1,31 @@ +#ifndef TYPE_CHECK_VISITOR_HPP +#define TYPE_CHECK_VISITOR_HPP + +#include +#include + +#include "lexing/Diagnostics.hpp" + +class Node; +class SymbolTable; + +struct TypeCheckResult { + int error_count = 0; + + [[nodiscard]] bool ok() const { return error_count == 0; } +}; + +class TypeInfo { + public: + void set(const Node &node, std::string type_name); + [[nodiscard]] const std::string *get(const Node &node) const; + + private: + std::unordered_map map_; +}; + +TypeCheckResult check_types(const Node &root, SymbolTable &table, + TypeInfo *type_info, + lexing::DiagnosticSink *sink = nullptr); + +#endif diff --git a/src/semantic/TypeNode.cpp b/src/semantic/TypeNode.cpp index 31dda41..6de41c0 100644 --- a/src/semantic/TypeNode.cpp +++ b/src/semantic/TypeNode.cpp @@ -1,3 +1 @@ #include "semantic/TypeNode.hpp" - -std::string TypeNode::checkTypes(SymbolTable &st) const { return value; } diff --git a/src/semantic/TypeNode.hpp b/src/semantic/TypeNode.hpp index 940b4d0..a8c40fb 100644 --- a/src/semantic/TypeNode.hpp +++ b/src/semantic/TypeNode.hpp @@ -9,8 +9,6 @@ class TypeNode : public Node { public: TypeNode(const std::string &value_, int l) : Node("Type", value_, l), value{value_} {} - - std::string checkTypes(SymbolTable &st) const override; }; #endif diff --git a/tests/symbol_table_test.cpp b/tests/symbol_table_test.cpp index 45c4111..186d814 100644 --- a/tests/symbol_table_test.cpp +++ b/tests/symbol_table_test.cpp @@ -23,6 +23,7 @@ #include "semantic/Scope.hpp" #include "semantic/SymbolTable.hpp" #include "semantic/SymbolTableVisitor.hpp" +#include "semantic/TypeCheckVisitor.hpp" #include "semantic/TypeNode.hpp" namespace { @@ -90,16 +91,6 @@ const Scope *find_child_scope(const Scope *parent, std::string_view name) { return nullptr; } -class FailingTypeNode final : public Node { - public: - explicit FailingTypeNode(int line) : Node("Failing type node", line) {} - - std::string checkTypes(SymbolTable &st) const override { - (void)st; - return ""; - } -}; - [[maybe_unused]] constexpr std::string_view kGoldenProgram2Source = R"(public class Main { public static void main(String[] args) { @@ -481,8 +472,12 @@ TEST(SymbolTable, GoldenProgram2) { EXPECT_EQ(max_params[1]->getID(), "b"); EXPECT_EQ(max_params[1]->getType(), "int"); - const auto root_type = root->checkTypes(st); - EXPECT_EQ(root_type, "void"); + TypeInfo type_info; + CollectingDiagnosticSink type_diag; + const auto type_result = check_types(*root, st, &type_info, &type_diag); + EXPECT_TRUE(type_result.ok()); + EXPECT_EQ(type_result.error_count, 0); + EXPECT_EQ(count_error_diagnostics(type_diag.diagnostics), 0); } TEST(SymbolTable, DuplicateClassReportsDiagnostic) { @@ -655,32 +650,92 @@ class Foo { } TEST(SymbolTable, ControlStatementPropagatesStatementTypeFailure) { - SymbolTable st; + constexpr std::string_view source = R"(public class Main { + public static void main(String[] args) { + System.out.println(0); + } +} + +class Foo { + public int bar() { + int x; + if (true) { + x = false; + } + return x; + } +} +)"; - IfNode if_node(std::make_unique(1), - std::make_unique(1), 1); + auto root = parse_program(source); + ASSERT_NE(root, nullptr); + + SymbolTable st; + CollectingDiagnosticSink semantic_diag; + const auto symbol_result = build_symbol_table(*root, st, &semantic_diag); + ASSERT_TRUE(symbol_result.ok()); - EXPECT_EQ(if_node.checkTypes(st), ""); + TypeInfo type_info; + CollectingDiagnosticSink type_diag; + const auto type_result = check_types(*root, st, &type_info, &type_diag); + EXPECT_FALSE(type_result.ok()); + EXPECT_GE(count_error_diagnostics(type_diag.diagnostics), 1); } TEST(SymbolTable, MethodWithoutParametersReturnMismatchFailsTypeCheck) { - SymbolTable st; + constexpr std::string_view source = R"(public class Main { + public static void main(String[] args) { + System.out.println(0); + } +} + +class Foo { + public int bar() { + return true; + } +} +)"; + + auto root = parse_program(source); + ASSERT_NE(root, nullptr); - MethodWithoutParametersNode method( - std::make_unique("int", 1), - std::make_unique("foo", 1), - std::make_unique( - std::make_unique(1), 1), - 1); + SymbolTable st; + CollectingDiagnosticSink semantic_diag; + const auto symbol_result = build_symbol_table(*root, st, &semantic_diag); + ASSERT_TRUE(symbol_result.ok()); - EXPECT_EQ(method.checkTypes(st), ""); + TypeInfo type_info; + CollectingDiagnosticSink type_diag; + const auto type_result = check_types(*root, st, &type_info, &type_diag); + EXPECT_FALSE(type_result.ok()); + EXPECT_GE(count_error_diagnostics(type_diag.diagnostics), 1); } TEST(SymbolTable, ClassAllocationRequiresDeclaredClassType) { - SymbolTable st; + constexpr std::string_view source = R"(public class Main { + public static void main(String[] args) { + System.out.println(0); + } +} + +class Foo { + public MissingClass bar() { + return new MissingClass(); + } +} +)"; + + auto root = parse_program(source); + ASSERT_NE(root, nullptr); - ClassAllocationNode allocation( - std::make_unique("MissingClass", 1), 1); + SymbolTable st; + CollectingDiagnosticSink semantic_diag; + const auto symbol_result = build_symbol_table(*root, st, &semantic_diag); + ASSERT_TRUE(symbol_result.ok()); - EXPECT_EQ(allocation.checkTypes(st), ""); + TypeInfo type_info; + CollectingDiagnosticSink type_diag; + const auto type_result = check_types(*root, st, &type_info, &type_diag); + EXPECT_FALSE(type_result.ok()); + EXPECT_GE(count_error_diagnostics(type_diag.diagnostics), 1); }