diff --git a/packages/boringlang/src/types/trait_checker.ts b/packages/boringlang/src/types/trait_checker.ts index 581b996..7692d04 100644 --- a/packages/boringlang/src/types/trait_checker.ts +++ b/packages/boringlang/src/types/trait_checker.ts @@ -1,29 +1,10 @@ import { Impl, Module, TraitTypeDeclaration, TypeUsage } from "../parse/ast"; +import { compareTypes } from "./type_system"; interface Context { environment: Record; } -const compareTypes = (typeA: TypeUsage, typeB: TypeUsage) => { - if (typeA.typeUsage !== typeB.typeUsage) { - throw Error(`Mismatched types: ${typeA.typeUsage} ${typeB.typeUsage}`); - } - if (typeA.typeUsage == "NamedTypeUsage" && typeB.typeUsage == "NamedTypeUsage") { - if (typeA.name.name !== typeB.name.name) { - throw Error(`Mismatched types: ${typeA.name.name} ${typeB.name.name}`); - } - } - if (typeA.typeUsage == "FunctionTypeUsage" && typeB.typeUsage == "FunctionTypeUsage") { - if (typeA.arguments.length !== typeB.arguments.length) { - throw Error(`Mismatched arg lengths: ${typeA.arguments.length} ${typeB.arguments.length}`); - } - for (let i = 0; i < typeA.arguments.length; i++) { - compareTypes(typeA.arguments[i], typeB.arguments[i]); - } - compareTypes(typeA.returnType, typeB.returnType); - } -}; - export default class TraitChecker { withModule = (module: Module) => { let ctx: Context = { environment: {} }; diff --git a/packages/boringlang/src/types/type_checker.ts b/packages/boringlang/src/types/type_checker.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/boringlang/src/types/type_system.ts b/packages/boringlang/src/types/type_system.ts new file mode 100644 index 0000000..a405b5a --- /dev/null +++ b/packages/boringlang/src/types/type_system.ts @@ -0,0 +1,165 @@ +import { TypeUsage } from "../parse/ast"; + +export const compareTypes = (typeA: TypeUsage, typeB: TypeUsage) => { + if (typeA.typeUsage !== typeB.typeUsage) { + throw Error(`Mismatched types: ${typeA.typeUsage} ${typeB.typeUsage}`); + } + if (typeA.typeUsage == "NamedTypeUsage" && typeB.typeUsage == "NamedTypeUsage") { + if (typeA.name.name !== typeB.name.name) { + throw Error(`Mismatched types: ${typeA.name.name} ${typeB.name.name}`); + } + } + if (typeA.typeUsage == "FunctionTypeUsage" && typeB.typeUsage == "FunctionTypeUsage") { + if (typeA.arguments.length !== typeB.arguments.length) { + throw Error(`Mismatched arg lengths: ${typeA.arguments.length} ${typeB.arguments.length}`); + } + for (let i = 0; i < typeA.arguments.length; i++) { + compareTypes(typeA.arguments[i], typeB.arguments[i]); + } + compareTypes(typeA.returnType, typeB.returnType); + } +}; + +interface Equals { + operation: "equals"; +} + +interface Argument { + operation: "argument"; + argNum: number; +} + +interface ReturnValue { + operation: "return"; +} + +interface Comparison { + left: TypeUsage; + operation: Equals | Argument | ReturnValue; + right: TypeUsage; +} + +class TypeSystem { + comparisons: Comparison[]; + result: Record; + + constructor() { + this.comparisons = []; + this.result = {}; + } + + compare = (comparison: Comparison) => { + this.comparisons.push(comparison); + }; + + solve = () => { + let foundUpdate = false; + let containsUnknown = false; + while (true) { + for (const comparison of this.comparisons) { + // if already found, just update + if (comparison.left.typeUsage === "UnknownTypeUsage" && this.result[comparison.left.name]) { + foundUpdate = true; + comparison.left = this.result[comparison.left.name]; + } + if ( + comparison.right.typeUsage === "UnknownTypeUsage" && + this.result[comparison.right.name] + ) { + foundUpdate = true; + comparison.right = this.result[comparison.right.name]; + } + // equals + if (comparison.operation.operation === "equals") { + // solve left + if ( + comparison.left.typeUsage === "UnknownTypeUsage" && + comparison.right.typeUsage !== "UnknownTypeUsage" + ) { + foundUpdate = true; + this.result[comparison.left.name] = comparison.right; + comparison.left = comparison.right; + } + // solve right + if ( + comparison.left.typeUsage !== "UnknownTypeUsage" && + comparison.right.typeUsage === "UnknownTypeUsage" + ) { + foundUpdate = true; + this.result[comparison.right.name] = comparison.left; + comparison.right = comparison.left; + } + if ( + comparison.left.typeUsage !== "UnknownTypeUsage" && + comparison.right.typeUsage !== "UnknownTypeUsage" + ) { + compareTypes(comparison.left, comparison.right); + } + } + // argument + if (comparison.operation.operation === "argument") { + if (comparison.left.typeUsage !== "FunctionTypeUsage") { + throw Error("Argument for something that isn't a function"); + } + // solve left + const argument = comparison.left.arguments[comparison.operation.argNum]; + if ( + argument.typeUsage === "UnknownTypeUsage" && + comparison.right.typeUsage !== "UnknownTypeUsage" + ) { + foundUpdate = true; + this.result[argument.name] = comparison.right; + comparison.left.arguments[comparison.operation.argNum] = comparison.right; + } + // solve right + if ( + argument.typeUsage !== "UnknownTypeUsage" && + comparison.right.typeUsage === "UnknownTypeUsage" + ) { + foundUpdate = true; + this.result[comparison.right.name] = + comparison.left.arguments[comparison.operation.argNum]; + comparison.right = comparison.left.arguments[comparison.operation.argNum]; + } + } + // return type + if (comparison.operation.operation === "return") { + if (comparison.left.typeUsage !== "FunctionTypeUsage") { + throw Error("return type for something that isn't a function"); + } + // solve left + if ( + comparison.left.returnType.typeUsage === "UnknownTypeUsage" && + comparison.right.typeUsage !== "UnknownTypeUsage" + ) { + foundUpdate = true; + this.result[comparison.left.returnType.name] = comparison.right; + comparison.left.returnType = comparison.right; + } + // solve right + if ( + comparison.left.returnType.typeUsage !== "UnknownTypeUsage" && + comparison.right.typeUsage === "UnknownTypeUsage" + ) { + foundUpdate = true; + this.result[comparison.right.name] = comparison.left.returnType; + comparison.right = comparison.left.returnType; + } + } + if ( + comparison.left.typeUsage === "UnknownTypeUsage" || + comparison.right.typeUsage === "UnknownTypeUsage" + ) { + containsUnknown = true; + } + } + if (!foundUpdate && containsUnknown) { + throw Error("Type system failed to resolve all unknowns"); + } + if (!foundUpdate) { + break; + } + } + return this.result; + }; +}