typir
Version:
General purpose type checking library
175 lines • 7.9 kB
JavaScript
/******************************************************************************
* 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 { NO_PARAMETER_NAME } from '../kinds/function/function-kind.js';
import { toArray } from '../utils/utils.js';
/**
* This implementation realizes operators as functions and creates types of kind 'function'.
* If Typir does not use the function kind so far, it will be automatically added.
* (Alternative implementation strategies for operators would be a dedicated kind for operators, which might extend the 'function' kind)
*
* Nevertheless, there are some differences between operators and functions:
* - Operators have no declaration.
* - It is not possible to have references to operators.
*
* The same operator (i.e. same operator name, e.g. "+" or "XOR") with different types for its operands will be realized as different function types,
* e.g. there are two functions for "+" for numbers and for strings.
*
* All operands are mandatory.
*/
export class DefaultOperatorFactory {
constructor(services) {
this.services = services;
}
createUnary(typeDetails) {
return new OperatorConfigurationUnaryChainImpl(this.services, typeDetails);
}
createBinary(typeDetails) {
return new OperatorConfigurationBinaryChainImpl(this.services, typeDetails);
}
createTernary(typeDetails) {
return new OperatorConfigurationTernaryChainImpl(this.services, typeDetails);
}
createGeneric(typeDetails) {
return new OperatorConfigurationGenericChainImpl(this.services, typeDetails);
}
}
class OperatorConfigurationUnaryChainImpl {
constructor(services, typeDetails) {
this.services = services;
this.typeDetails = Object.assign(Object.assign({}, typeDetails), { inferenceRules: [] });
}
inferenceRule(rule) {
this.typeDetails.inferenceRules.push(rule);
return this;
}
finish() {
const signatures = toSignatureArray(this.typeDetails);
const result = [];
for (const signature of signatures) {
const generic = new OperatorConfigurationGenericChainImpl(this.services, {
name: this.typeDetails.name,
outputType: signature.return,
inputParameter: [
{ name: 'operand', type: signature.operand },
],
});
// the same inference rule is used (and required) for all overloads, since multiple FunctionTypes are created!
this.typeDetails.inferenceRules.forEach(rule => generic.inferenceRule(rule));
result.push(generic.finish());
}
return result;
}
}
class OperatorConfigurationBinaryChainImpl {
constructor(services, typeDetails) {
this.services = services;
this.typeDetails = Object.assign(Object.assign({}, typeDetails), { inferenceRules: [] });
}
inferenceRule(rule) {
this.typeDetails.inferenceRules.push(rule);
return this;
}
finish() {
const signatures = toSignatureArray(this.typeDetails);
const result = [];
for (const signature of signatures) {
const generic = new OperatorConfigurationGenericChainImpl(this.services, {
name: this.typeDetails.name,
outputType: signature.return,
inputParameter: [
{ name: 'left', type: signature.left },
{ name: 'right', type: signature.right },
],
});
// the same inference rule is used (and required) for all overloads, since multiple FunctionTypes are created!
this.typeDetails.inferenceRules.forEach(rule => generic.inferenceRule(rule));
result.push(generic.finish());
}
return result;
}
}
class OperatorConfigurationTernaryChainImpl {
constructor(services, typeDetails) {
this.services = services;
this.typeDetails = Object.assign(Object.assign({}, typeDetails), { inferenceRules: [] });
}
inferenceRule(rule) {
this.typeDetails.inferenceRules.push(rule);
return this;
}
finish() {
const signatures = toSignatureArray(this.typeDetails);
const result = [];
for (const signature of signatures) {
const generic = new OperatorConfigurationGenericChainImpl(this.services, {
name: this.typeDetails.name,
outputType: signature.return,
inputParameter: [
{ name: 'first', type: signature.first },
{ name: 'second', type: signature.second },
{ name: 'third', type: signature.third },
],
});
// the same inference rule is used (and required) for all overloads, since multiple FunctionTypes are created!
this.typeDetails.inferenceRules.forEach(rule => generic.inferenceRule(rule));
result.push(generic.finish());
}
return result;
}
}
class OperatorConfigurationGenericChainImpl {
constructor(services, typeDetails) {
this.services = services;
this.typeDetails = Object.assign(Object.assign({}, typeDetails), { inferenceRules: [] });
}
inferenceRule(rule) {
this.typeDetails.inferenceRules.push(rule);
return this;
}
finish() {
// define/register the wanted operator as "special" function
const functionFactory = this.getFunctionFactory();
const operatorName = this.typeDetails.name;
// create the operator as type of kind 'function'
const newOperatorType = functionFactory.create({
functionName: operatorName,
outputParameter: { name: NO_PARAMETER_NAME, type: this.typeDetails.outputType },
inputParameters: this.typeDetails.inputParameter,
});
// infer the operator when the operator is called!
for (const inferenceRule of this.typeDetails.inferenceRules) {
newOperatorType.inferenceRuleForCalls({
languageKey: inferenceRule.languageKey,
filter: inferenceRule.filter ? ((languageNode) => inferenceRule.filter(languageNode, this.typeDetails.name)) : undefined,
matching: (languageNode) => inferenceRule.matching(languageNode, this.typeDetails.name),
inputArguments: (languageNode) => this.getInputArguments(inferenceRule, languageNode),
validation: toArray(inferenceRule.validation).map(validationRule => (functionCall, functionType, accept, typir) => validationRule(functionCall, operatorName, functionType, accept, typir)),
validateArgumentsOfFunctionCalls: inferenceRule.validateArgumentsOfCalls,
});
}
// operators have no declaration in the code => no inference rule for the operator declaration!
return newOperatorType.finish();
}
getInputArguments(inferenceRule, languageNode) {
return 'operands' in inferenceRule
? inferenceRule.operands(languageNode, this.typeDetails.name)
: [inferenceRule.operand(languageNode, this.typeDetails.name)];
}
getFunctionFactory() {
return this.services.factory.Functions;
}
}
function toSignatureArray(values) {
const result = toArray(values.signatures, { newArray: true }); // create a new array in order to prevent side-effects in the given array
if (values.signature) {
result.push(values.signature);
}
if (result.length <= 0) {
throw new Error('At least one signature must be given!');
}
return result;
}
//# sourceMappingURL=operator.js.map