[WIP] Improve type checking algorithm
This commit is contained in:
parent
92b94d7098
commit
5bd9dba3fe
7 changed files with 374 additions and 254 deletions
495
src/checker.ts
495
src/checker.ts
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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')),
|
||||||
|
])
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -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
16
test.bolt
Normal 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);
|
Loading…
Reference in a new issue