UNPKG

typir

Version:

General purpose type checking library

159 lines (136 loc) 7.31 kB
/****************************************************************************** * 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 { TypeGraphListener } from '../graph/type-graph.js'; import { Type } from '../graph/type-node.js'; import { TypeInferenceCollectorListener, TypeInferenceRule, TypeInferenceRuleOptions } from '../services/inference.js'; import { TypirServices, TypirSpecifics } from '../typir.js'; import { removeFromArray } from '../utils/utils.js'; import { TypeDescriptor } from './type-descriptor.js'; /** * A listener for TypeReferences, who will be informed about the resolved/found type of the current TypeReference. */ export interface TypeReferenceListener<T extends Type, Specifics extends TypirSpecifics = TypirSpecifics> { /** * Informs when the type of the reference is resolved/found. * @param reference the currently resolved TypeReference * @param resolvedType Usually the resolved type is either 'Identifiable' or 'Completed', * in rare cases this type might be 'Invalid', e.g. if there are corresponding inference rules or TypeInitializers. */ onTypeReferenceResolved(reference: TypeReference<T, Specifics>, resolvedType: T): void; /** * Informs when the type of the reference is invalidated/removed. * @param reference the currently invalidate/unresolved TypeReference * @param previousType undefined occurs in the special case, that the TypeReference never resolved a type so far, * but new listeners already want to be informed about the (current) type. */ onTypeReferenceInvalidated(reference: TypeReference<T, Specifics>, previousType: T | undefined): void; } /** * A TypeReference accepts a specification for a type and searches for the corresponding type in the type system according to this specification. * Different TypeReferences might resolve to the same Type. * This class is used during the use case, when a Typir type uses other types for its properties, * e.g. class types use other types from the type system for describing the types of its fields ("use existing type"). * * The internal logic of a TypeReference is independent from the kind of the type to resolve. * A TypeReference takes care of the lifecycle of the types. * * Once the type is resolved, listeners are notified about this and all following changes of its state. */ export class TypeReference< T extends Type, Specifics extends TypirSpecifics = TypirSpecifics, // optional, otherwise Types would have to specify this generic, but they don't have the Specifics > implements TypeGraphListener, TypeInferenceCollectorListener<Specifics> { protected readonly descriptor: TypeDescriptor<T, Specifics>; protected readonly services: TypirServices<Specifics>; protected resolvedType: T | undefined = undefined; /** These listeners will be informed, whenever the resolved state of this TypeReference switched from undefined to an actual type or from an actual type to undefined. */ protected readonly listeners: Array<TypeReferenceListener<T, Specifics>> = []; constructor(descriptor: TypeDescriptor<T, Specifics>, services: TypirServices<Specifics>) { this.descriptor = descriptor; this.services = services; this.startResolving(); } dispose() { this.stopResolving(); this.listeners.splice(0, this.listeners.length); } protected startResolving(): void { // discard the previously resolved type (if any) this.resolvedType = undefined; // react on new types this.services.infrastructure.Graph.addListener(this); // react on new inference rules this.services.Inference.addListener(this); // don't react on state changes of already existing types which are not (yet) completed, since TypeDescriptors don't care about the initialization state of types // try to resolve now this.resolve(); } protected stopResolving(): void { // it is not required to listen to new types anymore, since the type is already resolved/found this.services.infrastructure.Graph.removeListener(this); this.services.Inference.removeListener(this); } getType(): T | undefined { return this.resolvedType; } /** * Resolves the referenced type, if the type is not yet resolved. * Note that the resolved type might not be completed yet. * @returns the result of the currently executed resolution */ protected resolve(): 'ALREADY_RESOLVED' | 'SUCCESSFULLY_RESOLVED' | 'RESOLVING_FAILED' { if (this.resolvedType) { // the type is already resolved => nothing to do return 'ALREADY_RESOLVED'; } // try to resolve the type const resolvedType = this.services.infrastructure.TypeResolver.tryToResolve<T>(this.descriptor); if (resolvedType) { // the type is successfully resolved! this.resolvedType = resolvedType; this.stopResolving(); // notify observers this.listeners.slice().forEach(listener => listener.onTypeReferenceResolved(this, resolvedType)); return 'SUCCESSFULLY_RESOLVED'; } else { // the type is not resolved (yet) return 'RESOLVING_FAILED'; } } addListener(listener: TypeReferenceListener<T, Specifics>, informAboutCurrentState: boolean): void { this.listeners.push(listener); if (informAboutCurrentState) { if (this.resolvedType) { listener.onTypeReferenceResolved(this, this.resolvedType); } else { listener.onTypeReferenceInvalidated(this, undefined); } } } removeListener(listener: TypeReferenceListener<T, Specifics>): void { removeFromArray(listener, this.listeners); } onAddedType(_addedType: Type, _key: string): void { // after adding a new type, try to resolve the type this.resolve(); // possible performance optimization: is it possible to do this more performant by looking at the "addedType"? } onRemovedType(removedType: Type, _key: string): void { // the resolved type of this TypeReference is removed! if (removedType === this.resolvedType) { // notify observers, that the type reference is broken this.listeners.slice().forEach(listener => listener.onTypeReferenceInvalidated(this, this.resolvedType!)); // start resolving the type again this.startResolving(); } } onAddedInferenceRule(_rule: TypeInferenceRule<Specifics>, _options: TypeInferenceRuleOptions): void { // after adding a new inference rule, try to resolve the type this.resolve(); // possible performance optimization: use only the new inference rule to resolve the type } onRemovedInferenceRule(_rule: TypeInferenceRule<Specifics>, _options: TypeInferenceRuleOptions): void { // empty, since removed inference rules don't help to resolve a type } }