Make error checking on stdlib work again
This commit is contained in:
parent
8cb16db91a
commit
b0649fd8ed
8 changed files with 256 additions and 143 deletions
|
@ -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
41
src/ast.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
||||
|
|
|
@ -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]);
|
||||
//}
|
||||
|
||||
}
|
181
src/types.ts
181
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<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;
|
||||
|
|
28
src/util.ts
28
src/util.ts
|
@ -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}'.`);
|
||||
|
|
Loading…
Reference in a new issue