Make error checking on stdlib work again

This commit is contained in:
Sam Vervaeck 2020-05-26 22:58:31 +02:00
parent 8cb16db91a
commit b0649fd8ed
8 changed files with 256 additions and 143 deletions

View file

@ -91,7 +91,8 @@ node BoltSourceFile > SourceFile {
}
node BoltQualName {
modulePath: Option<Vec<BoltIdentifier>>,
modulePath: Option<BoltModulePath>,
name: BoltSymbol,
}
node BoltModulePath {

41
src/ast.d.ts vendored
View file

@ -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<BoltQualNameChild>
}
@ -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;

View file

@ -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,

View file

@ -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<string, Value>;
private fields: Map<string, Value>;
// 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 {

View file

@ -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<string> = {
@ -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<NodeVisitor>[] = [
CheckInvalidFilePaths,
CheckReferences,
CheckTypeAssignments,
];

View file

@ -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<ScopeSource> {
// 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<string, Scope>();
@ -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,6 +315,7 @@ class Scope {
}
} else {
const sym = {
id: nextSymbolId++,
name,
scope: this,
declarations: new Set([ node ]),
@ -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]);
//}
}

View file

@ -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<string, Type>();
private fieldTypes = new FastStringMap<string, RecordFieldType>();
constructor(
iterable: IterableIterator<[string, Type]>,
iterable?: Iterable<[string, RecordFieldType]>,
) {
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<number, OpaqueType>();
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:
@ -186,7 +254,6 @@ export class TypeChecker {
}
public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] {
const resolver = this.resolver;

View file

@ -72,11 +72,39 @@ export function uniq<T>(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<K extends PropertyKey, V> {
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}'.`);