From 5fd82fd322f17cf247f9c3bbb6f1337973abb4df Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Mon, 3 Aug 2026 20:03:54 +1000 Subject: [PATCH] Function return type checking --- src/typechecker/type.hpp | 5 +- src/typechecker/typechecker.cpp | 82 +++++++++++++++++++++++++++++++-- src/typechecker/typechecker.hpp | 1 + 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/typechecker/type.hpp b/src/typechecker/type.hpp index 282ecde..84b46ff 100644 --- a/src/typechecker/type.hpp +++ b/src/typechecker/type.hpp @@ -3,10 +3,12 @@ #include #include #include +#include #include #include #include #include +#include namespace Solstice { enum class BaseType { @@ -137,8 +139,9 @@ namespace Solstice { }; struct Function { - PossibleType returnTypes; std::vector argumentTypes; + std::unordered_map, Type> returnTypes; + std::vector> invalidCombinations; Function() = default; }; diff --git a/src/typechecker/typechecker.cpp b/src/typechecker/typechecker.cpp index 4117abd..16a820e 100644 --- a/src/typechecker/typechecker.cpp +++ b/src/typechecker/typechecker.cpp @@ -1,5 +1,6 @@ #include "typechecker.hpp" #include "type.hpp" +#include #include #include @@ -153,6 +154,45 @@ namespace Solstice { variables[*id].unknownType = true; } } + static inline std::vector> doCartesianProductOnTypeSets(const std::vector& typeSets) { + if (typeSets.empty()) return {}; + for (const auto& s : typeSets) { + if (s.possiblities.empty()) { + return {}; + } + } + + std::vector> result; + std::vector::const_iterator> iterators; + + // Initialize iterators to the start of each set + for (const auto& s : typeSets) { + iterators.push_back(s.possiblities.begin()); + } + + while (true) { + // Collect current elements from each set + std::vector current; + for (const auto& it : iterators) { + current.push_back(*it); + } + result.push_back(current); + + // Increment like an odometer (rightmost digit first) + int idx = iterators.size() - 1; + while (idx >= 0) { + iterators[idx]++; + if (iterators[idx] != typeSets[idx].possiblities.end()) break; + iterators[idx] = typeSets[idx].possiblities.begin(); + idx--; + } + + // If we wrapped the leftmost position, we're done + if (idx < 0) break; + } + + return result; + } void TypeChecker::checkFunctionBindNodeType(Node& node) { auto id = node.children[0].getIdentifier(); @@ -205,13 +245,38 @@ namespace Solstice { typeChecker.checkTypes(); - // collect the (possibly still-polymorphic) narrowed argument types, - // and the inferred return type from the body's implicit return value + // collect the (possibly still-polymorphic) narrowed argument types Function fn; for (const auto& name : paramNames) { fn.argumentTypes.push_back(typeChecker.variables[name]); } - fn.returnTypes = node.children[2].ptype; + + std::vector> candidates = doCartesianProductOnTypeSets(fn.argumentTypes); + + // re-check the body once per candidate with every parameter bound to + // a single concrete type. concrete (non-unknownType) variables always + // take the Narrowing::None path, so this reuses all the existing + // operator-checking logic and naturally rejects combinations that + // only looked valid in the marginal sets. + for (const auto& combo : candidates) { + TypeChecker concreteChecker(*this); + concreteChecker.setNode(node.children[2]); + concreteChecker.inFunction = true; + for (std::size_t i = 0; i < paramNames.size(); i++) { + concreteChecker.setVariable(paramNames[i], combo[i]); + } + try { + concreteChecker.checkTypes(); + auto retType = node.children[2].ptype.getOnlyType(); + if (retType.has_value()) { + fn.returnTypes[combo] = *retType; + } else { + fn.invalidCombinations.push_back(combo); + } + } catch (const std::exception&) { + fn.invalidCombinations.push_back(combo); + } + } functions[*id] = fn; } @@ -245,6 +310,13 @@ namespace Solstice { auto& left = node.children[0]; auto& right = node.children[1]; + // this node may be getting checked again (e.g. once polymorphically, + // then once more per concrete argument combination in + // checkFunctionBindNodeType) - possiblities is only ever inserted + // into below, so it must be reset here or results from a previous + // pass over this same node would leak into this one + node.ptype.possiblities.clear(); + // narrow types if we need to switch (doesNodeChildrenNeedNarrowing(node)) { case Narrowing::Both: { @@ -323,6 +395,10 @@ namespace Solstice { } + void TypeChecker::narrowFunctionCallNode(Node& node) { + + } + void TypeChecker::checkSetNodeType(Node& node) { auto id = node.children[0].getIdentifier(); if (!id.has_value()) { diff --git a/src/typechecker/typechecker.hpp b/src/typechecker/typechecker.hpp index 3f9e759..f94d1b9 100644 --- a/src/typechecker/typechecker.hpp +++ b/src/typechecker/typechecker.hpp @@ -47,6 +47,7 @@ namespace Solstice { Narrowing doesNodeChildrenNeedNarrowing(Node& node); void narrowBinaryNode(Node& node, std::unordered_map& overloads); + void narrowFunctionCallNode(Node& node); void checkAddType(Node& node); void checkSubtractType(Node& node);