typir
Version:
General purpose type checking library
145 lines • 10.1 kB
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 { Type, TypeDetails } from '../../graph/type-node.js';
import { TypeInitializer } from '../../initialization/type-initializer.js';
import { TypeReference } from '../../initialization/type-reference.js';
import { TypeDescriptor } from '../../initialization/type-descriptor.js';
import { ValidationRule } from '../../services/validation.js';
import { TypirSpecifics, TypirServices } from '../../typir.js';
import { InferCurrentTypeRule, NameTypePair, RegistrationOptions } from '../../utils/utils-definitions.js';
import { TypeCheckStrategy } from '../../utils/utils-type-comparison.js';
import { Kind, KindOptions } from '../kind.js';
import { AvailableFunctionsManager } from './function-overloading.js';
import { FunctionType } from './function-type.js';
export interface FunctionKindOptions<Specifics extends TypirSpecifics> extends KindOptions {
enforceFunctionName: boolean;
enforceInputParameterNames: boolean;
enforceOutputParameterName: boolean;
/** Will be used only internally as prefix for the unique identifiers for function type names. */
identifierPrefix: string;
/** If a function has no output type (e.g. "void" functions), this type is returned during the type inference of calls to these functions.
* The default value "THROW_ERROR" indicates to throw an error, i.e. type inference for calls of such functions are not allowed. */
typeToInferForCallsOfFunctionsWithoutOutput: 'THROW_ERROR' | TypeDescriptor<Type, Specifics>;
subtypeParameterChecking: TypeCheckStrategy;
}
export declare const FunctionKindName = "FunctionKind";
export interface CreateParameterDetails<Specifics extends TypirSpecifics> {
name: string;
type: TypeDescriptor<Type, Specifics>;
}
export interface FunctionTypeDetails<Specifics extends TypirSpecifics> extends TypeDetails<Specifics> {
functionName: string;
/** The order of parameters is important! */
outputParameter: CreateParameterDetails<Specifics> | undefined;
inputParameters: Array<CreateParameterDetails<Specifics>>;
}
export interface CreateFunctionTypeDetails<Specifics extends TypirSpecifics> extends FunctionTypeDetails<Specifics> {
inferenceRulesForDeclaration: Array<InferCurrentTypeRule<FunctionType, Specifics>>;
inferenceRulesForCalls: Array<InferFunctionCall<Specifics, Specifics['LanguageType']>>;
}
export interface InferFunctionCall<Specifics extends TypirSpecifics, T extends Specifics['LanguageType'] = Specifics['LanguageType']> extends InferCurrentTypeRule<FunctionType, Specifics, T> {
/**
* In case of overloaded functions, these input arguments are used to determine the actual function
* by comparing the types of the given arguments with the expected types of the input parameters of the function.
*/
inputArguments: (languageNode: T) => Array<Specifics['LanguageType']>;
/**
* This property controls the builtin validation which checks, whether the types of the given arguments of the function call
* fit to the expected types of the parameters of the function.
* The function calls to validate are represented by this inference rule,
* i.e. function calls represented by other inference rules have their own property and are not influenced by the value of this inference rule.
* By default, the property is switched off (e.g. `validateArgumentsOfFunctionCalls: false`),
* but in most applications this validation is desired and should be switched on (e.g. with `validateArgumentsOfFunctionCalls: true`).
* This property does _not_ influence the type inference,
* this property determines only, whether this special validation is applied to the current function call.
*
* This property is specific for this function type.
* If this function type is not overloaded, this property switches this validation off and on for the calls of this function,
* i.e. creates validation issues for all calls with mismatching argument types.
*
* If this function type is overloaded, different values for this property for different overloaded functions might be specified:
* If the property is switched off for all overloads, no validation issues will be created.
* If the property is switched on for at least one overload, validation issues for will be shown for all calls (when none of the signatures match),
* since it is unclear, which of the overloads is the desired one!
* But the shown validation issue/message will not report about signatures for which this validation property is switched off.
* While different values for this property for different overloads are possible in theory with the defined behaviour,
* in practise this seems to be rarely useful.
*/
validateArgumentsOfFunctionCalls?: boolean | ((languageNode: T) => boolean);
}
/**
* Architecture of Inference rules:
* - flag for overload / checking parameter types => no, that is bad usability, e.g. operators use already overloaded functions!
* - overloaded functions are specific for the function kind => solve it inside the FunctionKind!
*
* How many inference rules?
* - The inference rule for calls of each function type with the same name are grouped together in order to provide better error messages, if none of the variants match.
* - Checking multiple functions within the same rule (e.g. only one inference rule for the function kind or one inference rule for each function name) does not work,
* since multiple different sets of parameters must be returned for overloaded functions!
* - multiple IR collectors: how to apply all the other rules?!
*
* How many validation rules?
* - For validation, it is enough that at least one of the function variants match!
* - But checking that is not possible with independent rules for each function variant.
* - Therefore, it must be a single validation for each function name (with all type variants).
* - In order to simplify (de)registering validation rules, only one validation rule for all functions is used here (with an internal loop over all function names).
*
* How to know the available (overloaded) functions?
* - search in all Types VS remember them in a Map; add VS remove function type
*/
export interface FunctionFactoryService<Specifics extends TypirSpecifics> {
create(typeDetails: FunctionTypeDetails<Specifics>): FunctionConfigurationChain<Specifics>;
get(typeDetails: FunctionTypeDetails<Specifics>): TypeReference<FunctionType, Specifics>;
calculateIdentifier(typeDetails: FunctionTypeDetails<Specifics>): string;
/** Creates a validation rule which checks, that the function types are unique. */
createUniqueFunctionValidation(options: RegistrationOptions): ValidationRule<Specifics>;
}
export interface FunctionConfigurationChain<Specifics extends TypirSpecifics> {
/** for function declarations => returns the funtion type (the whole signature including all names) */
inferenceRuleForDeclaration<T extends Specifics['LanguageType']>(rule: InferCurrentTypeRule<FunctionType, Specifics, T>): FunctionConfigurationChain<Specifics>;
/** for function calls => returns the return type of the function */
inferenceRuleForCalls<T extends Specifics['LanguageType']>(rule: InferFunctionCall<Specifics, T>): FunctionConfigurationChain<Specifics>;
finish(): TypeInitializer<FunctionType, Specifics>;
}
/**
* Represents signatures of executable code.
*
* Constraints of overloaded functions:
* - no duplicated variants!
* - The names of all paramaters don't matter for functions to be unique.
* - a variant is uniquely identified by: function name (if available), types of input parameters; options.identifierPrefix
* - For overloaded functions, it is not enough to have different output types or different parameter names!
*
* TODO possible Extensions:
* - multiple output parameters
* - create variants of this, e.g. functions, procedures, lambdas
* - (structural vs nominal typing? somehow realized by the three options above ...)
* - optional parameters
* - parameters which are used for output AND input
*/
export declare class FunctionKind<Specifics extends TypirSpecifics> implements Kind, FunctionFactoryService<Specifics> {
readonly $name: string;
readonly services: TypirServices<Specifics>;
readonly options: Readonly<FunctionKindOptions<Specifics>>;
readonly functions: AvailableFunctionsManager<Specifics>;
constructor(services: TypirServices<Specifics>, options?: Partial<FunctionKindOptions<Specifics>>);
protected collectOptions(options?: Partial<FunctionKindOptions<Specifics>>): FunctionKindOptions<Specifics>;
protected createFunctionManager(): AvailableFunctionsManager<Specifics>;
get(typeDetails: FunctionTypeDetails<Specifics>): TypeReference<FunctionType, Specifics>;
create(typeDetails: FunctionTypeDetails<Specifics>): FunctionConfigurationChain<Specifics>;
getOutputTypeForFunctionCalls(functionType: FunctionType): Type | undefined;
calculateIdentifier(typeDetails: FunctionTypeDetails<Specifics>): string;
getParameterRepresentation(parameter: NameTypePair): string;
enforceFunctionName(name: string | undefined, enforce: boolean): void;
hasFunctionName(name: string | undefined): name is string;
enforceParameterName(name: string | undefined, enforce: boolean): void;
hasParameterName(name: string | undefined): name is string;
createUniqueFunctionValidation(options: RegistrationOptions): ValidationRule<Specifics>;
}
export declare function isFunctionKind<Specifics extends TypirSpecifics>(kind: unknown): kind is FunctionKind<Specifics>;
export declare const NO_FUNCTION_NAME = "";
export declare const NO_PARAMETER_NAME = "";
//# sourceMappingURL=function-kind.d.ts.map