Narrowing works
This commit is contained in:
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user