typir
Version:
General purpose type checking library
113 lines (101 loc) • 5.64 kB
text/typescript
/******************************************************************************
* Copyright 2024 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { isType, Type } from '../graph/type-node.js';
import { TypirServices, TypirSpecifics } from '../typir.js';
import { TypeInitializer } from './type-initializer.js';
import { TypeReference } from './type-reference.js';
export type BasicTypeDescriptor<T extends Type, Specifics extends TypirSpecifics> =
| T // the wanted type
| string // identifier of the type (to be searched in the type graph)
| TypeInitializer<T, Specifics> // delayed creation of types
| TypeReference<T, Specifics> // reference to a (maybe delayed) type
| Specifics['LanguageType'] // language node to infer the final type from
;
/**
* This TypeScript type defines the possible ways to identify a desired Typir type.
*/
export type TypeDescriptor<T extends Type, Specifics extends TypirSpecifics> =
| BasicTypeDescriptor<T, Specifics> // all base type descriptors
| (() => BasicTypeDescriptor<T, Specifics>) // all type descriptors might be given as functions as well, in order to ease delayed specifications
;
export interface TypeResolvingService<Specifics extends TypirSpecifics> {
/**
* Tries to find the specified type in the type system.
* This method does not care about the initialization state of the found type,
* this method is restricted to just search and find any type according to the given TypeDescriptor.
* @param descriptor the specification for the desired type
* @returns the found type; or undefined, if there is no such type in the type system
*/
tryToResolve<T extends Type>(descriptor: TypeDescriptor<T, Specifics>): T | undefined;
/**
* Finds the specified type in the type system.
* This method does not care about the initialization state of the found type,
* this method is restricted to just search and find any type according to the given TypeDescriptor.
* @param descriptor the specification for the desired type
* @returns the found type; or an exception, if the type cannot be resolved
*/
resolve<T extends Type>(descriptor: TypeDescriptor<T, Specifics>): T;
}
export class DefaultTypeResolver<Specifics extends TypirSpecifics> implements TypeResolvingService<Specifics> {
protected readonly services: TypirServices<Specifics>;
constructor(services: TypirServices<Specifics>) {
this.services = services;
}
tryToResolve<T extends Type>(descriptor: TypeDescriptor<T, Specifics>): T | undefined {
if (isType(descriptor)) {
// TODO is there a way to explicitly enforce/ensure "as T"?
return descriptor as T;
} else if (typeof descriptor === 'string') {
return this.services.infrastructure.Graph.getType(descriptor) as T;
} else if (descriptor instanceof TypeInitializer) {
return descriptor.getTypeInitial();
} else if (descriptor instanceof TypeReference) {
return descriptor.getType();
} else if (typeof descriptor === 'function') {
// execute the function and try to recursively resolve the returned result again
return this.tryToResolve<T>((descriptor as () => BasicTypeDescriptor<T, Specifics>).call(descriptor));
} else { // the descriptor is of type 'known' => do type inference on it
const result = this.services.Inference.inferType(descriptor);
// TODO failures must not be cached, otherwise a type will never be found in the future!!
if (isType(result)) {
return result as T;
} else {
return undefined;
}
}
}
resolve<T extends Type>(descriptor: TypeDescriptor<T, Specifics>): T {
if (isType(descriptor)) {
return descriptor as T;
} else if (typeof descriptor === 'string') {
return this.handleError<T>(
this.services.infrastructure.Graph.getType(descriptor) as T | undefined,
`A type with identifier '${descriptor}' as TypeDescriptor does not exist in the type graph.`
);
} else if (descriptor instanceof TypeInitializer) {
return this.handleError(descriptor.getTypeFinal(), "This TypeInitializer don't provide a type.");
} else if (descriptor instanceof TypeReference) {
return this.handleError(descriptor.getType(), 'This TypeReference has no resolved type.');
} else if (typeof descriptor === 'function') {
// execute the function and try to recursively resolve the returned result again
return this.resolve<T>((descriptor as () => BasicTypeDescriptor<T, Specifics>).call(descriptor));
} else {
const result = this.services.Inference.inferType(descriptor);
if (isType(result)) {
return result as T;
} else {
throw new Error(`For '${this.services.Printer.printLanguageNode(descriptor, false)}' as TypeDescriptor, no type can be inferred.`);
}
}
}
protected handleError<T extends Type>(result: T | undefined, errorMessage: string): T {
if (result) {
return result as T;
} else {
throw new Error(errorMessage);
}
}
}