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:
parent
f765ba6115
commit
05b024c3f4
13 changed files with 1461 additions and 328 deletions
|
@ -91,7 +91,8 @@ node BoltSourceFile > SourceFile {
|
|||
}
|
||||
|
||||
node BoltQualName {
|
||||
modulePath: Option<BoltModulePath>,
|
||||
isAbsolute: bool,
|
||||
modulePath: Vec<BoltIdentifier>,
|
||||
name: BoltSymbol,
|
||||
}
|
||||
|
||||
|
@ -103,8 +104,7 @@ node BoltModulePath {
|
|||
node BoltTypeExpression;
|
||||
|
||||
node BoltReferenceTypeExpression > BoltTypeExpression {
|
||||
modulePath: Option<BoltModulePath>,
|
||||
name: BoltIdentifier,
|
||||
name: BoltQualName,
|
||||
arguments: Option<Vec<BoltTypeExpression>>,
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,10 @@ node BoltFunctionTypeExpression > BoltTypeExpression {
|
|||
returnType: Option<BoltTypeExpression>,
|
||||
}
|
||||
|
||||
node BoltLiftedTypeExpression > BoltTypeExpression {
|
||||
expression: BoltExpression,
|
||||
}
|
||||
|
||||
node BoltTypeParameter {
|
||||
index: usize,
|
||||
name: BoltIdentifier,
|
||||
|
@ -166,8 +170,7 @@ node BoltTupleExpression > BoltExpression {
|
|||
}
|
||||
|
||||
node BoltReferenceExpression > BoltExpression {
|
||||
modulePath: Option<BoltModulePath>,
|
||||
name: BoltSymbol,
|
||||
name: BoltQualName,
|
||||
}
|
||||
|
||||
node BoltMemberExpression > BoltExpression {
|
||||
|
@ -284,7 +287,8 @@ node BoltVariableDeclaration > BoltFunctionBodyElement, BoltDeclaration {
|
|||
node BoltImportSymbol;
|
||||
|
||||
node BoltPlainImportSymbol > BoltImportSymbol {
|
||||
name: BoltQualName,
|
||||
remote: BoltQualName,
|
||||
local: BoltSymbol,
|
||||
}
|
||||
|
||||
node BoltImportDirective > BoltSourceElement {
|
||||
|
@ -296,7 +300,8 @@ node BoltImportDirective > BoltSourceElement {
|
|||
node BoltExportSymbol;
|
||||
|
||||
node BoltPlainExportSymbol {
|
||||
name: BoltQualName,
|
||||
local: BoltQualName,
|
||||
remote: BoltSymbol,
|
||||
}
|
||||
|
||||
node BoltExportDirective > BoltSourceElement {
|
||||
|
|
1250
src/ast.d.ts
vendored
1250
src/ast.d.ts
vendored
File diff suppressed because it is too large
Load diff
197
src/checks.ts
197
src/checks.ts
|
@ -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 { 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 { inject } from "./di";
|
||||
import { inject } from "./ioc";
|
||||
import { SymbolResolver, ScopeType } from "./resolver";
|
||||
import { assert } from "./util";
|
||||
import { assert, every } from "./util";
|
||||
import { emitNode } from "./emitter";
|
||||
import { TypeChecker, Type } from "./types";
|
||||
import { TypeChecker, Type, ErrorType } from "./types";
|
||||
import { getReturnStatementsInFunctionBody } from "./common";
|
||||
import { errorMonitor } from "events";
|
||||
|
||||
export class CheckInvalidFilePaths extends NodeVisitor {
|
||||
|
||||
|
@ -41,24 +42,65 @@ export class CheckReferences extends NodeVisitor {
|
|||
super();
|
||||
}
|
||||
|
||||
private checkBoltModulePath(node: BoltModulePath, symbolKind: ScopeType) {
|
||||
const scope = this.resolver.getScopeForNode(node, symbolKind);
|
||||
assert(scope !== null);
|
||||
const sym = this.resolver.resolveModulePath(node.elements.map(el => el.text), scope!);
|
||||
if (sym === null) {
|
||||
private checkBoltModulePath(node: BoltSyntax, elements: BoltIdentifier[]) {
|
||||
|
||||
let modScope = this.resolver.getScopeForNode(node, ScopeType.Module);
|
||||
assert(modScope !== 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({
|
||||
message: E_DECLARATION_NOT_FOUND,
|
||||
severity: 'error',
|
||||
args: { name: emitNode(node) },
|
||||
node,
|
||||
});
|
||||
// TODO add informational diagnostics about the modules that provided a partial match
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected visitBoltReferenceExpression(node: BoltReferenceExpression) {
|
||||
if (node.modulePath !== null) {
|
||||
this.checkBoltModulePath(node.modulePath, ScopeType.Variable);
|
||||
}
|
||||
this.checkBoltModulePath(node.name, node.name.modulePath);
|
||||
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
|
||||
assert(scope !== null);
|
||||
const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!);
|
||||
|
@ -75,14 +117,14 @@ export class CheckReferences extends NodeVisitor {
|
|||
protected visitBoltReferenceTypeExpression(node: BoltReferenceTypeExpression) {
|
||||
const scope = this.resolver.getScopeForNode(node, ScopeType.Type);
|
||||
assert(scope !== null);
|
||||
const symbolPath = getSymbolPathFromNode(node.path);
|
||||
const symbolPath = getSymbolPathFromNode(node.name);
|
||||
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
|
||||
if (resolvedSym === null) {
|
||||
this.diagnostics.add({
|
||||
message: E_TYPE_DECLARATION_NOT_FOUND,
|
||||
args: { name: emitNode(node.path) },
|
||||
args: { name: emitNode(node.name) },
|
||||
severity: 'error',
|
||||
node: node.path,
|
||||
node: node.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -92,59 +134,86 @@ export class CheckReferences extends NodeVisitor {
|
|||
|
||||
export class CheckTypeAssignments extends NodeVisitor {
|
||||
|
||||
constructor(
|
||||
@inject private diagnostics: DiagnosticPrinter,
|
||||
@inject private checker: TypeChecker,
|
||||
) {
|
||||
constructor(@inject private diagnostics: DiagnosticPrinter) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected visitBoltReturnStatement(node: BoltReturnStatement) {
|
||||
|
||||
const fnDecl = node.getParentOfKind(SyntaxKind.BoltFunctionDeclaration)!;
|
||||
|
||||
if (node.value === null) {
|
||||
if (fnDecl.returnType !== null && this.checker.isVoid(fnDecl.returnType)) {
|
||||
this.diagnostics.add({
|
||||
message: E_MUST_RETURN_A_VALUE,
|
||||
node,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
} 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 },
|
||||
});
|
||||
protected visitSyntax(node: Syntax) {
|
||||
for (const error of node.errors) {
|
||||
switch (error.type) {
|
||||
case ErrorType.AssignmentError:
|
||||
this.diagnostics.add({
|
||||
message: E_TYPES_NOT_ASSIGNABLE,
|
||||
severity: 'error',
|
||||
node: error.left,
|
||||
});
|
||||
default:
|
||||
throw new Error(`Could not add a diagnostic message for the error ${ErrorType[error.type]}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//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,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
//}
|
||||
|
|
|
@ -3,6 +3,7 @@ import chalk from "chalk"
|
|||
import {Syntax} from "./ast";
|
||||
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_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."
|
||||
|
|
|
@ -15,11 +15,11 @@ export class Emitter {
|
|||
|
||||
case SyntaxKind.BoltQualName:
|
||||
if (node.modulePath !== null) {
|
||||
if (node.modulePath.isAbsolute) {
|
||||
if (node.isAbsolute) {
|
||||
out += '::'
|
||||
}
|
||||
for (const element of node.modulePath.elements) {
|
||||
out += '::' + element.text;
|
||||
for (const element of node.modulePath) {
|
||||
out += element.text + '::';
|
||||
}
|
||||
}
|
||||
out += this.emit(node.name);
|
||||
|
|
|
@ -12,6 +12,10 @@ export class Record {
|
|||
this.fields = new Map(fields);
|
||||
}
|
||||
|
||||
public getFields(): IterableIterator<[string, Value]> {
|
||||
return this.fields[Symbol.iterator]();
|
||||
}
|
||||
|
||||
public clone(): Record {
|
||||
return new Record(this.fields);
|
||||
}
|
||||
|
|
|
@ -93,7 +93,8 @@ export class Frontend {
|
|||
const checkers = checks.map(check => container.createInstance(check));
|
||||
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
resolver.registerSourceFile(sourceFile as BoltSourceFile);
|
||||
checker.registerSourceFile(sourceFile);
|
||||
resolver.registerSourceFile(sourceFile);
|
||||
}
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
sourceFile.visit(checkers)
|
||||
|
|
129
src/parser.ts
129
src/parser.ts
|
@ -80,6 +80,7 @@ import {
|
|||
createBoltModulePath,
|
||||
BoltModulePath,
|
||||
isBoltSymbol,
|
||||
BoltIdentifierChild,
|
||||
} from "./ast"
|
||||
|
||||
import { parseForeignLanguage } from "./foreign"
|
||||
|
@ -109,8 +110,7 @@ function assertNoTokens(tokens: BoltTokenStream) {
|
|||
}
|
||||
}
|
||||
|
||||
const KIND_SYMBOL = [
|
||||
SyntaxKind.BoltIdentifier,
|
||||
const KIND_OPERATOR = [
|
||||
SyntaxKind.BoltOperator,
|
||||
SyntaxKind.BoltVBar,
|
||||
SyntaxKind.BoltLtSign,
|
||||
|
@ -124,7 +124,8 @@ const KIND_EXPRESSION_T0 = uniq([
|
|||
SyntaxKind.BoltMatchKeyword,
|
||||
SyntaxKind.BoltQuoteKeyword,
|
||||
SyntaxKind.BoltYieldKeyword,
|
||||
...KIND_SYMBOL,
|
||||
SyntaxKind.BoltIdentifier,
|
||||
SyntaxKind.BoltParenthesized,
|
||||
])
|
||||
|
||||
const KIND_STATEMENT_T0 = uniq([
|
||||
|
@ -239,17 +240,41 @@ export class Parser {
|
|||
|
||||
public parseQualName(tokens: BoltTokenStream): BoltQualName {
|
||||
|
||||
let modulePath = null;
|
||||
if (tokens.peek(2).kind === SyntaxKind.BoltDot) {
|
||||
modulePath = this.parseModulePath(tokens);
|
||||
const firstToken = tokens.peek();
|
||||
let isAbsolute = false;
|
||||
let modulePath = [];
|
||||
let name;
|
||||
if (tokens.peek().kind === SyntaxKind.BoltColonColon) {
|
||||
isAbsolute = true;
|
||||
tokens.get();
|
||||
}
|
||||
|
||||
const name = tokens.get();
|
||||
assertToken(name, SyntaxKind.BoltIdentifier);
|
||||
const startNode = modulePath !== null ? modulePath : name;
|
||||
const endNode = name;
|
||||
const node = createBoltQualName(modulePath, name as BoltIdentifier, null);
|
||||
setOrigNodeRange(node, startNode, endNode);
|
||||
while (true) {
|
||||
const t1 = tokens.get();
|
||||
if (tokens.peek().kind === SyntaxKind.BoltColonColon) {
|
||||
assertToken(t1, SyntaxKind.BoltIdentifier);
|
||||
modulePath.push(t1 as BoltIdentifier);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -395,15 +420,29 @@ export class Parser {
|
|||
public parseReferenceTypeExpression(tokens: BoltTokenStream): BoltReferenceTypeExpression {
|
||||
|
||||
const firstToken = tokens.peek();
|
||||
let modulePath = null;
|
||||
if (tokens.peek(2).kind === SyntaxKind.BoltColonColon) {
|
||||
modulePath = this.parseModulePath(tokens)
|
||||
let isAbsolute = false;
|
||||
let modulePath = [];
|
||||
let name;
|
||||
|
||||
if (tokens.peek().kind === SyntaxKind.BoltColonColon) {
|
||||
isAbsolute = true;
|
||||
tokens.get();
|
||||
}
|
||||
|
||||
const t1 = tokens.get();
|
||||
assertToken(t1, SyntaxKind.BoltIdentifier);
|
||||
let lastToken = t1;
|
||||
while (true) {
|
||||
const t1 = tokens.get();
|
||||
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;
|
||||
|
||||
if (tokens.peek().kind === SyntaxKind.BoltLtSign) {
|
||||
|
@ -428,7 +467,9 @@ export class Parser {
|
|||
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);
|
||||
return node;
|
||||
}
|
||||
|
@ -536,35 +577,10 @@ export class Parser {
|
|||
}
|
||||
|
||||
public parseReferenceExpression(tokens: BoltTokenStream): BoltReferenceExpression {
|
||||
|
||||
const firstToken = tokens.peek();
|
||||
let isAbsolute = false;
|
||||
let elements = [];
|
||||
|
||||
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);
|
||||
const name = this.parseQualName(tokens);
|
||||
const node = createBoltReferenceExpression(name);
|
||||
setOrigNodeRange(node, firstToken, name);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -631,7 +647,7 @@ export class Parser {
|
|||
}
|
||||
scanned.push(t2);
|
||||
}
|
||||
const result = createBoltQuoteExpression(scanned);
|
||||
const result = createBoltQuoteExpression(scanned as Token[]);
|
||||
setOrigNodeRange(result, t0, t1);
|
||||
return result;
|
||||
}
|
||||
|
@ -967,6 +983,7 @@ export class Parser {
|
|||
public parseModuleDeclaration(tokens: BoltTokenStream): BoltModule {
|
||||
|
||||
let modifiers = 0;
|
||||
let pathElements = [];
|
||||
|
||||
let t0 = tokens.get();
|
||||
const firstToken = t0;
|
||||
|
@ -979,16 +996,24 @@ export class Parser {
|
|||
throw new ParseError(t0, [SyntaxKind.BoltModKeyword])
|
||||
}
|
||||
|
||||
// FIXME should fail to parse absolute paths
|
||||
const name = this.parseModulePath(tokens);
|
||||
while (true) {
|
||||
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();
|
||||
if (t1.kind !== 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);
|
||||
return node;
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath {
|
|||
switch (node.kind) {
|
||||
case SyntaxKind.BoltReferenceExpression:
|
||||
return new SymbolPath(
|
||||
node.modulePath === null ? [] : node.modulePath.elements.map(id => id.text),
|
||||
node.modulePath !== null && node.modulePath.isAbsolute,
|
||||
node.name.modulePath.map(id => id.text),
|
||||
node.name.isAbsolute,
|
||||
emitNode(node.name),
|
||||
);
|
||||
case SyntaxKind.BoltIdentifier:
|
||||
|
@ -41,7 +41,7 @@ export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath {
|
|||
if (node.modulePath === null) {
|
||||
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:
|
||||
return new SymbolPath(
|
||||
node.elements.slice(0, -1).map(el => el.text),
|
||||
|
@ -104,7 +104,7 @@ class NodeScopeSource implements ScopeSource {
|
|||
|
||||
interface ResolutionStrategy {
|
||||
getSymbolName(node: Syntax): string;
|
||||
getScopeType(node: Syntax): ScopeType;
|
||||
getScopeTypes(node: Syntax): ScopeType[];
|
||||
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);
|
||||
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);
|
||||
if (!this.strategy.introducesNewScope(source, kind)) {
|
||||
const nextSource = this.strategy.getNextScopeSource(source, kind);
|
||||
|
@ -482,7 +482,7 @@ export class SymbolResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
public getSymbolForNode(node: Syntax, kind: ScopeType = this.strategy.getScopeType(node)) {
|
||||
public getSymbolForNode(node: Syntax, kind: ScopeType) {
|
||||
assert(this.strategy.hasSymbol(node));
|
||||
const scope = this.getScopeForNode(node, kind);
|
||||
if (scope === null) {
|
||||
|
|
|
@ -117,6 +117,11 @@ function createNode(nodeType) {
|
|||
return this.__NODE_TYPE.index;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(obj, 'errors', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: [],
|
||||
})
|
||||
Object.defineProperty(obj, 'id', {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import { TypeInfo } from "./types"
|
||||
import { Type } from "./checker"
|
||||
import { Package } from "./common"
|
||||
import { TextSpan } from "./text"
|
||||
|
||||
|
@ -12,7 +12,8 @@ export function isSyntax(value: any): value is Syntax;
|
|||
interface SyntaxBase {
|
||||
id: number;
|
||||
kind: SyntaxKind;
|
||||
_typeInfo: TypeInfo;
|
||||
type?: Type;
|
||||
errors: CompileError[]
|
||||
parentNode: Syntax | null;
|
||||
span: TextSpan | null;
|
||||
visit(visitors: NodeVisitor[]): void;
|
||||
|
|
|
@ -356,7 +356,7 @@ export function generateAST(decls: Declaration[]) {
|
|||
throw new Error(`Could not emit TypeScript type for reference type node named ${typeNode.name}`);
|
||||
}
|
||||
} 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}`);
|
||||
}
|
||||
|
|
154
src/types.ts
154
src/types.ts
|
@ -1,8 +1,12 @@
|
|||
|
||||
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 { 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 {
|
||||
OpaqueType,
|
||||
|
@ -24,6 +28,7 @@ export type Type
|
|||
| RecordType
|
||||
| VariantType
|
||||
| TupleType
|
||||
| UnionType
|
||||
|
||||
abstract class TypeBase {
|
||||
|
||||
|
@ -91,6 +96,18 @@ export class UnionType extends TypeBase {
|
|||
|
||||
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
|
||||
|
@ -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 {
|
||||
|
||||
kind: TypeKind.TupleType = TypeKind.TupleType;
|
||||
|
@ -180,17 +210,17 @@ export class TupleType extends TypeBase {
|
|||
// return new NeverType();
|
||||
//}
|
||||
|
||||
interface AssignmentError {
|
||||
node: Syntax;
|
||||
}
|
||||
|
||||
export class TypeChecker {
|
||||
|
||||
private opaqueTypes = new FastStringMap<number, OpaqueType>();
|
||||
|
||||
private anyType = new AnyType();
|
||||
private stringType = new OpaqueType();
|
||||
private intType = new OpaqueType();
|
||||
private floatType = new OpaqueType();
|
||||
private voidType = new OpaqueType();
|
||||
|
||||
private syntaxType = new UnionType(); // FIXME
|
||||
|
||||
constructor(private resolver: SymbolResolver) {
|
||||
|
||||
|
@ -203,10 +233,10 @@ export class TypeChecker {
|
|||
return this.intType;
|
||||
} else if (typeof(value) === 'number') {
|
||||
return this.floatType;
|
||||
} else if (isPlainObject(value)) {
|
||||
} else if (value instanceof Record) {
|
||||
const recordType = new RecordType()
|
||||
for (const key of Object.keys(value)) {
|
||||
recordType.addField(key, new PlainRecordFieldType(this.getTypeOfValue(value[key])));
|
||||
for (const [fieldName, fieldValue] of value.getFields()) {
|
||||
recordType.addField(name, new PlainRecordFieldType(this.getTypeOfValue(fieldValue)));
|
||||
}
|
||||
return recordType;
|
||||
} 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) {
|
||||
|
||||
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:
|
||||
{
|
||||
const recordSym = this.resolver.getSymbolForNode(node);
|
||||
const recordSym = this.resolver.getSymbolForNode(node, ScopeType.Type);
|
||||
assert(recordSym !== null);
|
||||
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!);
|
||||
this.opaqueTypes.set(recordSym!.id, opaqueType);
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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:
|
||||
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> {
|
||||
|
||||
// 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 isVoidType(type: Type): boolean {
|
||||
return type === this.voidType;
|
||||
}
|
||||
|
||||
|
||||
public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] {
|
||||
|
||||
const resolver = this.resolver;
|
||||
|
|
Loading…
Reference in a new issue