Narrowing works

This commit is contained in:
2026-08-03 08:15:48 +10:00
parent 9bee018c09
commit 45736fbcbc
3 changed files with 141 additions and 81 deletions

View File

@@ -137,8 +137,8 @@ namespace Solstice {
};
struct Function {
std::unordered_map<std::vector<Type>, Type> signatureToReturnType;
PossibleType returnTypes;
std::vector<PossibleType> argumentTypes;
Function() = default;
};

View File

@@ -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<std::string> 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<Type> newLeft;
std::unordered_set<Type> 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<Type> 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<Type> 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<Type> left;
std::unordered_set<Type> 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<Type> rightMatches;
std::unordered_set<Type> 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<Type> rightMatches;
std::unordered_set<Type> 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;
*/
}
}

View File

@@ -18,6 +18,7 @@ namespace Solstice {
class TypeChecker {
std::unordered_map<std::string, PossibleType> variables;
std::unordered_map<std::string, Type> types;
std::unordered_map<std::string, Function> functions;
std::unordered_map<TypePair, Type> addOverloads;
std::unordered_map<TypePair, Type> 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;
}
};
}