bolt/src/checker.ts

293 lines
7.8 KiB
TypeScript
Raw Normal View History

/**
*
* ```
* mod foo {
* type MyType1 = i32;
* mod bar {
* pub type MyType2 = MyType1;
* }
* }
* ```
*
* ```
* mod foo {
* let x = 1;
* mod bar {
* fn do_something(y) {
* return x + y;
* }
* }
* }
* ```
*
* Note that the `pub`-keyword is not present on `MyType1`.
*/
import {Syntax, SyntaxKind, BoltReferenceExpression, BoltDeclaration, BoltSourceFile, BoltSyntax, BoltReferenceTypeExpression, BoltTypeDeclaration} from "./ast";
import {FastStringMap} from "./util";
import {DiagnosticPrinter, E_TYPES_NOT_ASSIGNABLE, E_TYPE_DECLARATION_NOT_FOUND, E_DECLARATION_NOT_FOUND} from "./diagnostics";
2020-02-25 17:55:17 +01:00
interface SymbolInfo {
declarations: BoltDeclaration[];
}
interface TypeSymbolInfo {
declarations: BoltTypeDeclaration[];
2020-02-25 17:55:17 +01:00
}
function introducesNewScope(kind: SyntaxKind): boolean {
return kind === SyntaxKind.BoltSourceFile
|| kind === SyntaxKind.BoltModule
|| kind === SyntaxKind.BoltFunctionDeclaration
|| kind === SyntaxKind.BoltBlockExpression;
}
function introducesNewTypeScope(kind: SyntaxKind): boolean {
return kind === SyntaxKind.BoltModule
|| kind === SyntaxKind.BoltSourceFile;
}
type Scope = unknown;
type TypeScope = unknown;
2020-03-03 14:53:54 +01:00
function createSymbol(node: BoltDeclaration): SymbolInfo {
return { declarations: [ node ] };
}
2020-03-03 14:53:54 +01:00
function createTypeSymbol(node: BoltTypeDeclaration): TypeSymbolInfo {
return { declarations: [ node ] };
2020-03-03 14:53:54 +01:00
}
export class TypeChecker {
constructor(private diagnostics: DiagnosticPrinter) {
}
private symbols = new FastStringMap<string, SymbolInfo>();
private typeSymbols = new FastStringMap<string, TypeSymbolInfo>();
public checkSourceFile(node: BoltSourceFile): void {
const refExps = node.findAllChildrenOfKind(SyntaxKind.BoltReferenceExpression);
for (const refExp of refExps) {
if (this.resolveReferenceExpression(refExp) === null) {
this.diagnostics.add({
message: E_DECLARATION_NOT_FOUND,
args: { name: refExp.name.name.text },
severity: 'error',
node: refExp,
})
}
}
const typeRefExps = node.findAllChildrenOfKind(SyntaxKind.BoltReferenceTypeExpression);
for (const typeRefExp of typeRefExps) {
if (this.resolveTypeReferenceExpression(typeRefExp) === null) {
this.diagnostics.add({
message: E_TYPE_DECLARATION_NOT_FOUND,
args: { name: typeRefExp.name.name.text },
severity: 'error',
node: typeRefExp,
})
}
}
}
public registerSourceFile(node: BoltSourceFile): void {
this.addAllSymbolsInNode(node);
}
private addAllSymbolsInNode(node: BoltSyntax): void {
2020-02-25 17:55:17 +01:00
switch (node.kind) {
2020-05-23 15:04:32 +02:00
case SyntaxKind.BoltSourceFile:
case SyntaxKind.BoltModule:
for (const element of node.elements) {
this.addAllSymbolsInNode(element);
}
break;
2020-05-23 15:04:32 +02:00
case SyntaxKind.BoltFunctionDeclaration:
{
const scope = this.getScopeSurroundingNode(node);
const sym = createSymbol(node);
this.addSymbol(node.name.text, scope, sym);
2020-05-23 15:04:32 +02:00
break;
}
case SyntaxKind.BoltRecordDeclaration:
{
const typeScope = this.getTypeScopeSurroundingNode(node);
const typeSym = createTypeSymbol(node);
this.addTypeSymbol(node.name.text, typeScope, typeSym);
}
2020-05-23 15:04:32 +02:00
}
2020-05-23 15:04:32 +02:00
}
2020-02-25 17:55:17 +01:00
private addSymbol(name: string, scope: Scope, sym: SymbolInfo): void {
this.symbols.set(`${name}@${(scope as any).id}`, sym);
2020-02-25 17:55:17 +01:00
}
private addTypeSymbol(name: string, scope: TypeScope, sym: TypeSymbolInfo): void {
this.typeSymbols.set(`${name}@${(scope as any).id}`, sym);
}
2020-02-25 17:55:17 +01:00
public getParentScope(scope: Scope): Scope | null {
let node = scope as Syntax;
if (node.kind === SyntaxKind.BoltSourceFile) {
return null;
}
node = node.parentNode!;
while (!introducesNewScope(node.kind)) {
node = node.parentNode!;
}
return node;
}
2020-05-23 15:04:32 +02:00
public getParentTypeScope(scope: TypeScope): TypeScope | null {
let node = scope as Syntax;
if (node.kind === SyntaxKind.BoltSourceFile) {
return null;
2020-05-10 23:50:42 +02:00
}
node = node.parentNode!;
while (!introducesNewTypeScope(node.kind)) {
node = node.parentNode!;
}
return node;
}
private getScopeSurroundingNode(node: Syntax): Scope {
if (node.kind === SyntaxKind.BoltSourceFile) {
return node;
}
return this.getScopeForNode(node.parentNode);
2020-03-03 14:53:54 +01:00
}
private getTypeScopeSurroundingNode(node: Syntax): TypeScope {
if (node.kind === SyntaxKind.BoltSourceFile) {
return node;
}
return this.getScopeForNode(node.parentNode);
2020-03-03 14:53:54 +01:00
}
private getScopeForNode(node: Syntax): Scope {
if (node.kind === SyntaxKind.BoltSourceFile) {
return node;
}
let currNode = node;
while (!introducesNewScope(currNode.kind)) {
currNode = currNode.parentNode!;
}
return currNode;
}
private getTypeScopeForNode(node: Syntax): TypeScope {
if (node.kind === SyntaxKind.BoltSourceFile) {
return node;
}
let currNode = node;
while (!introducesNewTypeScope(currNode.kind)) {
currNode = currNode.parentNode!;
}
return currNode;
}
private lookupSymbolInScope(name: string, scope: Scope): SymbolInfo | null {
const key = `${name}@${(scope as any).id}`;
if (!this.symbols.has(key)) {
2020-05-23 15:04:32 +02:00
return null;
}
return this.symbols.get(key);
}
private lookupSymbolInTypeScope(name: string, scope: TypeScope): TypeSymbolInfo | null {
const key = `${name}@${(scope as any).id}`;
if (!this.typeSymbols.has(key)) {
return null;
}
return this.typeSymbols.get(key);
}
public findSymbolInScopeOf(name: string, scope: Scope): SymbolInfo | null {
while (true) {
const sym = this.lookupSymbolInScope(name, scope);
if (sym !== null) {
return sym;
}
const parentScope = this.getParentScope(scope);
if (parentScope === null) {
2020-05-10 18:54:57 +02:00
break;
}
scope = parentScope;
}
return null;
}
2020-05-10 18:54:57 +02:00
public findSymbolInTypeScopeOf(name: string, scope: TypeScope): TypeSymbolInfo | null {
while (true) {
const sym = this.lookupSymbolInTypeScope(name, scope);
if (sym !== null) {
return sym;
}
const parentTypeScope = this.getParentTypeScope(scope);
if (parentTypeScope === null) {
2020-05-10 23:50:42 +02:00
break;
}
scope = parentTypeScope;
}
return null;
}
2020-05-10 23:50:42 +02:00
public resolveReferenceExpression(node: BoltReferenceExpression): BoltDeclaration | null {
let scope = this.getScopeSurroundingNode(node);
if (node.name.modulePath !== null) {
while (true) {
let shouldSearchParentScopes = false;
let currScope = scope;
for (const name of node.name.modulePath) {
const sym = this.lookupSymbolInScope(name.text, currScope);
if (sym === null) {
shouldSearchParentScopes = true;
break;
}
if (sym.declarations[0].kind !== SyntaxKind.BoltModule) {
shouldSearchParentScopes = true;
break;
2020-03-03 14:53:54 +01:00
}
currScope = this.getScopeForNode(sym.declarations[0]);
2020-03-03 14:53:54 +01:00
}
if (!shouldSearchParentScopes) {
scope = currScope;
break;
2020-02-25 17:55:17 +01:00
}
const parentScope = this.getParentScope(scope);
if (parentScope === null) {
return null;
}
scope = parentScope;
}
2020-02-25 17:55:17 +01:00
}
const sym = this.findSymbolInScopeOf(node.name.name.text, scope);
if (sym === null) {
return null;
2020-02-25 17:55:17 +01:00
}
return sym.declarations[0]!;
2020-02-25 17:55:17 +01:00
}
public resolveTypeReferenceExpression(node: BoltReferenceTypeExpression): BoltTypeDeclaration | null {
const typeScope = this.getTypeScopeSurroundingNode(node);
const typeSym = this.findSymbolInTypeScopeOf(node.name.name.text, typeScope);
if (typeSym === null) {
return null;
2020-03-03 14:53:54 +01:00
}
return typeSym.declarations[0]!;
2020-03-03 14:53:54 +01:00
}
2020-02-25 17:55:17 +01:00
}