UNPKG

typir

Version:

General purpose type checking library

195 lines (181 loc) 11.8 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 { DefaultGraphAlgorithms, GraphAlgorithms } from './graph/graph-algorithms.js'; import { TypeGraph } from './graph/type-graph.js'; import { DefaultTypeResolver, TypeResolvingService } from './initialization/type-descriptor.js'; import { BottomFactoryService, BottomKind, BottomKindName } from './kinds/bottom/bottom-kind.js'; import { ClassFactoryService, ClassKind, ClassKindName } from './kinds/class/class-kind.js'; import { FunctionFactoryService, FunctionKind, FunctionKindName } from './kinds/function/function-kind.js'; import { PrimitiveFactoryService, PrimitiveKind, PrimitiveKindName } from './kinds/primitive/primitive-kind.js'; import { TopFactoryService, TopKind, TopKindName } from './kinds/top/top-kind.js'; import { DefaultTypeAssignability, TypeAssignability } from './services/assignability.js'; import { DefaultLanguageNodeInferenceCaching, DefaultTypeRelationshipCaching, LanguageNodeInferenceCaching, TypeRelationshipCaching } from './services/caching.js'; import { DefaultTypeConversion, TypeConversion } from './services/conversion.js'; import { DefaultTypeEquality, TypeEquality } from './services/equality.js'; import { DefaultTypeInferenceCollector, TypeInferenceCollector } from './services/inference.js'; import { DefaultKindRegistry, KindRegistry } from './services/kind-registry.js'; import { DefaultLanguageService, LanguageService } from './services/language.js'; import { DefaultOperatorFactory, OperatorFactoryService } from './services/operator.js'; import { DefaultTypeConflictPrinter, ProblemPrinter } from './services/printing.js'; import { DefaultSubType, SubType } from './services/subtype.js'; import { DefaultValidationCollector, DefaultValidationConstraints, ValidationCollector, ValidationConstraints, ValidationMessageProperties } from './services/validation.js'; import { inject, Module } from './utils/dependency-injection.js'; /** * Some design decisions for Typir: * - We don't use a graph library like graphology to realize the type graph in order to be more flexible and to reduce external dependencies. * - Where should inference rules be stored? Inference rules are stored in the central service, optionally bound to types in order to simplify removal of deleted types. * Inference rules are not linked to kinds (at least for now), since different types (of the same kind) might have different inference rules. * - No NameProvider for the name of types, since the name depends on the type of the kind => change the implementation of the kind. * - The type 'void' has a primitive kind (no dedicated kind for now). * - Once created/initialized, types are constant, e.g. no additional fields can be added to classes (but their types might be resolved a bit later). * - It is possible to use two different Typir instances side-by-side within the same application in general, * since the services are not realized by global functions, but by methods of classes which implement service interfaces. */ /** Some open design questions for future releases TODO * - How to bundle Typir configurations for reuse ("presets")? */ export type TypirServices<Specifics extends TypirSpecifics> = { readonly Assignability: TypeAssignability; readonly Equality: TypeEquality; readonly Conversion: TypeConversion; readonly Subtype: SubType; readonly Inference: TypeInferenceCollector<Specifics>; readonly caching: { readonly TypeRelationships: TypeRelationshipCaching; readonly LanguageNodeInference: LanguageNodeInferenceCaching; }; readonly Printer: ProblemPrinter<Specifics>; readonly Language: LanguageService<Specifics>; readonly validation: { readonly Collector: ValidationCollector<Specifics>; readonly Constraints: ValidationConstraints<Specifics>; }; readonly factory: { readonly Primitives: PrimitiveFactoryService<Specifics>; readonly Functions: FunctionFactoryService<Specifics>; readonly Classes: ClassFactoryService<Specifics>; readonly Top: TopFactoryService<Specifics>; readonly Bottom: BottomFactoryService<Specifics>; readonly Operators: OperatorFactoryService<Specifics>; }; readonly infrastructure: { readonly Graph: TypeGraph; readonly GraphAlgorithms: GraphAlgorithms; readonly Kinds: KindRegistry<Specifics>; readonly TypeResolver: TypeResolvingService<Specifics>; }; }; export function createDefaultTypirServicesModule<Specifics extends TypirSpecifics>(): Module<TypirServices<Specifics>> { return { Assignability: (services) => new DefaultTypeAssignability(services), Equality: (services) => new DefaultTypeEquality(services), Conversion: (services) => new DefaultTypeConversion(services), Subtype: (services) => new DefaultSubType(services), Inference: (services) => new DefaultTypeInferenceCollector(services), caching: { TypeRelationships: (services) => new DefaultTypeRelationshipCaching(services), LanguageNodeInference: () => new DefaultLanguageNodeInferenceCaching(), }, Printer: () => new DefaultTypeConflictPrinter(), Language: () => new DefaultLanguageService(), validation: { Collector: (services) => new DefaultValidationCollector(services), Constraints: (services) => new DefaultValidationConstraints(services), }, factory: { Primitives: (services) => services.infrastructure.Kinds.getOrCreateKind(PrimitiveKindName, services => new PrimitiveKind(services)), Functions: (services) => services.infrastructure.Kinds.getOrCreateKind(FunctionKindName, services => new FunctionKind(services)), Classes: (services) => services.infrastructure.Kinds.getOrCreateKind(ClassKindName, services => new ClassKind(services, { typing: 'Nominal' })), Top: (services) => services.infrastructure.Kinds.getOrCreateKind(TopKindName, services => new TopKind(services)), Bottom: (services) => services.infrastructure.Kinds.getOrCreateKind(BottomKindName, services => new BottomKind(services)), Operators: (services) => new DefaultOperatorFactory(services), }, infrastructure: { Graph: () => new TypeGraph(), GraphAlgorithms: (services) => new DefaultGraphAlgorithms(services), Kinds: (services) => new DefaultKindRegistry(services), TypeResolver: (services) => new DefaultTypeResolver(services), }, }; } /** * Creates the TypirServices with the default module containing the default implements for Typir, * which might be exchanged by the given optional customized modules. * @param customization1 optional Typir module with customizations * @param customization2 optional Typir module with customizations * @param customization3 optional Typir module with customizations * @param customization4 optional Typir module with customizations * @returns a Typir instance, i.e. the TypirServices with implementations for all services */ export function createTypirServices<Specifics extends TypirSpecifics>( customization1?: Module<TypirServices<Specifics>, PartialTypirServices<Specifics>>, customization2?: Module<TypirServices<Specifics>, PartialTypirServices<Specifics>>, customization3?: Module<TypirServices<Specifics>, PartialTypirServices<Specifics>>, customization4?: Module<TypirServices<Specifics>, PartialTypirServices<Specifics>>, ): TypirServices<Specifics> { return inject( // use the default implementations for all core Typir services createDefaultTypirServicesModule<Specifics>(), // optionally add some more language-specific customization, e.g. for ... customization1, // ... production customization2, // ... testing (in order to replace some customizations of production) customization3, // ... testing (e.g. to have customizations for all test cases and for single test cases) customization4, // ... for even more flexibility ); } /** * Creates the TypirServices with the default module containing the default implementations for Typir, * which might be exchanged by the given optional customized modules. * Additionally, some new services are defined, and implementations for them are registered. * @param moduleForAdditionalServices contains the configurations for all added services * @param customization1 optional Typir module with customizations (for new and existing services) * @param customization2 optional Typir module with customizations (for new and existing services) * @param customization3 optional Typir module with customizations (for new and existing services) * @param customization4 optional Typir module with customizations (for new and existing services) * @returns a Typir instance, i.e. the TypirServices consisting of the default services and the added services, * with implementations for all services */ export function createTypirServicesWithAdditionalServices<Specifics extends TypirSpecifics, AdditionalServices>( moduleForAdditionalServices: Module<TypirServices<Specifics> & AdditionalServices, AdditionalServices>, customization1?: Module<TypirServices<Specifics> & AdditionalServices, DeepPartial<TypirServices<Specifics> & AdditionalServices>>, customization2?: Module<TypirServices<Specifics> & AdditionalServices, DeepPartial<TypirServices<Specifics> & AdditionalServices>>, customization3?: Module<TypirServices<Specifics> & AdditionalServices, DeepPartial<TypirServices<Specifics> & AdditionalServices>>, customization4?: Module<TypirServices<Specifics> & AdditionalServices, DeepPartial<TypirServices<Specifics> & AdditionalServices>>, ): TypirServices<Specifics> & AdditionalServices { return inject( // use the default implementations for all core Typir services createDefaultTypirServicesModule<Specifics>(), // add implementations for all additional services moduleForAdditionalServices, // optionally add some more language-specific customization, e.g. for ... customization1, // ... production customization2, // ... testing (in order to replace some customizations of production) customization3, // ... testing (e.g. to have customizations for all test cases and for single test cases) customization4, // ... for even more flexibility ); } /** * A deep partial type definition for services. We look into T to see whether its type definition contains * any methods. If it does, it's one of our services and therefore should not be partialized. * Copied from Langium. */ //eslint-disable-next-line @typescript-eslint/ban-types export type DeepPartial<T> = T[keyof T] extends Function ? T : { [P in keyof T]?: DeepPartial<T[P]>; } /** Makes only the specified properties of the given type optional */ export type MakePropertyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; /** * Language-specific services to be partially overridden via dependency injection. */ export type PartialTypirServices<Specifics extends TypirSpecifics> = DeepPartial<TypirServices<Specifics>> /** * This type collects all TypeScript types which might be customized by applications or bindings for language workbenches. */ export interface TypirSpecifics { LanguageType: unknown; ValidationMessageProperties: ValidationMessageProperties; }