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 { struct Function {
std::unordered_map<std::vector<Type>, Type> signatureToReturnType; PossibleType returnTypes;
std::vector<PossibleType> argumentTypes;
Function() = default; Function() = default;
}; };

View File

@@ -142,12 +142,16 @@ namespace Solstice {
} }
checkNodeType(node.children[1]); checkNodeType(node.children[1]);
auto nodeType = node.children[1].ptype.getOnlyType(); if (node.children[1].ptype.possiblities.empty() && !node.children[1].ptype.unknownType) {
if (!nodeType.has_value()) { throw std::runtime_error("cannot assign name with no possible types");
throw std::runtime_error("cannot assign ambiguous type to name");
} }
variables[*id] = {{*nodeType}}; variables[*id] = node.children[1].ptype;
variables[*id].isConstant = true; 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) { void TypeChecker::checkFunctionBindNodeType(Node& node) {
@@ -162,11 +166,18 @@ namespace Solstice {
// create sub-checker // create sub-checker
TypeChecker typeChecker(*this); TypeChecker typeChecker(*this);
typeChecker.setNode(node.children[2]); 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) { for (const auto& arg : node.children[1].children) {
switch (arg.type) { switch (arg.type) {
case NodeType::Identifier: { case NodeType::Identifier: {
typeChecker.setVariableUnknown(*arg.getIdentifier()); auto name = *arg.getIdentifier();
typeChecker.setVariableUnknown(name);
paramNames.push_back(name);
break; break;
} }
case NodeType::SetType: { case NodeType::SetType: {
@@ -181,7 +192,9 @@ namespace Solstice {
if (types.find(*typeId) == types.end()) { if (types.find(*typeId) == types.end()) {
throw std::runtime_error("unknown type " + *typeId); 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; break;
} }
default: { default: {
@@ -192,18 +205,21 @@ namespace Solstice {
typeChecker.checkTypes(); typeChecker.checkTypes();
auto nodeType = node.children[2].ptype.getOnlyType(); // collect the (possibly still-polymorphic) narrowed argument types,
if (!nodeType.has_value()) { // and the inferred return type from the body's implicit return value
throw std::runtime_error("cannot assign ambiguous type to name"); Function fn;
for (const auto& name : paramNames) {
fn.argumentTypes.push_back(typeChecker.variables[name]);
} }
variables[*id] = {{*nodeType}}; fn.returnTypes = node.children[2].ptype;
variables[*id].isConstant = true;
functions[*id] = fn;
} }
Narrowing TypeChecker::doesNodeChildrenNeedNarrowing(Node& node) { Narrowing TypeChecker::doesNodeChildrenNeedNarrowing(Node& node) {
Narrowing ret; Narrowing ret = Narrowing::None;
if ( if (
node.children[0].type == NodeType::Expression && node.children[0].type == NodeType::Identifier &&
variables.find(*node.children[0].getIdentifier()) != variables.end() && variables.find(*node.children[0].getIdentifier()) != variables.end() &&
variables[*node.children[0].getIdentifier()].unknownType variables[*node.children[0].getIdentifier()].unknownType
) { ) {
@@ -211,7 +227,7 @@ namespace Solstice {
} }
if ( if (
node.children[1].type == NodeType::Expression && node.children[1].type == NodeType::Identifier &&
variables.find(*node.children[1].getIdentifier()) != variables.end() && variables.find(*node.children[1].getIdentifier()) != variables.end() &&
variables[*node.children[1].getIdentifier()].unknownType variables[*node.children[1].getIdentifier()].unknownType
) { ) {
@@ -235,95 +251,72 @@ namespace Solstice {
auto& leftVar = variables[*left.getIdentifier()]; auto& leftVar = variables[*left.getIdentifier()];
auto& rightVar = variables[*right.getIdentifier()]; auto& rightVar = variables[*right.getIdentifier()];
if (leftVar.possiblities.empty() && rightVar.possiblities.empty()) { // 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) { for (const auto& [key, value] : overloads) {
leftVar.possiblities.insert(key.left); bool leftOk = leftVar.possiblities.empty() || leftVar.possiblities.find(key.left) != leftVar.possiblities.end();
rightVar.possiblities.insert(key.right); bool rightOk = rightVar.possiblities.empty() || rightVar.possiblities.find(key.right) != rightVar.possiblities.end();
node.ptype.possiblities.insert(value); if (leftOk && rightOk) {
} newLeft.insert(key.left);
} else if (leftVar.possiblities.empty()) { newRight.insert(key.right);
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); node.ptype.possiblities.insert(value);
} }
} }
if (rightMatches.empty()) { if (newLeft.empty() || newRight.empty()) {
throw std::runtime_error("no valid overload compatible with previous statements"); throw std::runtime_error("no valid overload compatible with previous statements");
} }
leftVar.possiblities = rightMatches; leftVar.possiblities = newLeft;
} else if (rightVar.possiblities.empty()) { rightVar.possiblities = newRight;
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");
}
}
break; break;
} }
case Narrowing::Left: { case Narrowing::Left: {
auto& leftVar = variables[*left.getIdentifier()]; auto& leftVar = variables[*left.getIdentifier()];
std::unordered_set<Type> rightMatches; std::unordered_set<Type> newLeft;
for (const auto& [key, value] : overloads) { for (const auto& [key, value] : overloads) {
if (right.ptype.possiblities.find(key.right) != right.ptype.possiblities.end()) { bool leftOk = leftVar.possiblities.empty() || leftVar.possiblities.find(key.left) != leftVar.possiblities.end();
rightMatches.insert(key.left); bool rightOk = right.ptype.possiblities.find(key.right) != right.ptype.possiblities.end();
if (leftOk && rightOk) {
newLeft.insert(key.left);
node.ptype.possiblities.insert(value); node.ptype.possiblities.insert(value);
} }
} }
if (rightMatches.empty()) { if (newLeft.empty()) {
throw std::runtime_error("no valid overload compatible with previous statements"); throw std::runtime_error("no valid overload compatible with previous statements");
} }
leftVar.possiblities = rightMatches; leftVar.possiblities = newLeft;
break; break;
} }
case Narrowing::Right: { case Narrowing::Right: {
auto& rightVar = variables[*right.getIdentifier()]; auto& rightVar = variables[*right.getIdentifier()];
std::unordered_set<Type> rightMatches; std::unordered_set<Type> newRight;
for (const auto& [key, value] : overloads) { for (const auto& [key, value] : overloads) {
if (left.ptype.possiblities.find(key.left) != left.ptype.possiblities.end()) { bool leftOk = left.ptype.possiblities.find(key.left) != left.ptype.possiblities.end();
rightMatches.insert(key.left); 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); node.ptype.possiblities.insert(value);
} }
} }
if (rightMatches.empty()) { if (newRight.empty()) {
throw std::runtime_error("no valid overload compatible with previous statements"); throw std::runtime_error("no valid overload compatible with previous statements");
} }
rightVar.possiblities = rightMatches; rightVar.possiblities = newRight;
break; break;
} }
case Narrowing::None: { case Narrowing::None: {
for (const auto& [key, value] : overloads) { for (const auto& [key, value] : overloads) {
if ( 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() right.ptype.possiblities.find(key.right) != right.ptype.possiblities.end()
) { ) {
node.ptype.possiblities.insert(value); node.ptype.possiblities.insert(value);
} }
} }
break;
} }
} }
@@ -344,16 +337,27 @@ namespace Solstice {
} }
checkNodeType(node.children[1]); checkNodeType(node.children[1]);
auto nodeType = node.children[1].ptype.getOnlyType(); if (node.children[1].ptype.possiblities.empty() && !node.children[1].ptype.unknownType) {
if (!nodeType.has_value()) { throw std::runtime_error("cannot assign name with no possible types");
throw std::runtime_error("cannot assign ambiguous type to name"); }
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) { void TypeChecker::checkCodeBlockType(Node& node) {
if (node.children.empty()) {
return; 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) { 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) { void TypeChecker::checkNodeType(Node& node) {
switch (node.type) { switch (node.type) {
@@ -411,7 +464,6 @@ namespace Solstice {
case NodeType::Add: case NodeType::Add:
checkAddType(node); checkAddType(node);
break; break;
/*
case NodeType::Subtract: case NodeType::Subtract:
checkSubtractType(node); checkSubtractType(node);
break; break;
@@ -433,7 +485,6 @@ namespace Solstice {
case NodeType::LesserThan: case NodeType::LesserThan:
checkLesserThanType(node); checkLesserThanType(node);
break; break;
*/
} }
} }

View File

@@ -18,6 +18,7 @@ namespace Solstice {
class TypeChecker { class TypeChecker {
std::unordered_map<std::string, PossibleType> variables; std::unordered_map<std::string, PossibleType> variables;
std::unordered_map<std::string, Type> types; 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> addOverloads;
std::unordered_map<TypePair, Type> subtractOverloads; std::unordered_map<TypePair, Type> subtractOverloads;
@@ -80,5 +81,13 @@ namespace Solstice {
} }
void checkTypes(); void checkTypes();
const Function* getFunction(const std::string& name) const {
auto it = functions.find(name);
if (it == functions.end()) {
return nullptr;
}
return &it->second;
}
}; };
} }