Improve symbol resolution algorithms in type checker

This commit is contained in:
Sam Vervaeck 2020-05-25 15:52:11 +02:00
parent 17d91ef36c
commit e0566233c0
12 changed files with 535 additions and 287 deletions

View file

@ -277,7 +277,7 @@ node BoltPlainImportSymbol > BoltImportSymbol {
node BoltImportDirective > BoltSourceElement { node BoltImportDirective > BoltSourceElement {
modifiers: BoltModifiers, modifiers: BoltModifiers,
file: String, file: BoltStringLiteral,
symbols: Vec<BoltImportSymbol>, symbols: Vec<BoltImportSymbol>,
} }

43
src/ast.d.ts vendored
View file

@ -1,8 +1,28 @@
import { Type } from "./types"
import { ScopeInfo } from "./checker"
import { Package } from "./common" import { Package } from "./common"
import { TextSpan } from "./text"
export function setParents(node: Syntax): void;
export type SyntaxRange = [Syntax, Syntax];
export function isSyntax(value: any): value is Syntax; export function isSyntax(value: any): value is Syntax;
interface SyntaxBase<K extends SyntaxKind> {
id: number;
_scope?: ScopeInfo;
kind: K;
parentNode: ParentTypesOf<K> | null;
span: TextSpan | null;
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
}
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
export const enum SyntaxKind { export const enum SyntaxKind {
EndOfFile = 2, EndOfFile = 2,
@ -158,25 +178,6 @@ export const enum SyntaxKind {
JSSourceFile = 182, JSSourceFile = 182,
} }
import { TextSpan } from "./text"
export function setParents(node: Syntax): void;
export type SyntaxRange = [Syntax, Syntax];
interface SyntaxBase<K extends SyntaxKind> {
id: number;
kind: K;
parentNode: ParentTypesOf<K> | null;
span: TextSpan | null;
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
}
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
export interface EndOfFile extends SyntaxBase<SyntaxKind.EndOfFile> { export interface EndOfFile extends SyntaxBase<SyntaxKind.EndOfFile> {
kind: SyntaxKind.EndOfFile; kind: SyntaxKind.EndOfFile;
} }
@ -824,7 +825,7 @@ export interface BoltPlainImportSymbol extends SyntaxBase<SyntaxKind.BoltPlainIm
export interface BoltImportDirective extends SyntaxBase<SyntaxKind.BoltImportDirective> { export interface BoltImportDirective extends SyntaxBase<SyntaxKind.BoltImportDirective> {
kind: SyntaxKind.BoltImportDirective; kind: SyntaxKind.BoltImportDirective;
modifiers: BoltModifiers; modifiers: BoltModifiers;
file: string; file: BoltStringLiteral;
symbols: BoltImportSymbol[]; symbols: BoltImportSymbol[];
} }
@ -1705,7 +1706,7 @@ export function createBoltModule(modifiers: BoltModifiers, name: BoltQualName, e
export function createBoltFunctionDeclaration(modifiers: BoltModifiers, target: string, name: BoltSymbol, params: BoltParameter[], returnType: BoltTypeExpression | null, typeParams: BoltTypeParameter[] | null, body: BoltFunctionBodyElement[], span?: TextSpan | null): BoltFunctionDeclaration; export function createBoltFunctionDeclaration(modifiers: BoltModifiers, target: string, name: BoltSymbol, params: BoltParameter[], returnType: BoltTypeExpression | null, typeParams: BoltTypeParameter[] | null, body: BoltFunctionBodyElement[], span?: TextSpan | null): BoltFunctionDeclaration;
export function createBoltVariableDeclaration(modifiers: BoltModifiers, bindings: BoltPattern, type: BoltTypeExpression | null, value: BoltExpression | null, span?: TextSpan | null): BoltVariableDeclaration; export function createBoltVariableDeclaration(modifiers: BoltModifiers, bindings: BoltPattern, type: BoltTypeExpression | null, value: BoltExpression | null, span?: TextSpan | null): BoltVariableDeclaration;
export function createBoltPlainImportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainImportSymbol; export function createBoltPlainImportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainImportSymbol;
export function createBoltImportDirective(modifiers: BoltModifiers, file: string, symbols: BoltImportSymbol[], span?: TextSpan | null): BoltImportDirective; export function createBoltImportDirective(modifiers: BoltModifiers, file: BoltStringLiteral, symbols: BoltImportSymbol[], span?: TextSpan | null): BoltImportDirective;
export function createBoltExportSymbol(span?: TextSpan | null): BoltExportSymbol; export function createBoltExportSymbol(span?: TextSpan | null): BoltExportSymbol;
export function createBoltPlainExportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainExportSymbol; export function createBoltPlainExportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainExportSymbol;
export function createBoltExportDirective(file: string, symbols: BoltExportSymbol[] | null, span?: TextSpan | null): BoltExportDirective; export function createBoltExportDirective(file: string, symbols: BoltExportSymbol[] | null, span?: TextSpan | null): BoltExportDirective;

View file

@ -36,11 +36,6 @@ function pushAll<T>(array: T[], elements: T[]) {
} }
} }
function stripExtension(filepath: string) {
const i = filepath.lastIndexOf('.');
return i !== -1 ? filepath.substring(0, i) : filepath
}
function flatMap<T>(array: T[], proc: (element: T) => T[]) { function flatMap<T>(array: T[], proc: (element: T) => T[]) {
let out: T[] = [] let out: T[] = []
for (const element of array) { for (const element of array) {
@ -123,6 +118,7 @@ function loadPackageMetadata(rootDir: string) {
} }
function loadPackage(rootDir: string): Package { function loadPackage(rootDir: string): Package {
rootDir = path.resolve(rootDir);
const data = loadPackageMetadata(rootDir); const data = loadPackageMetadata(rootDir);
const pkg = new Package(rootDir, data.name, data.version, []); const pkg = new Package(rootDir, data.name, data.version, []);
for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) { for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) {
@ -132,13 +128,14 @@ function loadPackage(rootDir: string): Package {
} }
function loadPackagesAndSourceFiles(filenames: string[], cwd = '.'): Package[] { function loadPackagesAndSourceFiles(filenames: string[], cwd = '.'): Package[] {
cwd = path.resolve(cwd);
const anonPkg = new Package(cwd, null, null, []); const anonPkg = new Package(cwd, null, null, []);
const pkgs = [ anonPkg ]; const pkgs = [ anonPkg ];
for (const filename of filenames) { for (const filename of filenames) {
if (fs.statSync(filename).isDirectory()) { if (fs.statSync(filename).isDirectory()) {
pkgs.push(loadPackage(filename)); pkgs.push(loadPackage(filename));
} else { } else {
anonPkg.addSourceFile(parseSourceFile(filename, anonPkg, 0)); anonPkg.addSourceFile(parseSourceFile(filename, anonPkg));
} }
} }
return pkgs; return pkgs;

View file

@ -42,9 +42,12 @@ import {
isBoltStatement, isBoltStatement,
isBoltDeclaration, isBoltDeclaration,
isSourceFile, isSourceFile,
BoltReferenceTypeExpression BoltReferenceTypeExpression,
isBoltTypeDeclaration,
SourceFile,
BoltModifiers
} from "./ast"; } from "./ast";
import {FastStringMap, memoize, assert, verbose} from "./util"; import {FastStringMap, countDigits, assert, verbose} from "./util";
import { import {
DiagnosticPrinter, DiagnosticPrinter,
E_TYPES_NOT_ASSIGNABLE, E_TYPES_NOT_ASSIGNABLE,
@ -52,157 +55,90 @@ import {
E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
E_TYPE_DECLARATION_NOT_FOUND, E_TYPE_DECLARATION_NOT_FOUND,
E_DECLARATION_NOT_FOUND, E_DECLARATION_NOT_FOUND,
E_INVALID_ARGUMENTS E_INVALID_ARGUMENTS,
E_FILE_NOT_FOUND,
} from "./diagnostics"; } from "./diagnostics";
import { createAnyType, isOpaqueType, createOpaqueType, Type, createVoidType, createVariantType, isVoidType } from "./types"; import { createAnyType, isOpaqueType, createOpaqueType, Type, createVoidType, createVariantType, isVoidType } from "./types";
import { getReturnStatementsInFunctionBody, isAutoImported, toDeclarationPath, createDeclarationPath, hasRelativeModulePath, hasAbsoluteModulePath, DeclarationPath, getModulePath, getSymbolNameOfDeclarationPath } from "./common"; import { getReturnStatementsInFunctionBody, Package } from "./common";
import {emit} from "./emitter"; import {emit} from "./emitter";
import {Program} from "./program";
import {type} from "os";
const PACKAGE_SCOPE_ID = 0; // TODO
const GLOBAL_SCOPE_ID = 0;
interface SymbolInfo<N extends Syntax> { class SymbolPath {
declarations: N[];
}
function introducesNewScope(kind: SyntaxKind): boolean { constructor(
return kind === SyntaxKind.BoltSourceFile private parents: string[],
|| kind === SyntaxKind.BoltModule public isAbsolute: boolean,
|| kind === SyntaxKind.BoltFunctionDeclaration public name: string
|| kind === SyntaxKind.BoltBlockExpression; ) {
}
function introducesNewTypeScope(kind: SyntaxKind): boolean {
return kind === SyntaxKind.BoltModule
|| kind === SyntaxKind.BoltSourceFile;
}
type Scope = unknown;
type TypeScope = unknown;
function getScopeId(scope: Scope | TypeScope) {
if (isSyntax(scope)) {
return scope.id;
}
return PACKAGE_SCOPE_ID;
}
function createSymbol(node: BoltDeclaration): SymbolInfo {
return { declarations: [ node ] };
}
class SymbolResolver<N extends Syntax> {
private symbols = new FastStringMap<string, SymbolInfo<N>>();
constructor(private introducesNewScope: (kind: SyntaxKind) => boolean) {
} }
public addSymbol(name: string, node: N): void { public hasParents(): boolean {
const scope = this.getScopeSurroundingNode(node) return this.parents.length > 0;
verbose(`Adding symbol ${name} in scope #${getScopeId(scope)}`);
const sym = { declarations: [ node ] };
this.symbols.set(`${name}@${getScopeId(scope)}`, sym);
} }
public getParentScope(scope: Scope): Scope | null { public getParents() {
if (!isSyntax(scope)) { return this.parents;
// Scope is the global package scope
return null;
}
if (scope.kind === SyntaxKind.BoltSourceFile) {
return scope.package;
}
return this.getScopeForNode(scope.parentNode!)
} }
public getScopeSurroundingNode(node: Syntax): Scope { }
assert(node.parentNode !== null);
return this.getScopeForNode(node.parentNode!);
}
public getScopeForNode(node: Syntax): Scope { function nodeToSymbolPath(node: BoltSyntax): SymbolPath {
let currNode = node; switch (node.kind) {
while (!this.introducesNewScope(currNode.kind)) { case SyntaxKind.BoltIdentifier:
if (currNode.kind === SyntaxKind.BoltSourceFile) { return new SymbolPath([], false, emit(node));
return currNode.package; case SyntaxKind.BoltQualName:
const name = emit(node.name);
if (node.modulePath === null) {
return new SymbolPath([], false, name);
} }
currNode = currNode.parentNode!; return new SymbolPath(node.modulePath.map(id => id.text), false, name);
} default:
return currNode; throw new Error(`Could not extract a symbol path from the given node.`);
} }
}
private lookupSymbolInScope(name: string, scope: Scope): SymbolInfo<N> | null { enum SymbolKind {
const key = `${name}@${getScopeId(scope)}`; Type = 0x1,
if (!this.symbols.has(key)) { Variable = 0x2,
return null; Module = 0x4,
}
function* getAllSymbolKindsInMask(symbolKindMask: SymbolKind) {
const n = countDigits(symbolKindMask, 2);
for (let i = 1; i <= n; i++) {
if ((symbolKindMask & i) > 0) {
yield i;
} }
return this.symbols.get(key);
} }
}
public findSymbolInScopeOf(name: string, scope: Scope): SymbolInfo<N> | null { export interface ScopeInfo {
while (true) { id: number;
const sym = this.lookupSymbolInScope(name, scope); declaration: BoltSyntax | Package;
if (sym !== null) { parentScope?: ScopeInfo;
return sym; kind: SymbolKind,
} }
const parentScope = this.getParentScope(scope);
if (parentScope === null) {
break;
}
scope = parentScope;
}
return null;
}
public resolve(path: DeclarationPath, node: BoltSyntax): BoltSyntax | null {
let scope = this.getScopeSurroundingNode(node);
if (hasAbsoluteModulePath(path)) {
// TODO
} else if (hasRelativeModulePath(path)) {
while (true) {
let shouldSearchParentScopes = false;
let currScope = scope;
for (const name of getModulePath(path)) {
const sym = this.lookupSymbolInScope(name, currScope);
if (sym === null) {
shouldSearchParentScopes = true;
break;
}
if (sym.declarations[0].kind !== SyntaxKind.BoltModule) {
shouldSearchParentScopes = true;
break;
}
currScope = this.getScopeForNode(sym.declarations[0]);
}
if (!shouldSearchParentScopes) {
scope = currScope;
break;
}
const parentScope = this.getParentScope(scope);
if (parentScope === null) {
return null;
}
scope = parentScope;
}
}
const sym = this.findSymbolInScopeOf(getSymbolNameOfDeclarationPath(path), scope);
if (sym === null) {
return null;
}
return sym.declarations[0]!;
}
interface SymbolInfo {
kind: SymbolKind;
declarations: BoltSyntax[];
} }
export class TypeChecker { export class TypeChecker {
constructor(private diagnostics: DiagnosticPrinter) { constructor(
private diagnostics: DiagnosticPrinter,
private program: Program
) {
} }
private varResolver = new SymbolResolver<BoltDeclaration>(introducesNewScope); private symbols = new FastStringMap<string, SymbolInfo>();
private typeResolver = new SymbolResolver<BoltTypeDeclaration>(introducesNewTypeScope);
public checkSourceFile(node: BoltSourceFile): void { public checkSourceFile(node: BoltSourceFile): void {
@ -314,14 +250,6 @@ export class TypeChecker {
switch (node.kind) { switch (node.kind) {
case SyntaxKind.BoltModule:
{
for (const element of node.elements) {
visitSourceElement(element);
}
break;
}
case SyntaxKind.BoltRecordDeclaration: case SyntaxKind.BoltRecordDeclaration:
{ {
if (node.members !== null) { if (node.members !== null) {
@ -460,16 +388,15 @@ export class TypeChecker {
} }
private resolveType(name: string, node: BoltSyntax): Type | null { private resolveTypeName(name: string, node: BoltSyntax): Type | null {
const sym = this.typeResolver.findSymbolInScopeOf(name, this.typeResolver.getScopeSurroundingNode(node)) const sym = this.findSymbolInScopeOf(name, this.getScopeSurroundingNode(node));
if (sym === null) { if (sym === null) {
return null; return null;
} }
return this.getTypeOfNode(sym.declarations[0]); return this.getTypeOfNode(sym.declarations[0]);
} }
@memoize(node => node.id) private createType(node: BoltSyntax): Type {
private getTypeOfNode(node: BoltSyntax): Type {
switch (node.kind) { switch (node.kind) {
case SyntaxKind.BoltReferenceTypeExpression: case SyntaxKind.BoltReferenceTypeExpression:
{ {
@ -504,18 +431,18 @@ export class TypeChecker {
} }
case SyntaxKind.BoltConstantExpression: case SyntaxKind.BoltConstantExpression:
{ {
let type; return node.value.getType();
if (typeof node.value === 'string') { //if (typeof node.value === 'string') {
type = this.resolveType('String', node)!; // type = this.resolveTypeName('String', node)!;
} else if (typeof node.value === 'boolean') { //} else if (typeof node.value === 'boolean') {
type = this.resolveType('bool', node)!; // type = this.resolveTypeName('bool', node)!;
} else if (typeof node.value === 'bigint') { //} else if (typeof node.value === 'bigint') {
type = this.resolveType('i32', node)!; // type = this.resolveTypeName('i32', node)!;
} else { //} else {
throw new Error(`Could not derive type of constant expression.`); // throw new Error(`Could not derive type of constant expression.`);
} //}
assert(type !== null); //assert(type !== null);
return type; //return type;
} }
case SyntaxKind.BoltMatchExpression: case SyntaxKind.BoltMatchExpression:
{ {
@ -526,6 +453,15 @@ export class TypeChecker {
} }
} }
private getTypeOfNode(node: BoltSyntax): Type {
if (node._type !== undefined) {
return node._type;
}
const type = this.createType(node);
node._type = type;
return type;
}
private isTypeAssignableTo(left: Type, right: Type): boolean { private isTypeAssignableTo(left: Type, right: Type): boolean {
if (isOpaqueType(left) && isOpaqueType(right)) { if (isOpaqueType(left) && isOpaqueType(right)) {
return left === right; return left === right;
@ -574,43 +510,316 @@ export class TypeChecker {
} }
public registerSourceFile(node: BoltSourceFile): void { public registerSourceFile(node: BoltSourceFile): void {
this.addAllSymbolsInNode(node);
}
private addAllSymbolsInNode(node: BoltSyntax): void { const self = this;
switch (node.kind) { addAllSymbolsToScope(
node,
this.getScopeForNode(node, SymbolKind.Variable),
this.getScopeForNode(node, SymbolKind.Type),
this.getScopeForNode(node, SymbolKind.Module)
);
case SyntaxKind.BoltSourceFile: function addAllSymbolsToScope(node: BoltSyntax, variableScope: ScopeInfo, typeScope: ScopeInfo, moduleScope: ScopeInfo, allowDuplicates = false): void {
case SyntaxKind.BoltModule:
{ switch (node.kind) {
for (const element of node.elements) {
this.addAllSymbolsInNode(element); case SyntaxKind.BoltImportDirective:
{
if (node.symbols !== null) {
for (const importSymbol of node.symbols) {
// TODO
}
} else {
const sourceFile = self.program.resolveToSourceFile(node.file.value, node) as BoltSourceFile;
if (sourceFile === null) {
self.diagnostics.add({
severity: 'error',
message: E_FILE_NOT_FOUND,
args: { filename: node.file.value },
node: node.file,
});
} else {
for (const exportedNode of self.getAllExportedNodes(sourceFile)) {
addAllSymbolsToScope(exportedNode, variableScope, typeScope, moduleScope, true);
}
}
}
break;
} }
break;
}
case SyntaxKind.BoltFunctionDeclaration: case SyntaxKind.BoltSourceFile:
{ case SyntaxKind.BoltModule:
this.varResolver.addSymbol(emit(node.name), node); {
break; for (const element of node.elements) {
} addAllSymbolsToScope(element, variableScope, typeScope, moduleScope);
}
break;
}
case SyntaxKind.BoltFunctionDeclaration:
{
const symbolName = emit(node.name);
const sym = self.lookupSymbolInScope(symbolName, variableScope, SymbolKind.Variable)
if (sym !== null) {
if (!allowDuplicates) {
throw new Error(`Symbol '${name}' is already defined.`);
}
if (sym.declarations.indexOf(node) === -1) {
throw new Error(`Different symbols imported under the same name.`);
}
} else {
self.addSymbolToScope(symbolName, node, variableScope, SymbolKind.Variable);
}
break;
}
case SyntaxKind.BoltRecordDeclaration:
{
const symbolName = emit(node.name);
const sym = self.lookupSymbolInScope(symbolName, typeScope, SymbolKind.Type)
if (sym !== null) {
if (!allowDuplicates) {
throw new Error(`Symbol '${name}' is already defined.`);
}
if (sym.declarations.indexOf(node) === -1) {
throw new Error(`Different symbols imported under the same name.`);
}
} else {
self.addSymbolToScope(node.name.text, node, typeScope, SymbolKind.Type);
}
break;
}
case SyntaxKind.BoltRecordDeclaration:
{
this.typeResolver.addSymbol(node.name.text, node);
} }
} }
} }
private getAllExportedNodes(node: BoltSyntax): BoltSyntax[] {
const nodes: BoltSyntax[] = [];
visit(node);
return nodes;
function visit(node: BoltSyntax) {
if (isBoltDeclaration(node) || isBoltTypeDeclaration(node)) {
if ((node.modifiers & BoltModifiers.IsPublic) > 0) {
nodes.push(node);
}
}
switch (node.kind) {
case SyntaxKind.BoltFunctionDeclaration:
case SyntaxKind.BoltRecordDeclaration:
case SyntaxKind.BoltTypeAliasDeclaration:
nodes.push(node);
break;
case SyntaxKind.BoltModule:
case SyntaxKind.BoltSourceFile:
for (const element of node.elements) {
visit(element);
}
break;
}
}
}
private resolveReferenceExpression(node: BoltReferenceExpression): BoltDeclaration | null { private resolveReferenceExpression(node: BoltReferenceExpression): BoltDeclaration | null {
return this.varResolver.resolve(createDeclarationPath(node.name), node); const symbolPath = nodeToSymbolPath(node.name)
return this.resolveSymbolPath(symbolPath, node, SymbolKind.Variable);
} }
private resolveTypeReferenceExpression(node: BoltReferenceTypeExpression): BoltTypeDeclaration | null { private resolveTypeReferenceExpression(node: BoltReferenceTypeExpression): BoltTypeDeclaration | null {
return this.typeResolver.resolve(createDeclarationPath(node.name), node); const symbolPath = nodeToSymbolPath(node.name);
const scope = this.getScopeForNode(node, SymbolKind.Type);
return this.resolveSymbolPath(symbolPath, scope, SymbolKind.Type);
}
public addSymbol(name: string, node: BoltSyntax, kind: SymbolKind): void {
const scope = this.getScopeSurroundingNode(node, kind);
this.addSymbolToScope(name, node, scope, kind)
}
public addSymbolToScope(name: string, node: BoltSyntax, scope: ScopeInfo, symbolKindMask: SymbolKind): void {
verbose(`Adding symbol ${name} in scope #${scope.id}`);
const sym = { kind: symbolKindMask, declarations: [ node ] } as SymbolInfo;
for (const symbolKind of getAllSymbolKindsInMask(symbolKindMask)) {
this.symbols.set(`${symbolKind}:${name}:${scope.id}`, sym);
}
}
public getParentScope(scope: ScopeInfo, kind: SymbolKind): ScopeInfo | null {
// We might have already calculcated this scope's parent scope before;;
if (scope.parentScope !== undefined) {
return scope.parentScope;
}
if (isSyntax(scope.declaration)) {
// Edge case where there are no parent nodes left to traverse
if (scope.declaration.kind === SyntaxKind.BoltSourceFile) {
const pkg = (scope.declaration as BoltSourceFile).package;
return {
id: pkg.id,
declaration: pkg,
} as ScopeInfo;
}
return this.getScopeForNode(scope.declaration.parentNode!, kind)
}
// If the declaration was not an AST node, it can only be a package
return null;
}
public getScopeSurroundingNode(node: Syntax, kind: SymbolKind): ScopeInfo {
assert(node.parentNode !== null);
return this.getScopeForNode(node.parentNode!, kind);
}
public getScopeForNode(node: BoltSyntax, kind: SymbolKind): ScopeInfo {
let currNode = node;
while (true) {
// We might have created a scope for this node before,
// or saved the relevant scope for efficiency.
if (node._scope !== undefined) {
return node._scope;
}
// When we've reached a node that introduces a new scope according
// to the rules of the SymbolKind, we may continue.
if (this.introducesNewScope(currNode.kind, kind)) {
break;
}
assert(currNode.parentNode !== null);
currNode = currNode.parentNode!;
}
return {
id: currNode.id,
declaration: currNode,
} as ScopeInfo;
}
private lookupSymbolInScope(name: string, scope: ScopeInfo, symbolKindMask: SymbolKind): SymbolInfo | null {
for (const symbolKind of getAllSymbolKindsInMask(symbolKindMask)) {
const key = `${symbolKind}:${name}:${scope.id}`;
if (this.symbols.has(key)) {
return this.symbols.get(key);
}
}
return null;
}
private findSymbolInScopeOf(name: string, scope: ScopeInfo, kind: SymbolKind): SymbolInfo | null {
while (true) {
// Attempt to look up the symbol in the scope that was either passed to this
// method or one of its parents. If we found one, we're done.
const sym = this.lookupSymbolInScope(name, scope, kind);
if (sym !== null) {
return sym;
}
const parentScope = this.getParentScope(scope, kind);
// Failing to find a parent scope means that none of the enclosing
// scopes had the given variable. If this is the case, jump to the
// error handling logic.
if (parentScope === null) {
break;
}
scope = parentScope;
}
return null;
}
public resolveSymbolPath(path: SymbolPath, scope: ScopeInfo, kind: SymbolKind): BoltSyntax | null {
if (path.hasParents()) {
if (path.isAbsolute) {
// TODO
} else {
// We will keep looping until we are at the topmost module of
// the package corresponding to `node`.
while (true) {
let shouldSearchParentScopes = false;
let currScope = scope;
// 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 path.getParents()) {
const sym = this.lookupSymbolInScope(name, currScope, SymbolKind.Module);
if (sym === null) {
shouldSearchParentScopes = true;
break;
}
if (sym.declarations[0].kind !== SyntaxKind.BoltModule) {
shouldSearchParentScopes = true;
break;
}
currScope = this.getScopeForNode(sym.declarations[0], SymbolKind.Module);
}
// If the previous loop did not fail, we are done.
if (!shouldSearchParentScopes) {
scope = currScope;
break;
}
// We continue the outer loop by getting the parent module, which should be
// equivalent to getting the parent module scope.
const parentScope = this.getParentScope(scope, SymbolKind.Module);
if (parentScope === null) {
return null;
}
scope = parentScope;
}
}
}
// Once we've handled any module path that might have been present,
// we resolve the actual symbol using a helper method.
const sym = this.findSymbolInScopeOf(path.name, scope, kind);
if (sym === null) {
return null;
}
return sym.declarations[0]!;
}
private introducesNewScope(nodeKind: SyntaxKind, symbolKind: SymbolKind) {
switch (symbolKind) {
case SymbolKind.Variable:
return nodeKind === SyntaxKind.BoltSourceFile
|| nodeKind === SyntaxKind.BoltModule
|| nodeKind === SyntaxKind.BoltFunctionDeclaration
|| nodeKind === SyntaxKind.BoltBlockExpression;
case SymbolKind.Type:
return nodeKind === SyntaxKind.BoltModule
|| nodeKind === SyntaxKind.BoltSourceFile;
case SymbolKind.Module:
return nodeKind === SyntaxKind.BoltModule
|| nodeKind === SyntaxKind.BoltSourceFile;
}
} }
} }

View file

@ -10,16 +10,37 @@ import {
isBoltPunctuated, isBoltPunctuated,
SourceFile, SourceFile,
BoltSourceFile, BoltSourceFile,
BoltSourceFileModifiers BoltSourceFileModifiers,
isSourceFile
} from "./ast"; } from "./ast";
import { BOLT_SUPPORTED_LANGUAGES } from "./constants" import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
import {emit} from "./emitter"; import {emit} from "./emitter";
import {FastStringMap, enumerate, escapeChar} from "./util"; import {FastStringMap, enumerate, escapeChar, assert} from "./util";
import {TextSpan, TextPos, TextFile} from "./text"; import {TextSpan, TextPos, TextFile} from "./text";
import {Scanner} from "./scanner"; import {Scanner} from "./scanner";
export function getSourceFile(node: Syntax) {
while (true) {
if (isSourceFile(node)) {
return node
}
assert(node.parentNode !== null);
node = node.parentNode;
}
}
export function getPackage(node: Syntax) {
const sourceFile = getSourceFile(node);
assert(sourceFile.kind === SyntaxKind.BoltSourceFile);
return (sourceFile as BoltSourceFile).package;
}
let nextPackageId = 1;
export class Package { export class Package {
public id = nextPackageId++;
constructor( constructor(
public rootDir: string, public rootDir: string,
public name: string | null, public name: string | null,
@ -328,37 +349,3 @@ export function describeKind(kind: SyntaxKind): string {
} }
} }
export type DeclarationPath = unknown;
type DeclarationPathInfo = {
modulePath: string[],
isAbsolute: boolean,
name: string
};
export function getModulePath(path: DeclarationPath): string[] {
return (path as DeclarationPathInfo).modulePath;
}
export function hasAbsoluteModulePath(path: DeclarationPath): boolean {
return (path as DeclarationPathInfo).modulePath.length > 0
&& (path as DeclarationPathInfo).isAbsolute;
}
export function hasRelativeModulePath(path: DeclarationPath): boolean {
return (path as DeclarationPathInfo).modulePath.length > 0
&& !(path as DeclarationPathInfo).isAbsolute;
}
export function getSymbolNameOfDeclarationPath(path: DeclarationPath): string {
return (path as DeclarationPathInfo).name;
}
export function createDeclarationPath(node: BoltQualName): DeclarationPath {
const name = emit(node.name);
if (node.modulePath === null) {
return { modulePath: [], isAbsolute: false, name };
}
return { modulePath: node.modulePath.map(id => id.text), isAbsolute: false, name };
}

View file

@ -1,8 +1,9 @@
import chalk from "chalk" import chalk from "chalk"
import {Syntax} from "./ast"; import {Syntax} from "./ast";
import {format, MapLike, FormatArg} from "./util"; import {format, MapLike, FormatArg, countDigits} from "./util";
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."
export const E_FIELD_MUST_BE_STRING = "Field '{name}' must be a string." export const E_FIELD_MUST_BE_STRING = "Field '{name}' must be a string."
export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present." export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present."
@ -22,13 +23,6 @@ export interface Diagnostic {
node?: Syntax; node?: Syntax;
} }
export function countDigits(num: number) {
if (num === 0) {
return 1
}
return Math.ceil(Math.log10(num+1))
}
function firstIndexOfNonEmpty(str: string) { function firstIndexOfNonEmpty(str: string) {
let j = 0; let j = 0;
for (; j < str.length; j++) { for (; j < str.length; j++) {

View file

@ -70,11 +70,7 @@ export class Frontend {
constructor() { constructor() {
this.diagnostics = new DiagnosticPrinter(); this.diagnostics = new DiagnosticPrinter();
this.checker = new TypeChecker(this.diagnostics);
this.evaluator = new Evaluator(this.checker);
this.timing = new Timing(); this.timing = new Timing();
this.container.bindSelf(this.evaluator);
this.container.bindSelf(this.checker);
} }
@memoize @memoize
@ -89,16 +85,25 @@ export class Frontend {
} }
public typeCheck(program: Program) { public typeCheck(program: Program) {
const checker = new TypeChecker(this.diagnostics, program);
for (const sourceFile of program.getAllSourceFiles()) { for (const sourceFile of program.getAllSourceFiles()) {
this.checker.registerSourceFile(sourceFile as BoltSourceFile); checker.registerSourceFile(sourceFile as BoltSourceFile);
} }
for (const sourceFile of program.getAllSourceFiles()) { for (const sourceFile of program.getAllSourceFiles()) {
this.checker.checkSourceFile(sourceFile as BoltSourceFile); checker.checkSourceFile(sourceFile as BoltSourceFile);
} }
} }
public compile(program: Program, target: string) { public compile(program: Program, target: string) {
// FIXME type checker should be shared across multple different method invocations
const checker = new TypeChecker(this.diagnostics, program);
const container = new Container();
//container.bindSelf(evaluator);
container.bindSelf(checker);
switch (target) { switch (target) {
case "JS": case "JS":

View file

@ -337,15 +337,14 @@ export class Parser {
const t1 = tokens.get(); const t1 = tokens.get();
assertToken(t1, SyntaxKind.BoltStringLiteral); assertToken(t1, SyntaxKind.BoltStringLiteral);
const filename = (t1 as BoltStringLiteral).value;
const symbols: BoltImportSymbol[] = []; const symbols: BoltImportSymbol[] = null;
const t2 = tokens.get(); const t2 = tokens.get();
if (t2.kind === SyntaxKind.BoltParenthesized) { if (t2.kind === SyntaxKind.BoltParenthesized) {
// TODO implement grammar and parsing logic for symbols // TODO implement grammar and parsing logic for symbols
} }
const node = createBoltImportDirective(modifiers, filename, symbols); const node = createBoltImportDirective(modifiers, t1 as BoltStringLiteral, symbols);
setOrigNodeRange(node, t0, t1); setOrigNodeRange(node, t0, t1);
return node; return node;
} }

View file

@ -1,32 +1,70 @@
import { Package } from "./common" import * as path from "path"
import { SourceFile } from "./ast" import { Package, getPackage } from "./common"
import { FastStringMap } from "./util"; import { SourceFile, Syntax } from "./ast"
import { FastStringMap, assert, isInsideDirectory, stripExtensions } from "./util";
export class Program { export class Program {
private transformed = new FastStringMap<string, SourceFile>(); private packagesByName = new FastStringMap<string, Package>();
private sourceFilesByFilePath = new FastStringMap<string, SourceFile>();
constructor( constructor(
pkgs: Package[] pkgs: Package[]
) { ) {
for (const pkg of pkgs) { for (const pkg of pkgs) {
for (const sourceFile of pkg.sourceFiles) { for (const sourceFile of pkg.sourceFiles) {
this.transformed.set(sourceFile.span!.file.fullPath, sourceFile); this.sourceFilesByFilePath.set(stripExtensions(sourceFile.span!.file.fullPath), sourceFile);
} }
} }
} }
public getAllSourceFiles() { public getAllSourceFiles() {
return this.transformed.values(); return this.sourceFilesByFilePath.values();
}
public getSourceFile(filepath: string): SourceFile | null {
assert(path.isAbsolute(filepath));
if (!this.sourceFilesByFilePath.has(filepath)) {
return null;
}
return this.sourceFilesByFilePath.get(filepath);
}
public getPackageNamed(name: string): Package {
return this.packagesByName.get(name);
}
public resolveToSourceFile(importPath: string, fromNode: Syntax): SourceFile | null {
let resolvedFilePath: string;
if (importPath.startsWith('.')) {
const pkg = getPackage(fromNode);
resolvedFilePath = path.join(pkg.rootDir, importPath.substring(2));
assert(isInsideDirectory(resolvedFilePath, pkg.rootDir));
} else {
const elements = importPath.split('/');
const pkg = this.getPackageNamed(elements[0]);
let filename: string;
if (elements.length === 1) {
filename = 'lib';
} else {
assert(elements.length > 0);
assert(!elements.slice(1).some(element => element.startsWith('.')));
filename = elements.slice(1).join(path.sep);
}
resolvedFilePath = path.join(pkg.rootDir, filename)
assert(isInsideDirectory(resolvedFilePath, pkg.rootDir));
}
return this.getSourceFile(resolvedFilePath);
} }
public updateSourceFile(oldSourceFile: SourceFile, newSourceFile: SourceFile): void { public updateSourceFile(oldSourceFile: SourceFile, newSourceFile: SourceFile): void {
if (!this.transformed.has(oldSourceFile.span!.file.fullPath)) { if (!this.sourceFilesByFilePath.has(oldSourceFile.span!.file.fullPath)) {
throw new Error(`Could not update ${oldSourceFile.span!.file.origPath} because it was not found in this program.`); throw new Error(`Could not update ${oldSourceFile.span!.file.origPath} because it was not found in this program.`);
} }
this.transformed.delete(oldSourceFile.span!.file.fullPath); this.sourceFilesByFilePath.delete(oldSourceFile.span!.file.fullPath);
this.transformed.set(newSourceFile.span!.file.fullPath, newSourceFile); this.sourceFilesByFilePath.set(newSourceFile.span!.file.fullPath, newSourceFile);
} }
} }

View file

@ -0,0 +1,25 @@
import { Type } from "./types"
import { ScopeInfo } from "./checker"
import { Package } from "./common"
import { TextSpan } from "./text"
export function setParents(node: Syntax): void;
export type SyntaxRange = [Syntax, Syntax];
export function isSyntax(value: any): value is Syntax;
interface SyntaxBase<K extends SyntaxKind> {
id: number;
_scope?: ScopeInfo;
kind: K;
parentNode: ParentTypesOf<K> | null;
span: TextSpan | null;
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
}
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;

View file

@ -98,12 +98,7 @@ export function generateAST(decls: Declaration[]) {
// Write corresponding TypeScript declarations // Write corresponding TypeScript declarations
dtsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'treegen', 'ast.dts.template'), 'utf8'));
// FIXME These imports are specific to our project and should somehow
// form part of the user specification.
dtsFile.write('\nimport { Package } from "./common"\n\n');
dtsFile.write('export function isSyntax(value: any): value is Syntax;\n\n');
dtsFile.write(`\nexport const enum SyntaxKind {\n`); dtsFile.write(`\nexport const enum SyntaxKind {\n`);
for (const decl of leafNodes) { for (const decl of leafNodes) {
@ -111,27 +106,6 @@ export function generateAST(decls: Declaration[]) {
} }
dtsFile.write(`}\n\n`); dtsFile.write(`}\n\n`);
dtsFile.write(`
import { TextSpan } from "./text"
export function setParents(node: Syntax): void;
export type SyntaxRange = [Syntax, Syntax];
interface SyntaxBase<K extends SyntaxKind> {
id: number;
kind: K;
parentNode: ParentTypesOf<K> | null;
span: TextSpan | null;
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
}
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
`);
for (const decl of decls) { for (const decl of decls) {
if (decl.type === 'NodeDeclaration') { if (decl.type === 'NodeDeclaration') {
if (isLeafNode(decl.name)) { if (isLeafNode(decl.name)) {

View file

@ -14,6 +14,18 @@ export interface JsonArray extends Array<Json> { };
export interface JsonObject { [key: string]: Json } export interface JsonObject { [key: string]: Json }
export type Json = null | string | boolean | number | JsonArray | JsonObject; export type Json = null | string | boolean | number | JsonArray | JsonObject;
export function isInsideDirectory(filepath: string, rootDir: string): boolean {
const relPath = path.relative(rootDir, filepath)
return !relPath.startsWith('..');
}
export function stripExtensions(filepath: string) {
const i = filepath.indexOf('.')
return i !== -1
? filepath.substring(0, i)
: filepath;
}
export function isString(value: any): boolean { export function isString(value: any): boolean {
return typeof value === 'string'; return typeof value === 'string';
} }
@ -22,6 +34,13 @@ export function hasOwnProperty<T extends object, K extends PropertyKey>(obj: T,
return Object.prototype.hasOwnProperty.call(obj, key); return Object.prototype.hasOwnProperty.call(obj, key);
} }
export function countDigits(x: number, base: number = 10) {
if (x === 0) {
return 1
}
return Math.ceil(Math.log(x+1) / Math.log(base))
}
export function uniq<T>(elements: T[]): T[] { export function uniq<T>(elements: T[]): T[] {
const out: T[] = []; const out: T[] = [];
const visited = new Set<T>(); const visited = new Set<T>();