From b0649fd8edddbb60fe2653eb58de8e4c9ed32aa7 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Tue, 26 May 2020 22:58:31 +0200 Subject: [PATCH] Make error checking on stdlib work again --- src/ast-spec.txt | 3 +- src/ast.d.ts | 41 +++++++---- src/checks.ts | 4 +- src/evaluator.ts | 45 +++++------- src/frontend.ts | 7 +- src/resolver.ts | 86 ++++++++++++---------- src/types.ts | 185 ++++++++++++++++++++++++++++++++--------------- src/util.ts | 28 +++++++ 8 files changed, 256 insertions(+), 143 deletions(-) diff --git a/src/ast-spec.txt b/src/ast-spec.txt index dcba9fb7b..1c0a27a93 100644 --- a/src/ast-spec.txt +++ b/src/ast-spec.txt @@ -91,7 +91,8 @@ node BoltSourceFile > SourceFile { } node BoltQualName { - modulePath: Option>, + modulePath: Option, + name: BoltSymbol, } node BoltModulePath { diff --git a/src/ast.d.ts b/src/ast.d.ts index 9dbdf068a..34fecc7da 100644 --- a/src/ast.d.ts +++ b/src/ast.d.ts @@ -631,13 +631,15 @@ export interface BoltIdentifier extends SyntaxBase { } export type BoltIdentifierParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | never export type BoltIdentifierAnyParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | BoltSourceFile @@ -683,13 +685,15 @@ export interface BoltOperator extends SyntaxBase { } export type BoltOperatorParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | never export type BoltOperatorAnyParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | BoltSourceFile @@ -1179,13 +1183,15 @@ export interface BoltGtSign extends SyntaxBase { } export type BoltGtSignParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | never export type BoltGtSignAnyParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | BoltSourceFile @@ -1223,13 +1229,15 @@ export interface BoltExMark extends SyntaxBase { } export type BoltExMarkParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | never export type BoltExMarkAnyParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | BoltSourceFile @@ -1267,13 +1275,15 @@ export interface BoltLtSign extends SyntaxBase { } export type BoltLtSignParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | never export type BoltLtSignAnyParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | BoltSourceFile @@ -1311,13 +1321,15 @@ export interface BoltVBar extends SyntaxBase { } export type BoltVBarParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | never export type BoltVBarAnyParent -= BoltQuoteExpression += BoltQualName +| BoltQuoteExpression | BoltReferenceExpression | BoltFunctionDeclaration | BoltSourceFile @@ -2342,7 +2354,8 @@ export type BoltSourceFileChild export interface BoltQualName extends SyntaxBase { kind: SyntaxKind.BoltQualName; - modulePath: BoltIdentifier[] | null; + modulePath: BoltModulePath | null; + name: BoltSymbol; parentNode: BoltQualNameParent; getChildNodes(): IterableIterator } @@ -6936,7 +6949,7 @@ export function createBoltParenthesized(text: string, span?: TextSpan | null): B export function createBoltBraced(text: string, span?: TextSpan | null): BoltBraced; export function createBoltBracketed(text: string, span?: TextSpan | null): BoltBracketed; export function createBoltSourceFile(elements: BoltSourceElement[], package: Package, span?: TextSpan | null): BoltSourceFile; -export function createBoltQualName(modulePath: BoltIdentifier[] | null, span?: TextSpan | null): BoltQualName; +export function createBoltQualName(modulePath: BoltModulePath | null, name: BoltSymbol, span?: TextSpan | null): BoltQualName; export function createBoltModulePath(isAbsolute: boolean, elements: BoltIdentifier[], span?: TextSpan | null): BoltModulePath; export function createBoltReferenceTypeExpression(path: BoltModulePath, arguments: BoltTypeExpression[] | null, span?: TextSpan | null): BoltReferenceTypeExpression; export function createBoltFunctionTypeExpression(params: BoltParameter[], returnType: BoltTypeExpression | null, span?: TextSpan | null): BoltFunctionTypeExpression; diff --git a/src/checks.ts b/src/checks.ts index fc6e2b5a2..38f499903 100644 --- a/src/checks.ts +++ b/src/checks.ts @@ -32,7 +32,7 @@ export class CheckInvalidFilePaths extends NodeVisitor { } -export class CheckReference extends NodeVisitor { +export class CheckReferences extends NodeVisitor { constructor( @inject private diagnostics: DiagnosticPrinter, @@ -75,7 +75,7 @@ export class CheckReference extends NodeVisitor { protected visitBoltReferenceTypeExpression(node: BoltReferenceTypeExpression) { const scope = this.resolver.getScopeForNode(node, ScopeType.Type); assert(scope !== null); - const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!); + const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node.path), scope!); if (resolvedSym === null) { this.diagnostics.add({ message: E_TYPE_DECLARATION_NOT_FOUND, diff --git a/src/evaluator.ts b/src/evaluator.ts index 1c2cb1cbb..e439df32b 100644 --- a/src/evaluator.ts +++ b/src/evaluator.ts @@ -4,44 +4,39 @@ import { FastStringMap, assert } from "./util" import { emitNode } from "./emitter"; import { Type, TypeChecker, RecordType } from "./types"; -export interface Value { - readonly type?: Type; - data: ValueData; -} +//export class Record { -class Record { - - private fields: Map; +// private fields: Map; - constructor(fields: Iterable<[string, Value]>) { - this.fields = new Map(fields); - } +// constructor(fields: Iterable<[string, Value]>) { +// this.fields = new Map(fields); +// } - public clone(): Record { - return new Record(this.fields); - } +// public clone(): Record { +// return new Record(this.fields); +// } - public addField(name: string, value: Value): void { - this.fields.set(name, value); - } +// public addField(name: string, value: Value): void { +// this.fields.set(name, value); +// } - public deleteField(name: string): void { - this.fields.delete(name); - } +// public deleteField(name: string): void { +// this.fields.delete(name); +// } - public clear(): void { - this.fields.clear(); - } +// public clear(): void { +// this.fields.clear(); +// } -} +//} -type ValueData +export type Value = string | undefined | boolean | number | bigint - | Record + | object class Environment { diff --git a/src/frontend.ts b/src/frontend.ts index 5129d7942..d93f84613 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -6,7 +6,7 @@ import { EventEmitter } from "events" import { Program } from "./program" import { emitNode } from "./emitter" -import { Syntax, BoltSourceFile, SourceFile, NodeVisitor } from "./ast" +import { Syntax, BoltSourceFile, SourceFile, NodeVisitor, createBoltConditionalCase } from "./ast" import { getFileStem, MapLike } from "./util" import { verbose, memoize } from "./util" import { Container, Newable } from "./di" @@ -17,7 +17,7 @@ import { TransformManager } from "./transforms/index" import {DiagnosticPrinter} from "./diagnostics" import { TypeChecker } from "./types" import { checkServerIdentity } from "tls" -import { CheckInvalidFilePaths, CheckTypeAssignments } from "./checks" +import { CheckInvalidFilePaths, CheckTypeAssignments, CheckReferences } from "./checks" import { SymbolResolver, BoltSymbolResolutionStrategy } from "./resolver" const targetExtensions: MapLike = { @@ -84,6 +84,7 @@ export class Frontend { public check(program: Program) { + const resolver = new SymbolResolver(program, new BoltSymbolResolutionStrategy); const checker = new TypeChecker(resolver); @@ -91,9 +92,11 @@ export class Frontend { container.bindSelf(program); container.bindSelf(resolver); container.bindSelf(checker); + container.bindSelf(this.diagnostics); const checks: Newable[] = [ CheckInvalidFilePaths, + CheckReferences, CheckTypeAssignments, ]; diff --git a/src/resolver.ts b/src/resolver.ts index 70806df3a..e02ffbc79 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -28,6 +28,12 @@ export class SymbolPath { 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, + emitNode(node.name), + ); case SyntaxKind.BoltIdentifier: return new SymbolPath([], false, emitNode(node)); case SyntaxKind.BoltQualName: @@ -160,7 +166,7 @@ export class BoltSymbolResolutionStrategy implements ResolutionStrategy { } } - public introducesNewScope(node: BoltSyntax, kind: ScopeType): boolean { + public introducesNewScope(node: Syntax, kind: ScopeType): boolean { switch (kind) { case ScopeType.Variable: return node.kind === SyntaxKind.BoltSourceFile @@ -185,6 +191,11 @@ export class BoltSymbolResolutionStrategy implements ResolutionStrategy { public *getNextScopeSources(source: ScopeSource, kind: ScopeType): IterableIterator { + // If we are in the global scope, there is no scope above it. + if (source instanceof GlobalScopeSource) { + return; + } + // If we are at a scope that was created by an AST node, we // search the nearest parent that introduces a new scope of // the requested kind. If no such scope was found, then we @@ -196,12 +207,13 @@ export class BoltSymbolResolutionStrategy implements ResolutionStrategy { yield new PackageScopeSource(currNode.package); return; } - if (this.introducesNewScope(currNode, kind)) { - yield source; + const nextNode = currNode.parentNode; + assert(nextNode !== null); + if (this.introducesNewScope(nextNode, kind)) { + yield new NodeScopeSource(nextNode); return; } - assert(currNode.parentNode !== null); - currNode = currNode.parentNode; + currNode = nextNode; } } @@ -212,15 +224,14 @@ export class BoltSymbolResolutionStrategy implements ResolutionStrategy { return; } - // If we are in the global scope, there is no scope above it. - if (source instanceof GlobalScopeSource) { - return; - } + throw new Error(`Unknown scope source provided.`) } } +let nextSymbolId = 1; + class Scope { private static scopeCache = new FastStringMap(); @@ -245,7 +256,7 @@ class Scope { return Scope.scopeCache.get(this.globallyUniqueKey); } const newScope = new Scope(this.resolver, kind, this.source); - Scope.scopeCache.set(this.globallyUniqueKey, newScope); + Scope.scopeCache.set(newScope.globallyUniqueKey, newScope); return newScope; } @@ -254,10 +265,10 @@ class Scope { for (const nextSource of this.resolver.strategy.getNextScopeSources(this.source, this.kind)) { const key = `${this.kind}:${nextSource.id}`; if (Scope.scopeCache.has(key)) { - yield Scope.scopeCache.get(this.globallyUniqueKey); + yield Scope.scopeCache.get(key); } else { const newScope = new Scope(this.resolver, this.kind, nextSource); - Scope.scopeCache.set(this.globallyUniqueKey, newScope); + Scope.scopeCache.set(key, newScope); yield newScope; } } @@ -304,10 +315,11 @@ class Scope { } } else { const sym = { - name, - scope: this, - declarations: new Set([ node ]), - isExported: isExported(node) + id: nextSymbolId++, + name, + scope: this, + declarations: new Set([ node ]), + isExported: isExported(node) } as SymbolInfo; this.symbols.set(name, sym); } @@ -315,7 +327,8 @@ class Scope { } -interface SymbolInfo { +export interface SymbolInfo { + id: number; name: string; scope: Scope; isExported: boolean; @@ -409,22 +422,16 @@ export class SymbolResolver { // We will keep looping until we are at the topmost module of // the package corresponding to `node`. - while (true) { - - // An empty stack means we've looked everywhere but did not find a scope that contained - // the given module path. - if (stack.length === 0) { - return null; - } - + while (stack.length > 0) { let shouldSearchNextScopes = false; - let currScope = stack.pop()!; + let scope = stack.pop()!; + 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) { - const sym = currScope.getSymbol(name); + const sym = currScope.getLocalSymbol(name); if (sym === null) { shouldSearchNextScopes = true; break; @@ -435,19 +442,26 @@ export class SymbolResolver { // If the previous loop did not fail, we are done. if (!shouldSearchNextScopes) { - scope = currScope; - break; + return currScope; } - // We continue the outer loop by getting the parent module, which should be - // equivalent to getting the parent module scope. + // We continue the outer loop by going up one scope. for (const nextScope of scope.getNextScopes()) { stack.push(nextScope); } } - return scope; + return null; + } + + public getSymbolForNode(node: Syntax) { + assert(this.strategy.hasSymbol(node)); + const scope = this.getScopeForNode(node, this.strategy.getScopeType(node)); + if (scope === null) { + return null; + } + return scope.getSymbol(this.strategy.getSymbolName(node)); } public resolveSymbolPath(path: SymbolPath, scope: Scope): SymbolInfo | null { @@ -486,12 +500,4 @@ export class SymbolResolver { return sym; } - //public resolveTypeName(name: string, node: Syntax): Type | null { - // const sym = this.findSymbolInScopeOf(name, this.getScopeSurroundingNode(node)); - // if (sym === null) { - // return null; - // } - // return this.getTypeOfNode(sym.declarations[0]); - //} - } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index c274e54ad..88d7d559d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,8 @@ -import { FastStringMap, assert } from "./util"; +import { FastStringMap, assert, isPlainObject } from "./util"; import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString } from "./ast"; -import { getSymbolPathFromNode, ScopeType, SymbolResolver } from "./resolver"; +import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo } from "./resolver"; +import { Value } from "./evaluator"; enum TypeKind { OpaqueType, @@ -9,6 +10,7 @@ enum TypeKind { NeverType, FunctionType, RecordType, + PlainRecordFieldType, VariantType, UnionType, TupleType, @@ -24,24 +26,32 @@ export type Type | TupleType abstract class TypeBase { - abstract kind: TypeKind; + + public abstract kind: TypeKind; + + constructor(public symbol?: SymbolInfo) { + + } + } export class OpaqueType extends TypeBase { - kind: TypeKind.OpaqueType = TypeKind.OpaqueType; + + public kind: TypeKind.OpaqueType = TypeKind.OpaqueType; + } export class AnyType extends TypeBase { - kind: TypeKind.AnyType = TypeKind.AnyType; + public kind: TypeKind.AnyType = TypeKind.AnyType; } export class NeverType extends TypeBase { - kind: TypeKind.NeverType = TypeKind.NeverType; + public kind: TypeKind.NeverType = TypeKind.NeverType; } export class FunctionType extends TypeBase { - kind: TypeKind.FunctionType = TypeKind.FunctionType; + public kind: TypeKind.FunctionType = TypeKind.FunctionType; constructor( public paramTypes: Type[], @@ -54,7 +64,7 @@ export class FunctionType extends TypeBase { return this.paramTypes.length; } - public getParamTypeAtIndex(index: number) { + public getTypeAtParameterIndex(index: number) { if (index < 0 || index >= this.paramTypes.length) { throw new Error(`Could not get the parameter type at index ${index} because the index was out of bounds.`); } @@ -65,7 +75,7 @@ export class FunctionType extends TypeBase { export class VariantType extends TypeBase { - kind: TypeKind.VariantType = TypeKind.VariantType; + public kind: TypeKind.VariantType = TypeKind.VariantType; constructor(public elementTypes: Type[]) { super(); @@ -77,36 +87,57 @@ export class VariantType extends TypeBase { } -export function isVariantType(value: any): value is VariantType { - return value instanceof VariantType; +export class UnionType extends TypeBase { + + public kind: TypeKind.UnionType = TypeKind.UnionType; + +} + +export type RecordFieldType + = PlainRecordFieldType + +class PlainRecordFieldType extends TypeBase { + + public kind: TypeKind.PlainRecordFieldType = TypeKind.PlainRecordFieldType; + + constructor(public type: Type) { + super(); + } + } export class RecordType { - kind: TypeKind.RecordType = TypeKind.RecordType; + public kind: TypeKind.RecordType = TypeKind.RecordType; - private fieldTypes = new FastStringMap(); + private fieldTypes = new FastStringMap(); constructor( - iterable: IterableIterator<[string, Type]>, + iterable?: Iterable<[string, RecordFieldType]>, ) { - for (const [name, type] of iterable) { - this.fieldTypes.set(name, type); + if (iterable !== undefined) { + for (const [name, type] of iterable) { + this.fieldTypes.set(name, type); + } } } + public addField(name: string, type: RecordFieldType): void { + this.fieldTypes.set(name, type); + } + public hasField(name: string) { return name in this.fieldTypes; } - public getTypeOfField(name: string) { + public getFieldType(name: string) { return this.fieldTypes.get(name); } -} + public clear(): void { + this.fieldTypes.clear(); + } -export function isRecordType(value: any): value is RecordType { - return value.kind === TypeKind.RecordType; } export class TupleType extends TypeBase { @@ -119,45 +150,35 @@ export class TupleType extends TypeBase { } -export function isTupleType(value: any): value is TupleType { - return value.kind === TypeKind.TupleType; -} +//export function narrowType(outer: Type, inner: Type): Type { +// if (isAnyType(outer) || isNeverType(inner)) { +// return inner; +// } +// // TODO cover the other cases +// return outer; +//} -export function isVoidType(value: any) { - return isTupleType(value) && value.elementTypes.length === 0; -} - -export function narrowType(outer: Type, inner: Type): Type { - if (isAnyType(outer) || isNeverType(inner)) { - return inner; - } - // TODO cover the other cases - return outer; -} - -export function intersectTypes(a: Type, b: Type): Type { - if (isNeverType(a) || isNeverType(b)) { - return new NeverType(); - } - if (isAnyType(b)) { - return a - } - if (isAnyType(a)) { - return b; - } - if (isFunctionType(a) && isFunctionType(b)) { - if (a.paramTypes.length !== b.paramTypes.length) { - return new NeverType(); - } - const returnType = intersectTypes(a.returnType, b.returnType); - const paramTypes = a.paramTypes - .map((_, i) => intersectTypes(a.paramTypes[i], b.paramTypes[i])); - return new FunctionType(paramTypes, returnType) - } - return new NeverType(); -} - -export type TypeInfo = never; +//export function intersectTypes(a: Type, b: Type): Type { +// if (a.kind === TypeKind.NeverType && b.kind === TypeKind.NeverType) +// return new NeverType(); +// } +// if (a.kind == TypeKind.AnyType) { +// return a +// } +// if (isAnyType(a)) { +// return b; +// } +// if (a.kind === TypeKind.FunctionType && b.kind === TypeKind.FunctionType) { +// if (a.paramTypes.length !== b.paramTypes.length) { +// return new NeverType(); +// } +// const returnType = intersectTypes(a.returnType, b.returnType); +// const paramTypes = a.paramTypes +// .map((_, i) => intersectTypes(a.paramTypes[i], b.paramTypes[i])); +// return new FunctionType(paramTypes, returnType) +// } +// return new NeverType(); +//} interface AssignmentError { node: Syntax; @@ -165,10 +186,57 @@ interface AssignmentError { export class TypeChecker { + private opaqueTypes = new FastStringMap(); + + private stringType = new OpaqueType(); + private intType = new OpaqueType(); + private floatType = new OpaqueType(); + constructor(private resolver: SymbolResolver) { } + public getTypeOfValue(value: Value): Type { + if (typeof(value) === 'string') { + return this.stringType; + } else if (typeof(value) === 'bigint') { + return this.intType; + } else if (typeof(value) === 'number') { + return this.floatType; + } else if (isPlainObject(value)) { + const recordType = new RecordType() + for (const key of Object.keys(value)) { + recordType.addField(key, new PlainRecordFieldType(this.getTypeOfValue(value[key]))); + } + return recordType; + } else { + throw new Error(`Could not determine type of given value.`); + } + } + + public getTypeOfNode(node: Syntax) { + + switch (node.kind) { + + case SyntaxKind.BoltRecordDeclaration: + { + const recordSym = this.resolver.getSymbolForNode(node); + assert(recordSym !== null); + if (this.opaqueTypes.has(recordSym!.id)) { + return this.opaqueTypes.get(recordSym!.id); + } + const opaqueType = new OpaqueType(recordSym!); + this.opaqueTypes.set(recordSym!.id, opaqueType); + break; + } + + case SyntaxKind.BoltConstantExpression: + return this.getTypeOfValue(node.value); + + } + + } + public isVoid(node: Syntax): boolean { switch (node.kind) { case SyntaxKind.BoltTupleExpression: @@ -185,7 +253,6 @@ export class TypeChecker { // Next, add the resulting types as type hints to `fnReturnType`. } - public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] { diff --git a/src/util.ts b/src/util.ts index 33291f392..d9b97f134 100644 --- a/src/util.ts +++ b/src/util.ts @@ -72,11 +72,39 @@ export function uniq(elements: T[]): T[] { return out; } +function getTag(value: any) { + if (value == null) { + return value === undefined ? '[object Undefined]' : '[object Null]' + } + return toString.call(value) +} + +function isObjectLike(value: any) { + return typeof value === 'object' && value !== null; +} + +export function isPlainObject(value: any): value is object { + if (!isObjectLike(value) || getTag(value) != '[object Object]') { + return false + } + if (Object.getPrototypeOf(value) === null) { + return true + } + let proto = value + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto) + } + return Object.getPrototypeOf(value) === proto +} export class FastStringMap { private mapping = Object.create(null); + public clear(): void { + this.mapping.clear(); + } + public get(key: K): V { if (!(key in this.mapping)) { throw new Error(`No value found for key '${key}'.`);