Update code base

- Fix some issues in the parser and the AST spec
 - Fix invalid union types emitted by treegen
 - Fix and extend the Evaluator a bit
 - Swich API of type checker and make it store checking errors
   on the Syntax object itself
This commit is contained in:
Sam Vervaeck 2020-05-27 19:57:15 +02:00
parent f765ba6115
commit 05b024c3f4
13 changed files with 1461 additions and 328 deletions

View file

@ -91,7 +91,8 @@ node BoltSourceFile > SourceFile {
} }
node BoltQualName { node BoltQualName {
modulePath: Option<BoltModulePath>, isAbsolute: bool,
modulePath: Vec<BoltIdentifier>,
name: BoltSymbol, name: BoltSymbol,
} }
@ -103,8 +104,7 @@ node BoltModulePath {
node BoltTypeExpression; node BoltTypeExpression;
node BoltReferenceTypeExpression > BoltTypeExpression { node BoltReferenceTypeExpression > BoltTypeExpression {
modulePath: Option<BoltModulePath>, name: BoltQualName,
name: BoltIdentifier,
arguments: Option<Vec<BoltTypeExpression>>, arguments: Option<Vec<BoltTypeExpression>>,
} }
@ -113,6 +113,10 @@ node BoltFunctionTypeExpression > BoltTypeExpression {
returnType: Option<BoltTypeExpression>, returnType: Option<BoltTypeExpression>,
} }
node BoltLiftedTypeExpression > BoltTypeExpression {
expression: BoltExpression,
}
node BoltTypeParameter { node BoltTypeParameter {
index: usize, index: usize,
name: BoltIdentifier, name: BoltIdentifier,
@ -166,8 +170,7 @@ node BoltTupleExpression > BoltExpression {
} }
node BoltReferenceExpression > BoltExpression { node BoltReferenceExpression > BoltExpression {
modulePath: Option<BoltModulePath>, name: BoltQualName,
name: BoltSymbol,
} }
node BoltMemberExpression > BoltExpression { node BoltMemberExpression > BoltExpression {
@ -284,7 +287,8 @@ node BoltVariableDeclaration > BoltFunctionBodyElement, BoltDeclaration {
node BoltImportSymbol; node BoltImportSymbol;
node BoltPlainImportSymbol > BoltImportSymbol { node BoltPlainImportSymbol > BoltImportSymbol {
name: BoltQualName, remote: BoltQualName,
local: BoltSymbol,
} }
node BoltImportDirective > BoltSourceElement { node BoltImportDirective > BoltSourceElement {
@ -296,7 +300,8 @@ node BoltImportDirective > BoltSourceElement {
node BoltExportSymbol; node BoltExportSymbol;
node BoltPlainExportSymbol { node BoltPlainExportSymbol {
name: BoltQualName, local: BoltQualName,
remote: BoltSymbol,
} }
node BoltExportDirective > BoltSourceElement { node BoltExportDirective > BoltSourceElement {

1250
src/ast.d.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,14 @@
import { BoltImportDirective, Syntax, BoltParameter, BoltModulePath, BoltReferenceExpression, BoltReferenceTypeExpression, BoltSourceFile, BoltCallExpression, BoltReturnKeyword, BoltReturnStatement, SyntaxKind, NodeVisitor } from "./ast"; import { BoltImportDirective, Syntax, BoltParameter, BoltModulePath, BoltReferenceExpression, BoltReferenceTypeExpression, BoltSourceFile, BoltCallExpression, BoltReturnKeyword, BoltReturnStatement, SyntaxKind, NodeVisitor, BoltSyntax, BoltIdentifier } from "./ast";
import { Program } from "./program"; import { Program } from "./program";
import { DiagnosticPrinter, E_FILE_NOT_FOUND, E_TYPES_NOT_ASSIGNABLE, E_DECLARATION_NOT_FOUND, E_TYPE_DECLARATION_NOT_FOUND, E_MUST_RETURN_A_VALUE } from "./diagnostics"; import { DiagnosticPrinter, E_FILE_NOT_FOUND, E_TYPES_NOT_ASSIGNABLE, E_DECLARATION_NOT_FOUND, E_TYPE_DECLARATION_NOT_FOUND, E_MUST_RETURN_A_VALUE, E_MAY_NOT_RETURN_A_VALUE } from "./diagnostics";
import { getSymbolPathFromNode } from "./resolver" import { getSymbolPathFromNode } from "./resolver"
import { inject } from "./di"; import { inject } from "./ioc";
import { SymbolResolver, ScopeType } from "./resolver"; import { SymbolResolver, ScopeType } from "./resolver";
import { assert } from "./util"; import { assert, every } from "./util";
import { emitNode } from "./emitter"; import { emitNode } from "./emitter";
import { TypeChecker, Type } from "./types"; import { TypeChecker, Type, ErrorType } from "./types";
import { getReturnStatementsInFunctionBody } from "./common"; import { getReturnStatementsInFunctionBody } from "./common";
import { errorMonitor } from "events";
export class CheckInvalidFilePaths extends NodeVisitor { export class CheckInvalidFilePaths extends NodeVisitor {
@ -41,24 +42,65 @@ export class CheckReferences extends NodeVisitor {
super(); super();
} }
private checkBoltModulePath(node: BoltModulePath, symbolKind: ScopeType) { private checkBoltModulePath(node: BoltSyntax, elements: BoltIdentifier[]) {
const scope = this.resolver.getScopeForNode(node, symbolKind);
assert(scope !== null); let modScope = this.resolver.getScopeForNode(node, ScopeType.Module);
const sym = this.resolver.resolveModulePath(node.elements.map(el => el.text), scope!); assert(modScope !== null);
if (sym === null) { let foundModule = false;
let partiallyMatchingModules = [];
// We will keep looping until we are at the topmost module of
// the package corresponding to `node`.
while (true) {
let failedToFindScope = false;
let currScope = modScope;
// Go through each of the parent names in normal order, resolving to the module
// that declared the name, and mark when we failed to look up the inner module.
for (const name of elements) {
const sym = currScope!.getLocalSymbol(name.text);;
if (sym === null) {
failedToFindScope = true;
partiallyMatchingModules.push((currScope!.source) as NodeScopeSource).node);
break;
}
assert(every(sym.declarations.values(), decl => decl.kind === SyntaxKind.BoltModule));
currScope = sym.scope;
}
// If the previous loop did not fail, that means we found a module.
if (!failedToFindScope) {
foundModule = true;
break;
}
// We continue the outer loop by going up one scope.
const nextScope = modScope!.getNextScope();
// If we are here and there are no scopes left to search in, then no scope had the given module.
if (nextScope === null) {
break;
}
modScope = nextScope;
}
if (!foundModule) {
this.diagnostics.add({ this.diagnostics.add({
message: E_DECLARATION_NOT_FOUND, message: E_DECLARATION_NOT_FOUND,
severity: 'error', severity: 'error',
args: { name: emitNode(node) }, args: { name: emitNode(node) },
node, node,
}); });
// TODO add informational diagnostics about the modules that provided a partial match
} }
} }
protected visitBoltReferenceExpression(node: BoltReferenceExpression) { protected visitBoltReferenceExpression(node: BoltReferenceExpression) {
if (node.modulePath !== null) { this.checkBoltModulePath(node.name, node.name.modulePath);
this.checkBoltModulePath(node.modulePath, ScopeType.Variable);
}
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable); const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
assert(scope !== null); assert(scope !== null);
const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!); const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!);
@ -75,14 +117,14 @@ export class CheckReferences extends NodeVisitor {
protected visitBoltReferenceTypeExpression(node: BoltReferenceTypeExpression) { protected visitBoltReferenceTypeExpression(node: BoltReferenceTypeExpression) {
const scope = this.resolver.getScopeForNode(node, ScopeType.Type); const scope = this.resolver.getScopeForNode(node, ScopeType.Type);
assert(scope !== null); assert(scope !== null);
const symbolPath = getSymbolPathFromNode(node.path); const symbolPath = getSymbolPathFromNode(node.name);
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!); const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
if (resolvedSym === null) { if (resolvedSym === null) {
this.diagnostics.add({ this.diagnostics.add({
message: E_TYPE_DECLARATION_NOT_FOUND, message: E_TYPE_DECLARATION_NOT_FOUND,
args: { name: emitNode(node.path) }, args: { name: emitNode(node.name) },
severity: 'error', severity: 'error',
node: node.path, node: node.name,
}) })
} }
} }
@ -92,59 +134,86 @@ export class CheckReferences extends NodeVisitor {
export class CheckTypeAssignments extends NodeVisitor { export class CheckTypeAssignments extends NodeVisitor {
constructor( constructor(@inject private diagnostics: DiagnosticPrinter) {
@inject private diagnostics: DiagnosticPrinter,
@inject private checker: TypeChecker,
) {
super(); super();
} }
protected visitBoltReturnStatement(node: BoltReturnStatement) { protected visitSyntax(node: Syntax) {
for (const error of node.errors) {
const fnDecl = node.getParentOfKind(SyntaxKind.BoltFunctionDeclaration)!; switch (error.type) {
case ErrorType.AssignmentError:
if (node.value === null) { this.diagnostics.add({
if (fnDecl.returnType !== null && this.checker.isVoid(fnDecl.returnType)) { message: E_TYPES_NOT_ASSIGNABLE,
this.diagnostics.add({ severity: 'error',
message: E_MUST_RETURN_A_VALUE, node: error.left,
node, });
severity: 'error', default:
}); throw new Error(`Could not add a diagnostic message for the error ${ErrorType[error.type]}`)
}
} else {
for (const error of this.checker.getAssignmentErrors(fnDecl.returnType, node.value)) {
this.diagnostics.add({
message: E_MUST_RETURN_A_VALUE,
node: node,
severity: 'error',
});
}
}
}
protected visitBoltParameter(node: BoltParameter) {
if (node.defaultValue !== null) {
for (const error of this.checker.getAssignmentErrors(node.bindings, node.defaultValue)) {
this.diagnostics.add({
severity: 'error',
message: E_TYPES_NOT_ASSIGNABLE,
args: { node: error.node }
});
}
}
}
protected visitBoltCallExpression(node: BoltCallExpression) {
for (const fnDecl of this.checker.getCallableFunctions(node)) {
for (const error of this.checker.getAssignmentErrors(fnDecl, node)) {
this.diagnostics.add({
severity: 'error',
message: E_TYPES_NOT_ASSIGNABLE,
args: { node: error.node },
});
} }
} }
} }
} }
//export class CheckTypeAssignments extends NodeVisitor {
// constructor(
// @inject private diagnostics: DiagnosticPrinter,
// @inject private checker: TypeChecker,
// ) {
// super();
// }
// protected visitBoltReturnStatement(node: BoltReturnStatement) {
// const fnDecl = node.getParentOfKind(SyntaxKind.BoltFunctionDeclaration)!;
// if ((this.checker.isVoidType(node) && !this.checker.isVoidType(fnDecl)) {
// this.diagnostics.add({
// message: E_MUST_RETURN_A_VALUE,
// node: node.value !== null ? node.value : node,
// severity: 'error',
// });
// } else if (!this.checker.isVoidType(node) && this.checker.isVoidType(fnDecl)) {
// this.diagnostics.add({
// message: E_MAY_NOT_RETURN_A_VALUE,
// node: node.value !== null ? node.value : node,
// severity: 'error',
// })
// } else {
// for (const error of this.checker.checkAssignment(fnDecl, node)) {
// this.diagnostics.add({
// message: E_TYPES_NOT_ASSIGNABLE,
// node: error.node,
// severity: 'error',
// });
// }
// }
// }
// protected visitBoltParameter(node: BoltParameter) {
// if (node.defaultValue !== null) {
// for (const error of this.checker.checkAssignment(node.bindings, node.defaultValue)) {
// this.diagnostics.add({
// severity: 'error',
// message: E_TYPES_NOT_ASSIGNABLE,
// node: error.node,
// });
// }
// }
// }
// protected visitBoltCallExpression(node: BoltCallExpression) {
// for (const fnDecl of this.checker.getCallableFunctions(node)) {
// for (const error of this.checker.checkAssignment(fnDecl, node)) {
// this.diagnostics.add({
// severity: 'error',
// message: E_TYPES_NOT_ASSIGNABLE,
// node: error.node,
// });
// }
// }
// }
//}

View file

@ -3,6 +3,7 @@ import chalk from "chalk"
import {Syntax} from "./ast"; import {Syntax} from "./ast";
import {format, MapLike, FormatArg, countDigits} from "./util"; import {format, MapLike, FormatArg, countDigits} from "./util";
export const E_MAY_NOT_RETURN_A_VALUE = "Returning a value inside a function that does not return values."
export const E_MUST_RETURN_A_VALUE = "The function must return a value on all control paths.";;;; export const E_MUST_RETURN_A_VALUE = "The function must return a value on all control paths.";;;;
export const E_FILE_NOT_FOUND = "A file named {filename} was not found."; export const E_FILE_NOT_FOUND = "A file named {filename} was not found.";
export const E_FIELD_HAS_INVALID_VERSION_NUMBER = "Field '{name}' contains an invalid version nunmber." export const E_FIELD_HAS_INVALID_VERSION_NUMBER = "Field '{name}' contains an invalid version nunmber."

View file

@ -15,11 +15,11 @@ export class Emitter {
case SyntaxKind.BoltQualName: case SyntaxKind.BoltQualName:
if (node.modulePath !== null) { if (node.modulePath !== null) {
if (node.modulePath.isAbsolute) { if (node.isAbsolute) {
out += '::' out += '::'
} }
for (const element of node.modulePath.elements) { for (const element of node.modulePath) {
out += '::' + element.text; out += element.text + '::';
} }
} }
out += this.emit(node.name); out += this.emit(node.name);

View file

@ -12,6 +12,10 @@ export class Record {
this.fields = new Map(fields); this.fields = new Map(fields);
} }
public getFields(): IterableIterator<[string, Value]> {
return this.fields[Symbol.iterator]();
}
public clone(): Record { public clone(): Record {
return new Record(this.fields); return new Record(this.fields);
} }

View file

@ -93,7 +93,8 @@ export class Frontend {
const checkers = checks.map(check => container.createInstance(check)); const checkers = checks.map(check => container.createInstance(check));
for (const sourceFile of program.getAllSourceFiles()) { for (const sourceFile of program.getAllSourceFiles()) {
resolver.registerSourceFile(sourceFile as BoltSourceFile); checker.registerSourceFile(sourceFile);
resolver.registerSourceFile(sourceFile);
} }
for (const sourceFile of program.getAllSourceFiles()) { for (const sourceFile of program.getAllSourceFiles()) {
sourceFile.visit(checkers) sourceFile.visit(checkers)

View file

@ -80,6 +80,7 @@ import {
createBoltModulePath, createBoltModulePath,
BoltModulePath, BoltModulePath,
isBoltSymbol, isBoltSymbol,
BoltIdentifierChild,
} from "./ast" } from "./ast"
import { parseForeignLanguage } from "./foreign" import { parseForeignLanguage } from "./foreign"
@ -109,8 +110,7 @@ function assertNoTokens(tokens: BoltTokenStream) {
} }
} }
const KIND_SYMBOL = [ const KIND_OPERATOR = [
SyntaxKind.BoltIdentifier,
SyntaxKind.BoltOperator, SyntaxKind.BoltOperator,
SyntaxKind.BoltVBar, SyntaxKind.BoltVBar,
SyntaxKind.BoltLtSign, SyntaxKind.BoltLtSign,
@ -124,7 +124,8 @@ const KIND_EXPRESSION_T0 = uniq([
SyntaxKind.BoltMatchKeyword, SyntaxKind.BoltMatchKeyword,
SyntaxKind.BoltQuoteKeyword, SyntaxKind.BoltQuoteKeyword,
SyntaxKind.BoltYieldKeyword, SyntaxKind.BoltYieldKeyword,
...KIND_SYMBOL, SyntaxKind.BoltIdentifier,
SyntaxKind.BoltParenthesized,
]) ])
const KIND_STATEMENT_T0 = uniq([ const KIND_STATEMENT_T0 = uniq([
@ -239,17 +240,41 @@ export class Parser {
public parseQualName(tokens: BoltTokenStream): BoltQualName { public parseQualName(tokens: BoltTokenStream): BoltQualName {
let modulePath = null; const firstToken = tokens.peek();
if (tokens.peek(2).kind === SyntaxKind.BoltDot) { let isAbsolute = false;
modulePath = this.parseModulePath(tokens); let modulePath = [];
let name;
if (tokens.peek().kind === SyntaxKind.BoltColonColon) {
isAbsolute = true;
tokens.get();
} }
const name = tokens.get(); while (true) {
assertToken(name, SyntaxKind.BoltIdentifier); const t1 = tokens.get();
const startNode = modulePath !== null ? modulePath : name; if (tokens.peek().kind === SyntaxKind.BoltColonColon) {
const endNode = name; assertToken(t1, SyntaxKind.BoltIdentifier);
const node = createBoltQualName(modulePath, name as BoltIdentifier, null); modulePath.push(t1 as BoltIdentifier);
setOrigNodeRange(node, startNode, endNode); tokens.get();
} else {
if (t1.kind === SyntaxKind.BoltParenthesized) {
const innerTokens = createTokenStream(t1);
const t2 = innerTokens.get();
if (!isBoltOperatorLike(t2)) {
throw new ParseError(t2, KIND_OPERATOR);
}
assertNoTokens(innerTokens);
name = t2;
} else if (t1.kind === SyntaxKind.BoltIdentifier) {
name = t1;
} else {
throw new ParseError(t1, [SyntaxKind.BoltParenthesized, SyntaxKind.BoltIdentifier]);
}
break;
}
}
const node = createBoltQualName(isAbsolute, modulePath, name as BoltIdentifier, null);
setOrigNodeRange(node, firstToken, name);
return node; return node;
} }
@ -395,15 +420,29 @@ export class Parser {
public parseReferenceTypeExpression(tokens: BoltTokenStream): BoltReferenceTypeExpression { public parseReferenceTypeExpression(tokens: BoltTokenStream): BoltReferenceTypeExpression {
const firstToken = tokens.peek(); const firstToken = tokens.peek();
let modulePath = null; let isAbsolute = false;
if (tokens.peek(2).kind === SyntaxKind.BoltColonColon) { let modulePath = [];
modulePath = this.parseModulePath(tokens) let name;
if (tokens.peek().kind === SyntaxKind.BoltColonColon) {
isAbsolute = true;
tokens.get();
} }
const t1 = tokens.get(); while (true) {
assertToken(t1, SyntaxKind.BoltIdentifier); const t1 = tokens.get();
let lastToken = t1; if (tokens.peek().kind === SyntaxKind.BoltColonColon) {
assertToken(t1, SyntaxKind.BoltIdentifier);
modulePath.push(t1 as BoltIdentifier);
tokens.get();
} else {
assertToken(t1, SyntaxKind.BoltIdentifier);
name = t1 as BoltIdentifier;
break;
}
}
let lastToken: BoltToken = name;
let typeArgs: BoltTypeExpression[] | null = null; let typeArgs: BoltTypeExpression[] | null = null;
if (tokens.peek().kind === SyntaxKind.BoltLtSign) { if (tokens.peek().kind === SyntaxKind.BoltLtSign) {
@ -428,7 +467,9 @@ export class Parser {
lastToken = t4; lastToken = t4;
} }
const node = createBoltReferenceTypeExpression(modulePath, t1 as BoltIdentifier, typeArgs); const qualName = createBoltQualName(isAbsolute, modulePath, name);
setOrigNodeRange(qualName, firstToken, modulePath.length > 0 ? modulePath[modulePath.length-1] : firstToken)
const node = createBoltReferenceTypeExpression(qualName, typeArgs);
setOrigNodeRange(node, firstToken, lastToken); setOrigNodeRange(node, firstToken, lastToken);
return node; return node;
} }
@ -536,35 +577,10 @@ export class Parser {
} }
public parseReferenceExpression(tokens: BoltTokenStream): BoltReferenceExpression { public parseReferenceExpression(tokens: BoltTokenStream): BoltReferenceExpression {
const firstToken = tokens.peek(); const firstToken = tokens.peek();
let isAbsolute = false; const name = this.parseQualName(tokens);
let elements = []; const node = createBoltReferenceExpression(name);
setOrigNodeRange(node, firstToken, name);
while (true) {
const t2 = tokens.peek(2);
if (t2.kind !== SyntaxKind.BoltColonColon) {
break;
}
const t1 = tokens.get();
assertToken(t1, SyntaxKind.BoltIdentifier);
elements.push(t1 as BoltIdentifier)
tokens.get();
}
let path = null;
if (elements.length > 0) {
path = createBoltModulePath(isAbsolute, elements);
setOrigNodeRange(path, elements[0], elements[elements.length-1]);
}
const t3 = tokens.get();
assertToken(t3, SyntaxKind.BoltIdentifier);
// TODO Add support for parsing parenthesized operators
const node = createBoltReferenceExpression(path, t3 as BoltIdentifier);
setOrigNodeRange(node, firstToken, t3);
return node; return node;
} }
@ -631,7 +647,7 @@ export class Parser {
} }
scanned.push(t2); scanned.push(t2);
} }
const result = createBoltQuoteExpression(scanned); const result = createBoltQuoteExpression(scanned as Token[]);
setOrigNodeRange(result, t0, t1); setOrigNodeRange(result, t0, t1);
return result; return result;
} }
@ -967,6 +983,7 @@ export class Parser {
public parseModuleDeclaration(tokens: BoltTokenStream): BoltModule { public parseModuleDeclaration(tokens: BoltTokenStream): BoltModule {
let modifiers = 0; let modifiers = 0;
let pathElements = [];
let t0 = tokens.get(); let t0 = tokens.get();
const firstToken = t0; const firstToken = t0;
@ -979,16 +996,24 @@ export class Parser {
throw new ParseError(t0, [SyntaxKind.BoltModKeyword]) throw new ParseError(t0, [SyntaxKind.BoltModKeyword])
} }
// FIXME should fail to parse absolute paths while (true) {
const name = this.parseModulePath(tokens); const t1 = tokens.get();
assertToken(t1, SyntaxKind.BoltIdentifier)
pathElements.push(t1 as BoltIdentifier)
const t2 = tokens.peek();
if (t2.kind !== SyntaxKind.BoltColonColon) {
break;
}
tokens.get();
}
const t1 = tokens.get(); const t1 = tokens.get();
if (t1.kind !== SyntaxKind.BoltBraced) { if (t1.kind !== SyntaxKind.BoltBraced) {
throw new ParseError(t1, [SyntaxKind.BoltBraced]) throw new ParseError(t1, [SyntaxKind.BoltBraced])
} }
const sentences = this.parseSourceElements(createTokenStream(t1)); const elements = this.parseSourceElements(createTokenStream(t1));
const node = createBoltModule(modifiers, name.elements, sentences); const node = createBoltModule(modifiers, pathElements, elements);
setOrigNodeRange(node, firstToken, t1); setOrigNodeRange(node, firstToken, t1);
return node; return node;
} }

View file

@ -30,8 +30,8 @@ export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath {
switch (node.kind) { switch (node.kind) {
case SyntaxKind.BoltReferenceExpression: case SyntaxKind.BoltReferenceExpression:
return new SymbolPath( return new SymbolPath(
node.modulePath === null ? [] : node.modulePath.elements.map(id => id.text), node.name.modulePath.map(id => id.text),
node.modulePath !== null && node.modulePath.isAbsolute, node.name.isAbsolute,
emitNode(node.name), emitNode(node.name),
); );
case SyntaxKind.BoltIdentifier: case SyntaxKind.BoltIdentifier:
@ -41,7 +41,7 @@ export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath {
if (node.modulePath === null) { if (node.modulePath === null) {
return new SymbolPath([], false, name); return new SymbolPath([], false, name);
} }
return new SymbolPath(node.modulePath.elements.map(id => id.text), false, name); return new SymbolPath(node.modulePath.map(id => id.text), false, name);
case SyntaxKind.BoltModulePath: case SyntaxKind.BoltModulePath:
return new SymbolPath( return new SymbolPath(
node.elements.slice(0, -1).map(el => el.text), node.elements.slice(0, -1).map(el => el.text),
@ -104,7 +104,7 @@ class NodeScopeSource implements ScopeSource {
interface ResolutionStrategy { interface ResolutionStrategy {
getSymbolName(node: Syntax): string; getSymbolName(node: Syntax): string;
getScopeType(node: Syntax): ScopeType; getScopeTypes(node: Syntax): ScopeType[];
getNextScopeSource(source: ScopeSource, kind: ScopeType): ScopeSource | null; getNextScopeSource(source: ScopeSource, kind: ScopeType): ScopeSource | null;
} }
@ -425,12 +425,12 @@ export class SymbolResolver {
} }
} }
public getScopeSurroundingNode(node: Syntax, kind: ScopeType = this.strategy.getScopeType(node)): Scope | null { public getScopeSurroundingNode(node: Syntax, kind: ScopeType): Scope | null {
assert(node.parentNode !== null); assert(node.parentNode !== null);
return this.getScopeForNode(node.parentNode!, kind); return this.getScopeForNode(node.parentNode!, kind);
} }
public getScopeForNode(node: Syntax, kind: ScopeType = this.strategy.getScopeType(node)): Scope | null { public getScopeForNode(node: Syntax, kind: ScopeType): Scope | null {
let source: ScopeSource = new NodeScopeSource(node); let source: ScopeSource = new NodeScopeSource(node);
if (!this.strategy.introducesNewScope(source, kind)) { if (!this.strategy.introducesNewScope(source, kind)) {
const nextSource = this.strategy.getNextScopeSource(source, kind); const nextSource = this.strategy.getNextScopeSource(source, kind);
@ -482,7 +482,7 @@ export class SymbolResolver {
return null; return null;
} }
public getSymbolForNode(node: Syntax, kind: ScopeType = this.strategy.getScopeType(node)) { public getSymbolForNode(node: Syntax, kind: ScopeType) {
assert(this.strategy.hasSymbol(node)); assert(this.strategy.hasSymbol(node));
const scope = this.getScopeForNode(node, kind); const scope = this.getScopeForNode(node, kind);
if (scope === null) { if (scope === null) {

View file

@ -117,6 +117,11 @@ function createNode(nodeType) {
return this.__NODE_TYPE.index; return this.__NODE_TYPE.index;
} }
}); });
Object.defineProperty(obj, 'errors', {
enumerable: false,
configurable: true,
value: [],
})
Object.defineProperty(obj, 'id', { Object.defineProperty(obj, 'id', {
enumerable: true, enumerable: true,
configurable: true, configurable: true,

View file

@ -1,5 +1,5 @@
import { TypeInfo } from "./types" import { Type } from "./checker"
import { Package } from "./common" import { Package } from "./common"
import { TextSpan } from "./text" import { TextSpan } from "./text"
@ -12,7 +12,8 @@ export function isSyntax(value: any): value is Syntax;
interface SyntaxBase { interface SyntaxBase {
id: number; id: number;
kind: SyntaxKind; kind: SyntaxKind;
_typeInfo: TypeInfo; type?: Type;
errors: CompileError[]
parentNode: Syntax | null; parentNode: Syntax | null;
span: TextSpan | null; span: TextSpan | null;
visit(visitors: NodeVisitor[]): void; visit(visitors: NodeVisitor[]): void;

View file

@ -356,7 +356,7 @@ export function generateAST(decls: Declaration[]) {
throw new Error(`Could not emit TypeScript type for reference type node named ${typeNode.name}`); throw new Error(`Could not emit TypeScript type for reference type node named ${typeNode.name}`);
} }
} else if (typeNode.type === 'UnionTypeNode') { } else if (typeNode.type === 'UnionTypeNode') {
return typeNode.elements.map(emitTypeScriptType).join(' | '); return '(' + typeNode.elements.map(emitTypeScriptType).join(' | ') + ')';
} }
throw new Error(`Could not emit TypeScript type for type node ${typeNode}`); throw new Error(`Could not emit TypeScript type for type node ${typeNode}`);
} }

View file

@ -1,8 +1,12 @@
import { FastStringMap, assert, isPlainObject } from "./util"; import { FastStringMap, assert, isPlainObject } from "./util";
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString } from "./ast"; import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, isBoltMacroCall, BoltTypeExpression } from "./ast";
import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo } from "./resolver"; import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo } from "./resolver";
import { Value } from "./evaluator"; import { Value, Record } from "./evaluator";
// TODO For function bodies, we can do something special.
// Sort the return types and find the largest types, eliminating types that fall under other types.
// Next, add the resulting types as type hints to `fnReturnType`.
enum TypeKind { enum TypeKind {
OpaqueType, OpaqueType,
@ -24,6 +28,7 @@ export type Type
| RecordType | RecordType
| VariantType | VariantType
| TupleType | TupleType
| UnionType
abstract class TypeBase { abstract class TypeBase {
@ -91,6 +96,18 @@ export class UnionType extends TypeBase {
public kind: TypeKind.UnionType = TypeKind.UnionType; public kind: TypeKind.UnionType = TypeKind.UnionType;
constructor(private elements: Type[] = []) {
super();
}
public addElement(element: Type): void {
this.elements.push(element);
}
public getElements(): IterableIterator<Type> {
return this.elements[Symbol.iterator]();
}
} }
export type RecordFieldType export type RecordFieldType
@ -140,6 +157,19 @@ export class RecordType {
} }
export enum ErrorType {
AssignmentError,
}
interface AssignmentError {
type: ErrorType.AssignmentError;
left: Syntax;
right: Syntax;
}
export type CompileError
= AssignmentError
export class TupleType extends TypeBase { export class TupleType extends TypeBase {
kind: TypeKind.TupleType = TypeKind.TupleType; kind: TypeKind.TupleType = TypeKind.TupleType;
@ -180,17 +210,17 @@ export class TupleType extends TypeBase {
// return new NeverType(); // return new NeverType();
//} //}
interface AssignmentError {
node: Syntax;
}
export class TypeChecker { export class TypeChecker {
private opaqueTypes = new FastStringMap<number, OpaqueType>(); private opaqueTypes = new FastStringMap<number, OpaqueType>();
private anyType = new AnyType();
private stringType = new OpaqueType(); private stringType = new OpaqueType();
private intType = new OpaqueType(); private intType = new OpaqueType();
private floatType = new OpaqueType(); private floatType = new OpaqueType();
private voidType = new OpaqueType();
private syntaxType = new UnionType(); // FIXME
constructor(private resolver: SymbolResolver) { constructor(private resolver: SymbolResolver) {
@ -203,10 +233,10 @@ export class TypeChecker {
return this.intType; return this.intType;
} else if (typeof(value) === 'number') { } else if (typeof(value) === 'number') {
return this.floatType; return this.floatType;
} else if (isPlainObject(value)) { } else if (value instanceof Record) {
const recordType = new RecordType() const recordType = new RecordType()
for (const key of Object.keys(value)) { for (const [fieldName, fieldValue] of value.getFields()) {
recordType.addField(key, new PlainRecordFieldType(this.getTypeOfValue(value[key]))); recordType.addField(name, new PlainRecordFieldType(this.getTypeOfValue(fieldValue)));
} }
return recordType; return recordType;
} else { } else {
@ -214,46 +244,108 @@ export class TypeChecker {
} }
} }
public getTypeOfNode(node: Syntax) { public registerSourceFile(sourceFile: SourceFile): void {
for (const node of sourceFile.preorder()) {
if (isBoltMacroCall(node)) {
continue; // FIXME only continue when we're not in an expression context
}
if (isBoltExpression(node)) {
node.type = this.createInitialTypeForExpression(node);
}
}
}
private createInitialTypeForExpression(node: Syntax): Type {
if (node.type !== undefined) {
return node.type;
}
let resultType;
switch (node.kind) { switch (node.kind) {
case SyntaxKind.BoltMatchExpression:
{
const unionType = new UnionType();
for (const matchArm of node.arms) {
unionType.addElement(this.createInitialTypeForExpression(matchArm.body));
}
resultType = unionType;
break;
}
case SyntaxKind.BoltRecordDeclaration: case SyntaxKind.BoltRecordDeclaration:
{ {
const recordSym = this.resolver.getSymbolForNode(node); const recordSym = this.resolver.getSymbolForNode(node, ScopeType.Type);
assert(recordSym !== null); assert(recordSym !== null);
if (this.opaqueTypes.has(recordSym!.id)) { if (this.opaqueTypes.has(recordSym!.id)) {
return this.opaqueTypes.get(recordSym!.id); resultType = this.opaqueTypes.get(recordSym!.id);
} else {
const opaqueType = new OpaqueType(recordSym!);
this.opaqueTypes.set(recordSym!.id, opaqueType);
resultType = opaqueType;
} }
const opaqueType = new OpaqueType(recordSym!); break;
this.opaqueTypes.set(recordSym!.id, opaqueType); }
case SyntaxKind.BoltFunctionExpression:
{
const paramTypes = node.params.map(param => {
if (param.type === null) {
return this.anyType;
}
return this.createInitialTypeForTypeExpression(param.type);
});
let returnType = node.returnType === null
? this.anyType
: this.createInitialTypeForTypeExpression(node.returnType);
const funcType = new FunctionType(paramTypes, returnType);
break;
}
case SyntaxKind.BoltQuoteExpression:
return this.syntaxType;
case SyntaxKind.BoltMemberExpression:
case SyntaxKind.BoltReferenceExpression:
case SyntaxKind.BoltCallExpression:
case SyntaxKind.BoltBlockExpression:
{
resultType = this.anyType;
break; break;
} }
case SyntaxKind.BoltConstantExpression: case SyntaxKind.BoltConstantExpression:
return this.getTypeOfValue(node.value); {
resultType = this.getTypeOfValue(node.value);
break;
}
}
}
public isVoid(node: Syntax): boolean {
switch (node.kind) {
case SyntaxKind.BoltTupleExpression:
return node.elements.length === 0;
default: default:
throw new Error(`Could not determine whether the given type resolves to the void type.`) throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`);
}
node.type = resultType;
return resultType;
}
private createInitialTypeForTypeExpression(node: BoltTypeExpression): Type {
switch (node.kind) {
case SyntaxKind.BoltLiftedTypeExpression:
return this.createInitialTypeForExpression(node.expression);
default:
throw new Error(`Could not create a type for node ${kindToString(node.kind)}.`);
} }
} }
public *getAssignmentErrors(left: Syntax, right: Syntax): IterableIterator<AssignmentError> { public isVoidType(type: Type): boolean {
return type === this.voidType;
// TODO For function bodies, we can do something special.
// Sort the return types and find the largest types, eliminating types that fall under other types.
// Next, add the resulting types as type hints to `fnReturnType`.
} }
public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] { public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] {
const resolver = this.resolver; const resolver = this.resolver;