[WIP] Improve type checking algorithm

This commit is contained in:
Sam Vervaeck 2020-10-30 14:53:56 +01:00
parent 92b94d7098
commit 5bd9dba3fe
7 changed files with 374 additions and 254 deletions

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@ import {
BoltToken BoltToken
} from "./ast"; } from "./ast";
import { BOLT_SUPPORTED_LANGUAGES } from "./constants" import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
import { FastStringMap, enumOr, escapeChar, assert, registerClass, Newable, GeneratorStream } from "./util"; import { FastStringMap, enumOr, escapeChar, assert, registerClass, Newable, GeneratorStream, FastMultiMap } from "./util";
import { TextSpan, TextPos, TextFile } from "./text"; import { TextSpan, TextPos, TextFile } from "./text";
import { Scanner } from "./scanner"; import { Scanner } from "./scanner";
import { convertNodeToSymbolPath, SymbolPath } from "./resolver"; import { convertNodeToSymbolPath, SymbolPath } from "./resolver";
@ -173,7 +173,7 @@ type OperatorTableList = [OperatorKind, number, string][][];
export class OperatorTable { export class OperatorTable {
private operatorsByName = new FastStringMap<string, OperatorInfo>(); private operatorsByName = new FastMultiMap<string, OperatorInfo>();
//private operatorsByPrecedence = FastStringMap<number, OperatorInfo>(); //private operatorsByPrecedence = FastStringMap<number, OperatorInfo>();
constructor(definitions: OperatorTableList) { constructor(definitions: OperatorTableList) {
@ -181,18 +181,21 @@ export class OperatorTable {
for (const group of definitions) { for (const group of definitions) {
for (const [kind, arity, name] of group) { for (const [kind, arity, name] of group) {
const info = { kind, arity, name, precedence: i } const info = { kind, arity, name, precedence: i }
this.operatorsByName.set(name, info); this.operatorsByName.add(name, info);
//this.operatorsByPrecedence[i] = info;
} }
i++; i++;
} }
} }
public lookup(name: string): OperatorInfo | null { public lookup(arity: number, name: string): OperatorInfo | null {
if (!this.operatorsByName.has(name)) { if (!this.operatorsByName.has(name)) {
return null; return null;
} }
return this.operatorsByName.get(name); for (const operatorInfo of this.operatorsByName.get(name)) {
if (operatorInfo.arity === arity) {
return operatorInfo;
}
}
} }
} }

View file

@ -21,8 +21,8 @@ export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present."
export const E_FIELD_MUST_BE_BOOLEAN = "Field '{name}' must be a either 'true' or 'false'." export const E_FIELD_MUST_BE_BOOLEAN = "Field '{name}' must be a either 'true' or 'false'."
export const E_TYPE_DECLARATION_NOT_FOUND = "A type declaration named '{name}' was not found." export const E_TYPE_DECLARATION_NOT_FOUND = "A type declaration named '{name}' was not found."
export const E_DECLARATION_NOT_FOUND = "Reference to an undefined declaration '{name}'."; export const E_DECLARATION_NOT_FOUND = "Reference to an undefined declaration '{name}'.";
export const E_TYPE_MISMATCH = "Types {left} and {right} are not semantically equivalent."; export const E_TYPE_MISMATCH = "Types {left} and {right} are not compatible with one another.";
export const E_THIS_NODE_CAUSED_INVALID_TYPE = "This node resolved to the type {type}, which caused type matching to fail." export const E_THIS_NODE_CAUSED_INVALID_TYPE = "This node resolved to the type {type}, which is incompatible with {origType}."
export const E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL = "Too few arguments for function call. Expected {expected} but got {actual}."; export const E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL = "Too few arguments for function call. Expected {expected} but got {actual}.";
export const E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL = "Too many arguments for function call. Expected {expected} but got {actual}."; export const E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL = "Too many arguments for function call. Expected {expected} but got {actual}.";
export const E_NOT_CALLABLE = "The result of this expression is not callable." export const E_NOT_CALLABLE = "The result of this expression is not callable."
@ -30,11 +30,12 @@ export const E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER = "Candidate function
export const E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER = "This argument is missing a corresponding parameter." export const E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER = "This argument is missing a corresponding parameter."
export const E_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'" export const E_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'"
export const E_RECORD_MISSING_MEMBER = "Record {name} does not have a member declaration named {memberName}" export const E_RECORD_MISSING_MEMBER = "Record {name} does not have a member declaration named {memberName}"
export const E_TYPE_NEVER_MATCHES = "Type '{type}' never matches anything."
export const E_TYPES_MISSING_MEMBER = "Not all types resolve to a record with the a member named '{name}'." export const E_TYPES_MISSING_MEMBER = "Not all types resolve to a record with the a member named '{name}'."
export const E_NODE_DOES_NOT_CONTAIN_MEMBER = "This node does not contain the the member '{name}'." export const E_NODE_DOES_NOT_CONTAIN_MEMBER = "This node does not contain the the member '{name}'."
export const E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID = "May not return a value because the function's return type resolves to '()'" export const E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID = "May not return a value because the function's return type resolves to '()'"
export const E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID = "Must return a value because the function's return type does not resolve to '()'" export const E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID = "Must return a value because the function's return type does not resolve to '()'"
export const E_ARGUMENT_TYPE_NOT_ASSIGNABLE = "This argument's type is not assignable to the function's parameter type." export const E_ARGUMENT_TYPE_NOT_ASSIGNABLE = "This argument's type '{argType}' is not assignable to the function's parameter type '{paramType}'."
export const E_PARAMETER_DECLARED_HERE = "Function parameter was declared here." export const E_PARAMETER_DECLARED_HERE = "Function parameter was declared here."
export const E_BUILTIN_TYPE_MISSING = "A built-in type named '{name}' in the prelude." export const E_BUILTIN_TYPE_MISSING = "A built-in type named '{name}' in the prelude."

View file

@ -87,6 +87,8 @@ import {
BoltBraced, BoltBraced,
BoltAssignStatement, BoltAssignStatement,
createBoltAssignStatement, createBoltAssignStatement,
BoltExportDirective,
BoltParenthesized,
} from "./ast" } from "./ast"
import { parseForeignLanguage } from "./foreign" import { parseForeignLanguage } from "./foreign"
@ -205,7 +207,7 @@ export class Parser {
], ],
]); ]);
typeOperatorTable = new OperatorTable([ private typeOperatorTable = new OperatorTable([
[ [
[OperatorKind.InfixL, 2, '|'], [OperatorKind.InfixL, 2, '|'],
] ]
@ -755,8 +757,51 @@ export class Parser {
} }
public parseExpression(tokens: BoltTokenStream) {
return this.parseBinaryExpression(tokens, this.parseUnaryExpression(tokens), 0);
}
private parseExpression(tokens: BoltTokenStream): BoltExpression { private parseUnaryExpression(tokens: BoltTokenStream) {
return this.parseExpressionPrimitive(tokens);
}
private parseBinaryExpression(tokens: BoltTokenStream, lhs: BoltExpression, minPrecedence: number) {
let lookahead = tokens.peek(1);
while (true) {
if (lookahead.kind !== SyntaxKind.BoltOperator) {
break;
}
const lookaheadDesc = this.exprOperatorTable.lookup(2, lookahead.text);
if (lookaheadDesc === null || lookaheadDesc.precedence < minPrecedence) {
break;
}
const op = lookahead;
const opDesc = this.exprOperatorTable.lookup(2, op.text);
if (opDesc === null) {
break;
}
tokens.get();
let rhs = this.parseUnaryExpression(tokens)
lookahead = tokens.peek()
while (lookaheadDesc.arity === 2
&& ((lookaheadDesc.precedence > opDesc.precedence)
|| lookaheadDesc.kind === OperatorKind.InfixR && lookaheadDesc.precedence === opDesc.precedence)) {
rhs = this.parseBinaryExpression(tokens, rhs, lookaheadDesc.precedence)
lookahead = tokens.peek();
}
lookahead = tokens.peek();
const qualName = createBoltQualName(false, [], op);
setOrigNodeRange(qualName, op, op);
const refExpr = createBoltReferenceExpression(qualName);
setOrigNodeRange(refExpr, op, op);
const binExpr = createBoltCallExpression(refExpr, [lhs, rhs]);
setOrigNodeRange(binExpr, lhs, rhs);
lhs = binExpr;
}
return lhs
}
private parseExpressionPrimitive(tokens: BoltTokenStream): BoltExpression {
try { try {
const forked = tokens.fork(); const forked = tokens.fork();
@ -774,6 +819,11 @@ export class Parser {
let result; let result;
if (t0.kind === SyntaxKind.BoltVBar) { if (t0.kind === SyntaxKind.BoltVBar) {
result = this.parseFunctionExpression(tokens); result = this.parseFunctionExpression(tokens);
} else if (t0.kind === SyntaxKind.BoltParenthesized) {
tokens.get();
const innerTokens = createTokenStream(t0);
result = this.parseExpression(innerTokens);
setOrigNodeRange(result, t0, t0);
} else if (t0.kind === SyntaxKind.BoltBraced) { } else if (t0.kind === SyntaxKind.BoltBraced) {
result = this.parseBlockExpression(tokens); result = this.parseBlockExpression(tokens);
} else if (t0.kind === SyntaxKind.BoltQuoteKeyword) { } else if (t0.kind === SyntaxKind.BoltQuoteKeyword) {
@ -1648,32 +1698,6 @@ export class Parser {
return elements return elements
} }
//parseBinOp(tokens: TokenStream, lhs: Expr , minPrecedence: number) {
// let lookahead = tokens.peek(1);
// while (true) {
// if (lookahead.kind !== SyntaxKind.BoltOperator) {
// break;
// }
// const lookaheadDesc = this.getOperatorDesc(2, lookahead.text);
// if (lookaheadDesc === null || lookaheadDesc.precedence < minPrecedence) {
// break;
// }
// const op = lookahead;
// const opDesc = this.getOperatorDesc(2, op.text);
// tokens.get();
// let rhs = this.parsePrimExpr(tokens)
// lookahead = tokens.peek()
// while (lookaheadDesc.arity === 2
// && ((lookaheadDesc.precedence > opDesc.precedence)
// || lookaheadDesc.kind === OperatorKind.InfixR && lookaheadDesc.precedence === opDesc.precedence)) {
// rhs = this.parseBinOp(tokens, rhs, lookaheadDesc.precedence)
// }
// lookahead = tokens.peek();
// lhs = new CallExpr(new RefExpr(new QualName(op, [])), [lhs, rhs]);
// }
// return lhs
//}
public parseSourceFile(tokens: BoltTokenStream, pkg: Package | null = null): BoltSourceFile { public parseSourceFile(tokens: BoltTokenStream, pkg: Package | null = null): BoltSourceFile {
const elements = this.parseSourceElements(tokens); const elements = this.parseSourceElements(tokens);
const t1 = tokens.peek(); const t1 = tokens.peek();

View file

@ -1,4 +1,13 @@
import { AnyType, UnionType } from "../checker" import test from "ava";
import { createBoltIdentifier } from "../ast";
import { AnyType, IntersectType, PrimType } from "../checker"
import { createRef } from "../util";
test('an intersection with an any-type should remove that any-type', t => {
const t1 = new IntersectType([
createRef(new AnyType()),
createRef(new PrimType('@int')),
])
});

View file

@ -791,7 +791,7 @@ export function format(message: string, data: MapLike<FormatArg>) {
} }
value = modifierFn(value); value = modifierFn(value);
} }
out += value.toString(); out += prettyPrint(value);
reset(); reset();
} }

16
test.bolt Normal file
View file

@ -0,0 +1,16 @@
struct int;
struct String;
fn (a: int) - (b: int) -> int;
fn (a: int) * (b: int) -> int;
fn fac(n: int) -> int {
return match n {
0 => 1,
1 => 1,
k => k * (k-1),
}
}
fac(1);