diff --git a/src/typechecker/type.hpp b/src/typechecker/type.hpp index dca778b..282ecde 100644 --- a/src/typechecker/type.hpp +++ b/src/typechecker/type.hpp @@ -137,8 +137,8 @@ namespace Solstice { }; struct Function { - std::unordered_map, Type> signatureToReturnType; - + PossibleType returnTypes; + std::vector argumentTypes; Function() = default; }; diff --git a/src/typechecker/typechecker.cpp b/src/typechecker/typechecker.cpp index 0bc65a5..56d1d5f 100644 --- a/src/typechecker/typechecker.cpp +++ b/src/typechecker/typechecker.cpp @@ -142,12 +142,16 @@ namespace Solstice { } checkNodeType(node.children[1]); - auto nodeType = node.children[1].ptype.getOnlyType(); - if (!nodeType.has_value()) { - throw std::runtime_error("cannot assign ambiguous type to name"); + if (node.children[1].ptype.possiblities.empty() && !node.children[1].ptype.unknownType) { + throw std::runtime_error("cannot assign name with no possible types"); } - variables[*id] = {{*nodeType}}; + variables[*id] = node.children[1].ptype; variables[*id].isConstant = true; + // if not narrowed down to a single type yet, keep it open so later + // usages of this name can continue narrowing it + if (!variables[*id].getOnlyType().has_value()) { + variables[*id].unknownType = true; + } } void TypeChecker::checkFunctionBindNodeType(Node& node) { @@ -162,11 +166,18 @@ namespace Solstice { // create sub-checker TypeChecker typeChecker(*this); typeChecker.setNode(node.children[2]); + typeChecker.inFunction = true; + + // track parameter names in declaration order so we can pull their + // narrowed types back out once the body has been checked + std::vector paramNames; for (const auto& arg : node.children[1].children) { switch (arg.type) { case NodeType::Identifier: { - typeChecker.setVariableUnknown(*arg.getIdentifier()); + auto name = *arg.getIdentifier(); + typeChecker.setVariableUnknown(name); + paramNames.push_back(name); break; } case NodeType::SetType: { @@ -181,7 +192,9 @@ namespace Solstice { if (types.find(*typeId) == types.end()) { throw std::runtime_error("unknown type " + *typeId); } - typeChecker.setVariable(*arg.children[0].getIdentifier(), types[*typeId]); + auto name = *arg.children[0].getIdentifier(); + typeChecker.setVariable(name, types[*typeId]); + paramNames.push_back(name); break; } default: { @@ -191,19 +204,22 @@ namespace Solstice { } typeChecker.checkTypes(); - - auto nodeType = node.children[2].ptype.getOnlyType(); - if (!nodeType.has_value()) { - throw std::runtime_error("cannot assign ambiguous type to name"); + + // collect the (possibly still-polymorphic) narrowed argument types, + // and the inferred return type from the body's implicit return value + Function fn; + for (const auto& name : paramNames) { + fn.argumentTypes.push_back(typeChecker.variables[name]); } - variables[*id] = {{*nodeType}}; - variables[*id].isConstant = true; + fn.returnTypes = node.children[2].ptype; + + functions[*id] = fn; } Narrowing TypeChecker::doesNodeChildrenNeedNarrowing(Node& node) { - Narrowing ret; + Narrowing ret = Narrowing::None; if ( - node.children[0].type == NodeType::Expression && + node.children[0].type == NodeType::Identifier && variables.find(*node.children[0].getIdentifier()) != variables.end() && variables[*node.children[0].getIdentifier()].unknownType ) { @@ -211,7 +227,7 @@ namespace Solstice { } if ( - node.children[1].type == NodeType::Expression && + node.children[1].type == NodeType::Identifier && variables.find(*node.children[1].getIdentifier()) != variables.end() && variables[*node.children[1].getIdentifier()].unknownType ) { @@ -234,96 +250,73 @@ namespace Solstice { case Narrowing::Both: { auto& leftVar = variables[*left.getIdentifier()]; auto& rightVar = variables[*right.getIdentifier()]; - - if (leftVar.possiblities.empty() && rightVar.possiblities.empty()) { - for (const auto& [key, value] : overloads) { - leftVar.possiblities.insert(key.left); - rightVar.possiblities.insert(key.right); + + // an empty possibility set means "still fully open" (no prior + // narrowing yet), so treat it as no constraint; otherwise only + // keep overloads compatible with what's already been narrowed + std::unordered_set newLeft; + std::unordered_set newRight; + for (const auto& [key, value] : overloads) { + bool leftOk = leftVar.possiblities.empty() || leftVar.possiblities.find(key.left) != leftVar.possiblities.end(); + bool rightOk = rightVar.possiblities.empty() || rightVar.possiblities.find(key.right) != rightVar.possiblities.end(); + if (leftOk && rightOk) { + newLeft.insert(key.left); + newRight.insert(key.right); node.ptype.possiblities.insert(value); } - } else if (leftVar.possiblities.empty()) { - std::unordered_set rightMatches; - for (const auto& [key, value] : overloads) { - if (rightVar.possiblities.find(key.right) != rightVar.possiblities.end()) { - rightMatches.insert(key.left); - node.ptype.possiblities.insert(value); - } - } - if (rightMatches.empty()) { - throw std::runtime_error("no valid overload compatible with previous statements"); - } - leftVar.possiblities = rightMatches; - } else if (rightVar.possiblities.empty()) { - std::unordered_set leftMatches; - for (const auto& [key, value] : overloads) { - if (leftVar.possiblities.find(key.right) != leftVar.possiblities.end()) { - leftMatches.insert(key.left); - node.ptype.possiblities.insert(value); - } - } - if (leftMatches.empty()) { - throw std::runtime_error("no valid overload compatible with previous statements"); - } - rightVar.possiblities = leftMatches; - } else { - std::unordered_set left; - std::unordered_set right; - for (const auto& [key, value] : overloads) { - if ( - leftVar.possiblities.find(key.right) != leftVar.possiblities.end() && - rightVar.possiblities.find(key.right) != rightVar.possiblities.end() - ) { - left.insert(key.left); - right.insert(key.right); - node.ptype.possiblities.insert(value); - } - } - if (left.empty() || right.empty()) { - throw std::runtime_error("no valid overload compatible with previous statements"); - } } + if (newLeft.empty() || newRight.empty()) { + throw std::runtime_error("no valid overload compatible with previous statements"); + } + leftVar.possiblities = newLeft; + rightVar.possiblities = newRight; break; } case Narrowing::Left: { auto& leftVar = variables[*left.getIdentifier()]; - std::unordered_set rightMatches; + std::unordered_set newLeft; for (const auto& [key, value] : overloads) { - if (right.ptype.possiblities.find(key.right) != right.ptype.possiblities.end()) { - rightMatches.insert(key.left); + bool leftOk = leftVar.possiblities.empty() || leftVar.possiblities.find(key.left) != leftVar.possiblities.end(); + bool rightOk = right.ptype.possiblities.find(key.right) != right.ptype.possiblities.end(); + if (leftOk && rightOk) { + newLeft.insert(key.left); node.ptype.possiblities.insert(value); } } - if (rightMatches.empty()) { + if (newLeft.empty()) { throw std::runtime_error("no valid overload compatible with previous statements"); } - leftVar.possiblities = rightMatches; + leftVar.possiblities = newLeft; break; } case Narrowing::Right: { auto& rightVar = variables[*right.getIdentifier()]; - std::unordered_set rightMatches; + std::unordered_set newRight; for (const auto& [key, value] : overloads) { - if (left.ptype.possiblities.find(key.left) != left.ptype.possiblities.end()) { - rightMatches.insert(key.left); + bool leftOk = left.ptype.possiblities.find(key.left) != left.ptype.possiblities.end(); + bool rightOk = rightVar.possiblities.empty() || rightVar.possiblities.find(key.right) != rightVar.possiblities.end(); + if (leftOk && rightOk) { + newRight.insert(key.right); node.ptype.possiblities.insert(value); } } - if (rightMatches.empty()) { + if (newRight.empty()) { throw std::runtime_error("no valid overload compatible with previous statements"); } - rightVar.possiblities = rightMatches; + rightVar.possiblities = newRight; break; } case Narrowing::None: { for (const auto& [key, value] : overloads) { if ( - left.ptype.possiblities.find(key.right) != left.ptype.possiblities.end() && + left.ptype.possiblities.find(key.left) != left.ptype.possiblities.end() && right.ptype.possiblities.find(key.right) != right.ptype.possiblities.end() ) { node.ptype.possiblities.insert(value); } } + break; } } @@ -344,16 +337,27 @@ namespace Solstice { } checkNodeType(node.children[1]); - auto nodeType = node.children[1].ptype.getOnlyType(); - if (!nodeType.has_value()) { - throw std::runtime_error("cannot assign ambiguous type to name"); + if (node.children[1].ptype.possiblities.empty() && !node.children[1].ptype.unknownType) { + throw std::runtime_error("cannot assign name with no possible types"); + } + variables[*id] = node.children[1].ptype; + // if not narrowed down to a single type yet, keep it open so later + // usages of this name can continue narrowing it + if (!variables[*id].getOnlyType().has_value()) { + variables[*id].unknownType = true; } - variables[*id] = {{*nodeType}}; } void TypeChecker::checkCodeBlockType(Node& node) { - - return; + if (node.children.empty()) { + throw std::runtime_error("code block has no statements"); + } + for (auto& child : node.children) { + checkNodeType(child); + } + // implicit return: the value of a code block is the value of its + // last statement (there is no explicit 'return' keyword) + node.ptype = node.children.back().ptype; } void TypeChecker::checkAddType(Node& node) { @@ -366,6 +370,55 @@ namespace Solstice { } + void TypeChecker::checkSubtractType(Node& node) { + checkNodeType(node.children[0]); + checkNodeType(node.children[1]); + + narrowBinaryNode(node, subtractOverloads); + } + + void TypeChecker::checkMultiplyType(Node& node) { + checkNodeType(node.children[0]); + checkNodeType(node.children[1]); + + narrowBinaryNode(node, multiplyOverloads); + } + + void TypeChecker::checkDivideType(Node& node) { + checkNodeType(node.children[0]); + checkNodeType(node.children[1]); + + narrowBinaryNode(node, divideOverloads); + } + + void TypeChecker::checkEqualType(Node& node) { + checkNodeType(node.children[0]); + checkNodeType(node.children[1]); + + narrowBinaryNode(node, equalOverloads); + } + + void TypeChecker::checkNotEqualType(Node& node) { + checkNodeType(node.children[0]); + checkNodeType(node.children[1]); + + narrowBinaryNode(node, notEqualOverloads); + } + + void TypeChecker::checkGreaterThanType(Node& node) { + checkNodeType(node.children[0]); + checkNodeType(node.children[1]); + + narrowBinaryNode(node, greaterThanOverloads); + } + + void TypeChecker::checkLesserThanType(Node& node) { + checkNodeType(node.children[0]); + checkNodeType(node.children[1]); + + narrowBinaryNode(node, lesserThanOverloads); + } + void TypeChecker::checkNodeType(Node& node) { switch (node.type) { @@ -411,7 +464,6 @@ namespace Solstice { case NodeType::Add: checkAddType(node); break; - /* case NodeType::Subtract: checkSubtractType(node); break; @@ -433,7 +485,6 @@ namespace Solstice { case NodeType::LesserThan: checkLesserThanType(node); break; - */ } } diff --git a/src/typechecker/typechecker.hpp b/src/typechecker/typechecker.hpp index 617336a..3f9e759 100644 --- a/src/typechecker/typechecker.hpp +++ b/src/typechecker/typechecker.hpp @@ -18,6 +18,7 @@ namespace Solstice { class TypeChecker { std::unordered_map variables; std::unordered_map types; + std::unordered_map functions; std::unordered_map addOverloads; std::unordered_map subtractOverloads; @@ -80,5 +81,13 @@ namespace Solstice { } void checkTypes(); + + const Function* getFunction(const std::string& name) const { + auto it = functions.find(name); + if (it == functions.end()) { + return nullptr; + } + return &it->second; + } }; }