From 1784d7af9cc144b7184337b73ce60b6ba1d31e3c Mon Sep 17 00:00:00 2001 From: Johannes Meier Date: Thu, 19 Dec 2024 13:00:24 +0100 Subject: [PATCH] renamed "domain element" to "language node" --- examples/lox/src/language/lox-linker.ts | 2 +- examples/lox/src/language/lox-scope.ts | 2 +- .../lox/src/language/lox-type-checking.ts | 60 ++++---- examples/ox/src/language/ox-type-checking.ts | 40 +++--- .../src/features/langium-caching.ts | 34 ++--- .../src/features/langium-printing.ts | 10 +- .../src/features/langium-type-creator.ts | 12 +- .../src/features/langium-validation.ts | 4 +- packages/typir-langium/src/typir-langium.ts | 4 +- packages/typir/src/graph/type-node.ts | 17 +-- .../typir/src/initialization/type-selector.ts | 4 +- .../typir/src/kinds/bottom/bottom-kind.ts | 6 +- .../src/kinds/class/class-initializer.ts | 30 ++-- packages/typir/src/kinds/class/class-kind.ts | 10 +- .../typir/src/kinds/class/class-validation.ts | 60 ++++---- .../typir/src/kinds/class/top-class-kind.ts | 6 +- .../kinds/function/function-initializer.ts | 32 ++--- .../typir/src/kinds/function/function-kind.ts | 28 ++-- .../src/kinds/function/function-validation.ts | 20 +-- .../src/kinds/primitive/primitive-kind.ts | 6 +- packages/typir/src/kinds/top/top-kind.ts | 6 +- packages/typir/src/services/caching.ts | 42 +++--- packages/typir/src/services/inference.ts | 128 +++++++++--------- packages/typir/src/services/operator.ts | 28 ++-- packages/typir/src/services/printing.ts | 10 +- packages/typir/src/services/validation.ts | 80 +++++------ packages/typir/src/typir.ts | 6 +- packages/typir/test/type-definitions.test.ts | 14 +- 28 files changed, 351 insertions(+), 350 deletions(-) diff --git a/examples/lox/src/language/lox-linker.ts b/examples/lox/src/language/lox-linker.ts index ff4727e..3555ca6 100644 --- a/examples/lox/src/language/lox-linker.ts +++ b/examples/lox/src/language/lox-linker.ts @@ -53,7 +53,7 @@ export class LoxLinker extends DefaultLinker { // the following approach does not work, since the container's cross-references are required for type inference, but they are not yet resolved // const type = this.typir.Inference.inferType(container); // if (isFunctionType(type)) { - // return type.associatedDomainElement; + // return type.associatedLanguageNode // } } return this.createLinkingError(refInfo); diff --git a/examples/lox/src/language/lox-scope.ts b/examples/lox/src/language/lox-scope.ts index 315b479..8ec23dd 100644 --- a/examples/lox/src/language/lox-scope.ts +++ b/examples/lox/src/language/lox-scope.ts @@ -41,7 +41,7 @@ export class LoxScopeProvider extends DefaultScopeProvider { // use Typir to identify the ClassType of the current expression (including variables, fields of nested classes, ...) const previousType = this.typir.Inference.inferType(previous); if (isClassType(previousType)) { - return this.scopeClassMembers(previousType.associatedDomainElement as Class); // the Class was associated with this ClassType during its creation + return this.scopeClassMembers(previousType.associatedLanguageNode as Class); // the Class was associated with this ClassType during its creation } return EMPTY_SCOPE; } diff --git a/examples/lox/src/language/lox-type-checking.ts b/examples/lox/src/language/lox-type-checking.ts index cabdc62..cb1bd50 100644 --- a/examples/lox/src/language/lox-type-checking.ts +++ b/examples/lox/src/language/lox-type-checking.ts @@ -86,8 +86,8 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator { // show a warning to the user, if something like "3 == false" is compared, since different types already indicate, that the IF condition will be evaluated to false validationRule: (node, _operatorName, _operatorType, typir) => typir.validation.Constraints.ensureNodeIsEquals(node.left, node.right, (actual, expected) => { message: `This comparison will always return '${node.operator === '==' ? 'false' : 'true'}' as '${node.left.$cstNode?.text}' and '${node.right.$cstNode?.text}' have the different types '${actual.name}' and '${expected.name}'.`, - domainElement: node, // inside the BinaryExpression ... - domainProperty: 'operator', // ... mark the '==' or '!=' token, i.e. the 'operator' property + languageNode: node, // inside the BinaryExpression ... + languageProperty: 'operator', // ... mark the '==' or '!=' token, i.e. the 'operator' property severity: 'warning' }), // (The use of "node.right" and "node.left" without casting is possible, since the type checks of the given 'inferenceRule' are reused for the 'validationRule'. // This approach saves the duplication of checks for inference and validation, but makes the validation rules depending on the inference rule.) @@ -98,7 +98,7 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator { // this validation will be checked for each call of this operator! validationRule: (node, _opName, _opType, typir) => typir.validation.Constraints.ensureNodeIsAssignable(node.right, node.left, (actual, expected) => { message: `The expression '${node.right.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.left.$cstNode?.text}' with type '${expected.name}'`, - domainProperty: 'value' }), + languageProperty: 'value' }), }); // unary operators @@ -106,10 +106,10 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator { this.typir.factory.Operators.createUnary({ name: '-', signature: { operand: typeNumber, return: typeNumber }, inferenceRule: unaryInferenceRule }); // additional inference rules for ... - this.typir.Inference.addInferenceRule((domainElement: unknown) => { + this.typir.Inference.addInferenceRule((languageNode: unknown) => { // ... member calls - if (isMemberCall(domainElement)) { - const ref = domainElement.element?.ref; + if (isMemberCall(languageNode)) { + const ref = languageNode.element?.ref; if (isClass(ref)) { return InferenceRuleNotApplicable; // not required anymore } else if (isFieldMember(ref)) { @@ -129,18 +129,18 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator { } } // ... variable declarations - if (isVariableDeclaration(domainElement)) { - if (domainElement.type) { - return domainElement.type; // the user declared this variable with a type - } else if (domainElement.value) { - return domainElement.value; // the user didn't declare a type for this variable => do type inference of the assigned value instead! + if (isVariableDeclaration(languageNode)) { + if (languageNode.type) { + return languageNode.type; // the user declared this variable with a type + } else if (languageNode.value) { + return languageNode.value; // the user didn't declare a type for this variable => do type inference of the assigned value instead! } else { return InferenceRuleNotApplicable; // this case is impossible, there is a validation in the Langium LOX validator for this case } } // ... parameters - if (isParameter(domainElement)) { - return domainElement.type; + if (isParameter(languageNode)) { + return languageNode.type; } return InferenceRuleNotApplicable; }); @@ -150,15 +150,15 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator { (node: unknown, typir: TypirServices) => { if (isIfStatement(node) || isWhileStatement(node) || isForStatement(node)) { return typir.validation.Constraints.ensureNodeIsAssignable(node.condition, typeBool, - () => { message: "Conditions need to be evaluated to 'boolean'.", domainProperty: 'condition' }); + () => { message: "Conditions need to be evaluated to 'boolean'.", languageProperty: 'condition' }); } if (isVariableDeclaration(node)) { return [ ...typir.validation.Constraints.ensureNodeHasNotType(node, typeVoid, - () => { message: "Variable can't be declared with a type 'void'.", domainProperty: 'type' }), + () => { message: "Variable can't be declared with a type 'void'.", languageProperty: 'type' }), ...typir.validation.Constraints.ensureNodeIsAssignable(node.value, node, (actual, expected) => { message: `The expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to '${node.name}' with type '${expected.name}'`, - domainProperty: 'value' }), + languageProperty: 'value' }), ]; } if (isReturnStatement(node)) { @@ -167,7 +167,7 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator { // the return value must fit to the return type of the function / method return typir.validation.Constraints.ensureNodeIsAssignable(node.value, callableDeclaration.returnType, (actual, expected) => { message: `The expression '${node.value!.$cstNode?.text}' of type '${actual.name}' is not usable as return value for the function '${callableDeclaration.name}' with return type '${expected.name}'.`, - domainProperty: 'value' }); + languageProperty: 'value' }); } } return []; @@ -216,22 +216,22 @@ export class LoxTypeCreator extends AbstractLangiumTypeCreator { .filter(isMethodMember) // only Methods, no Fields .map(member => createFunctionDetails(member)), // same logic as for functions, since the LOX grammar defines them very similar // inference rule for declaration - inferenceRuleForDeclaration: (domainElement: unknown) => domainElement === node, + inferenceRuleForDeclaration: (languageNode: unknown) => languageNode === node, // inference rule for constructor calls (i.e. class literals) conforming to the current class inferenceRuleForConstructor: { // > filter: isMemberCall, - matching: (domainElement: MemberCall) => isClass(domainElement.element?.ref) && domainElement.element!.ref.name === className && domainElement.explicitOperationCall, - inputValuesForFields: (_domainElement: MemberCall) => new Map(), // values for fields don't matter for nominal typing + matching: (languageNode: MemberCall) => isClass(languageNode.element?.ref) && languageNode.element!.ref.name === className && languageNode.explicitOperationCall, + inputValuesForFields: (_languageNode: MemberCall) => new Map(), // values for fields don't matter for nominal typing }, inferenceRuleForReference: { // > filter: isTypeReference, - matching: (domainElement: TypeReference) => isClass(domainElement.reference?.ref) && domainElement.reference!.ref.name === className, - inputValuesForFields: (_domainElement: TypeReference) => new Map(), // values for fields don't matter for nominal typing + matching: (languageNode: TypeReference) => isClass(languageNode.reference?.ref) && languageNode.reference!.ref.name === className, + inputValuesForFields: (_languageNode: TypeReference) => new Map(), // values for fields don't matter for nominal typing }, // inference rule for accessing fields - inferenceRuleForFieldAccess: (domainElement: unknown) => isMemberCall(domainElement) && isFieldMember(domainElement.element?.ref) && domainElement.element!.ref.$container === node && !domainElement.explicitOperationCall - ? domainElement.element!.ref.name : InferenceRuleNotApplicable, - associatedDomainElement: node, + inferenceRuleForFieldAccess: (languageNode: unknown) => isMemberCall(languageNode) && isFieldMember(languageNode.element?.ref) && languageNode.element!.ref.$container === node && !languageNode.explicitOperationCall + ? languageNode.element!.ref.name : InferenceRuleNotApplicable, + associatedLanguageNode: node, }); // explicitly declare, that 'nil' can be assigned to any Class variable @@ -251,18 +251,18 @@ function createFunctionDetails(node: FunctionDeclaration | MethodMember): Create outputParameter: { name: NO_PARAMETER_NAME, type: node.returnType }, inputParameters: node.parameters.map(p => ({ name: p.name, type: p.type })), // inference rule for function declaration: - inferenceRuleForDeclaration: (domainElement: unknown) => domainElement === node, // only the current function/method declaration matches! + inferenceRuleForDeclaration: (languageNode: unknown) => languageNode === node, // only the current function/method declaration matches! /** inference rule for funtion/method calls: * - inferring of overloaded functions works only, if the actual arguments have the expected types! * - (inferring calls to non-overloaded functions works independently from the types of the given parameters) * - additionally, validations for the assigned values to the expected parameter( type)s are derived */ inferenceRuleForCalls: { filter: isMemberCall, - matching: (domainElement: MemberCall) => (isFunctionDeclaration(domainElement.element?.ref) || isMethodMember(domainElement.element?.ref)) - && domainElement.explicitOperationCall && domainElement.element!.ref.name === callableName, - inputArguments: (domainElement: MemberCall) => domainElement.arguments + matching: (languageNode: MemberCall) => (isFunctionDeclaration(languageNode.element?.ref) || isMethodMember(languageNode.element?.ref)) + && languageNode.explicitOperationCall && languageNode.element!.ref.name === callableName, + inputArguments: (languageNode: MemberCall) => languageNode.arguments }, - associatedDomainElement: node, + associatedLanguageNode: node, }; } diff --git a/examples/ox/src/language/ox-type-checking.ts b/examples/ox/src/language/ox-type-checking.ts index 95ec267..0c9e440 100644 --- a/examples/ox/src/language/ox-type-checking.ts +++ b/examples/ox/src/language/ox-type-checking.ts @@ -84,10 +84,10 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator { */ // additional inference rules ... - this.typir.Inference.addInferenceRule((domainElement: unknown) => { + this.typir.Inference.addInferenceRule((languageNode: unknown) => { // ... for member calls (which are used in expressions) - if (isMemberCall(domainElement)) { - const ref = domainElement.element.ref; + if (isMemberCall(languageNode)) { + const ref = languageNode.element.ref; if (isVariableDeclaration(ref)) { // use variables inside expressions! return ref; @@ -104,13 +104,13 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator { } } // ... variable declarations - if (isVariableDeclaration(domainElement)) { - if (domainElement.type) { + if (isVariableDeclaration(languageNode)) { + if (languageNode.type) { // the user declared this variable with a type - return domainElement.type; - } else if (domainElement.value) { + return languageNode.type; + } else if (languageNode.value) { // the didn't declared a type for this variable => do type inference of the assigned value instead! - return domainElement.value; + return languageNode.value; } else { return InferenceRuleNotApplicable; // this case is impossible, there is a validation in the Langium LOX validator for this case } @@ -123,21 +123,21 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator { (node: unknown, typir: TypirServices) => { if (isIfStatement(node) || isWhileStatement(node) || isForStatement(node)) { return typir.validation.Constraints.ensureNodeIsAssignable(node.condition, typeBool, - () => { message: "Conditions need to be evaluated to 'boolean'.", domainProperty: 'condition' }); + () => { message: "Conditions need to be evaluated to 'boolean'.", languageProperty: 'condition' }); } if (isVariableDeclaration(node)) { return [ ...typir.validation.Constraints.ensureNodeHasNotType(node, typeVoid, - () => { message: "Variables can't be declared with the type 'void'.", domainProperty: 'type' }), + () => { message: "Variables can't be declared with the type 'void'.", languageProperty: 'type' }), ...typir.validation.Constraints.ensureNodeIsAssignable(node.value, node, - (actual, expected) => { message: `The initialization expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.name}' with type '${expected.name}'.`, domainProperty: 'value' }) + (actual, expected) => { message: `The initialization expression '${node.value?.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.name}' with type '${expected.name}'.`, languageProperty: 'value' }) ]; } if (isAssignmentStatement(node) && node.varRef.ref) { return typir.validation.Constraints.ensureNodeIsAssignable(node.value, node.varRef.ref, (actual, expected) => { message: `The expression '${node.value.$cstNode?.text}' of type '${actual.name}' is not assignable to the variable '${node.varRef.ref!.name}' with type '${expected.name}'.`, - domainProperty: 'value', + languageProperty: 'value', }); } if (isReturnStatement(node)) { @@ -145,7 +145,7 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator { if (functionDeclaration && functionDeclaration.returnType.primitive !== 'void' && node.value) { // the return value must fit to the return type of the function return typir.validation.Constraints.ensureNodeIsAssignable(node.value, functionDeclaration.returnType, - () => { message: `The expression '${node.value!.$cstNode?.text}' is not usable as return value for the function '${functionDeclaration.name}'.`, domainProperty: 'value' }); + () => { message: `The expression '${node.value!.$cstNode?.text}' is not usable as return value for the function '${functionDeclaration.name}'.`, languageProperty: 'value' }); } } return []; @@ -156,19 +156,19 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator { this.typir.validation.Collector.addValidationRuleWithBeforeAndAfter(new UniqueFunctionValidation(this.typir, isFunctionDeclaration)); } - onNewAstNode(domainElement: AstNode): void { + onNewAstNode(languageNode: AstNode): void { // define function types // they have to be updated after each change of the Langium document, since they are derived from the user-defined FunctionDeclarations! - if (isFunctionDeclaration(domainElement)) { - const functionName = domainElement.name; + if (isFunctionDeclaration(languageNode)) { + const functionName = languageNode.name; // define function type this.typir.factory.Functions.create({ functionName, // note that the following two lines internally use type inference here in order to map language types to Typir types - outputParameter: { name: NO_PARAMETER_NAME, type: domainElement.returnType }, - inputParameters: domainElement.parameters.map(p => ({ name: p.name, type: p.type })), + outputParameter: { name: NO_PARAMETER_NAME, type: languageNode.returnType }, + inputParameters: languageNode.parameters.map(p => ({ name: p.name, type: p.type })), // inference rule for function declaration: - inferenceRuleForDeclaration: (node: unknown) => node === domainElement, // only the current function declaration matches! + inferenceRuleForDeclaration: (node: unknown) => node === languageNode, // only the current function declaration matches! /** inference rule for funtion calls: * - inferring of overloaded functions works only, if the actual arguments have the expected types! * - (inferring calls to non-overloaded functions works independently from the types of the given parameters) @@ -179,7 +179,7 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator { inputArguments: (call: MemberCall) => call.arguments // they are needed to validate, that the given arguments are assignable to the parameters // Note that OX does not support overloaded function declarations for simplicity: Look into LOX to see how to handle overloaded functions and methods! }, - associatedDomainElement: domainElement, + associatedLanguageNode: languageNode, }); } } diff --git a/packages/typir-langium/src/features/langium-caching.ts b/packages/typir-langium/src/features/langium-caching.ts index 17207bf..da415b0 100644 --- a/packages/typir-langium/src/features/langium-caching.ts +++ b/packages/typir-langium/src/features/langium-caching.ts @@ -5,45 +5,45 @@ ******************************************************************************/ import { AstNode, DocumentCache, DocumentState, LangiumSharedCoreServices } from 'langium'; -import { CachePending, DomainElementInferenceCaching, Type } from 'typir'; +import { CachePending, LanguageNodeInferenceCaching, Type } from 'typir'; import { getDocumentKey } from '../utils/typir-langium-utils.js'; // cache AstNodes -export class LangiumDomainElementInferenceCaching implements DomainElementInferenceCaching { +export class LangiumLanguageNodeInferenceCaching implements LanguageNodeInferenceCaching { protected readonly cache: DocumentCache; // removes cached AstNodes, if their underlying LangiumDocuments are invalidated constructor(langiumServices: LangiumSharedCoreServices) { this.cache = new DocumentCache(langiumServices, DocumentState.IndexedReferences); } - cacheSet(domainElement: AstNode, type: Type): void { - this.pendingClear(domainElement); - this.cache.set(getDocumentKey(domainElement), domainElement, type); + cacheSet(languageNode: AstNode, type: Type): void { + this.pendingClear(languageNode); + this.cache.set(getDocumentKey(languageNode), languageNode, type); } - cacheGet(domainElement: AstNode): Type | undefined { - if (this.pendingGet(domainElement)) { + cacheGet(languageNode: AstNode): Type | undefined { + if (this.pendingGet(languageNode)) { return undefined; } else { - return this.cache.get(getDocumentKey(domainElement), domainElement) as (Type | undefined); + return this.cache.get(getDocumentKey(languageNode), languageNode) as (Type | undefined); } } - pendingSet(domainElement: AstNode): void { - this.cache.set(getDocumentKey(domainElement), domainElement, CachePending); + pendingSet(languageNode: AstNode): void { + this.cache.set(getDocumentKey(languageNode), languageNode, CachePending); } - pendingClear(domainElement: AstNode): void { - const key = getDocumentKey(domainElement); - if (this.cache.get(key, domainElement) !== CachePending) { + pendingClear(languageNode: AstNode): void { + const key = getDocumentKey(languageNode); + if (this.cache.get(key, languageNode) !== CachePending) { // do nothing } else { - this.cache.delete(key, domainElement); + this.cache.delete(key, languageNode); } } - pendingGet(domainElement: AstNode): boolean { - const key = getDocumentKey(domainElement); - return this.cache.has(key, domainElement) && this.cache.get(key, domainElement) === CachePending; + pendingGet(languageNode: AstNode): boolean { + const key = getDocumentKey(languageNode); + return this.cache.has(key, languageNode) && this.cache.get(key, languageNode) === CachePending; } } diff --git a/packages/typir-langium/src/features/langium-printing.ts b/packages/typir-langium/src/features/langium-printing.ts index 42153f6..38324a5 100644 --- a/packages/typir-langium/src/features/langium-printing.ts +++ b/packages/typir-langium/src/features/langium-printing.ts @@ -9,12 +9,12 @@ import { DefaultTypeConflictPrinter } from 'typir'; export class LangiumProblemPrinter extends DefaultTypeConflictPrinter { - /** When printing a domain element, i.e. an AstNode, print the text of the corresponding CstNode. */ - override printDomainElement(domainElement: unknown, sentenceBegin?: boolean | undefined): string { - if (isAstNode(domainElement)) { - return `${sentenceBegin ? 'T' : 't'}he AstNode '${domainElement.$cstNode?.text}'`; + /** When printing a language node, i.e. an AstNode, print the text of the corresponding CstNode. */ + override printLanguageNode(languageNode: unknown, sentenceBegin?: boolean | undefined): string { + if (isAstNode(languageNode)) { + return `${sentenceBegin ? 'T' : 't'}he AstNode '${languageNode.$cstNode?.text}'`; } - return super.printDomainElement(domainElement, sentenceBegin); + return super.printLanguageNode(languageNode, sentenceBegin); } } diff --git a/packages/typir-langium/src/features/langium-type-creator.ts b/packages/typir-langium/src/features/langium-type-creator.ts index 7c95b19..1ab811c 100644 --- a/packages/typir-langium/src/features/langium-type-creator.ts +++ b/packages/typir-langium/src/features/langium-type-creator.ts @@ -21,17 +21,17 @@ export interface LangiumTypeCreator { /** * For the initialization of the type system, e.g. to register primitive types and operators, inference rules and validation rules, - * which are constant and don't depend on the actual domain elements. - * This method will be executed once before the first added/updated/removed domain element. + * which are constant and don't depend on the actual language nodes. + * This method will be executed once before the first added/updated/removed language node. */ onInitialize(): void; /** * React on updates of the AST in order to add/remove corresponding types from the type system, * e.g. for user-defined functions to create corresponding function types in the type graph. - * @param domainElement an AstNode of the current AST + * @param languageNode an AstNode of the current AST */ - onNewAstNode(domainElement: AstNode): void; + onNewAstNode(languageNode: AstNode): void; } export abstract class AbstractLangiumTypeCreator implements LangiumTypeCreator, TypeGraphListener { @@ -68,7 +68,7 @@ export abstract class AbstractLangiumTypeCreator implements LangiumTypeCreator, abstract onInitialize(): void; - abstract onNewAstNode(domainElement: AstNode): void; + abstract onNewAstNode(languageNode: AstNode): void; /** * Starts the initialization. @@ -141,7 +141,7 @@ export class PlaceholderLangiumTypeCreator extends AbstractLangiumTypeCreator { override onInitialize(): void { throw new Error('This method needs to be implemented! Extend the AbstractLangiumTypeCreator and register it in the Typir module: TypeCreator: (typirServices) => new MyLangiumTypeCreator(typirServices, langiumServices)'); } - override onNewAstNode(_domainElement: AstNode): void { + override onNewAstNode(_languageNode: AstNode): void { throw new Error('This method needs to be implemented! Extend the AbstractLangiumTypeCreator and register it in the Typir module: TypeCreator: (typirServices) => new MyLangiumTypeCreator(typirServices, langiumServices)'); } } diff --git a/packages/typir-langium/src/features/langium-validation.ts b/packages/typir-langium/src/features/langium-validation.ts index df70e09..9b56c84 100644 --- a/packages/typir-langium/src/features/langium-validation.ts +++ b/packages/typir-langium/src/features/langium-validation.ts @@ -33,7 +33,7 @@ export function registerTypirValidationChecks(langiumServices: LangiumDefaultCor * Improved Validation API for Langium: * - const ref: (kind: unknown) => kind is FunctionKind = isFunctionKind; // use this signature for Langium? * - register validations for AST node $types (similar as Langium does it) => this is much more performant -* - [{ selector: isVariableDeclaration, result: domainElement => domainElement.type }, {}] Array> +* - [{ selector: isVariableDeclaration, result: languageNode => languageNode.type }, {}] Array> * - discriminator rule: $type '$VariableDeclaration' + record / "Sprungtabelle" for the Langium-binding (or both in core)? for improved performance (?) * - alternativ discriminator rule: unknown => string; AstNode => node.$type; Vorsicht mit Sub-Typen (Vollständigkeit+Updates, no abstract types)! * Apply the same ideas for InferenceRules as well! @@ -86,7 +86,7 @@ export class DefaultLangiumTypirValidator implements LangiumTypirValidator { // print all found problems for the given AST node for (const problem of problems) { const message = this.services.Printer.printValidationProblem(problem); - accept(problem.severity, message, { node, property: problem.domainProperty, index: problem.domainIndex }); + accept(problem.severity, message, { node, property: problem.languageProperty, index: problem.languageIndex }); } } } diff --git a/packages/typir-langium/src/typir-langium.ts b/packages/typir-langium/src/typir-langium.ts index 7bd79a9..ef8bdd4 100644 --- a/packages/typir-langium/src/typir-langium.ts +++ b/packages/typir-langium/src/typir-langium.ts @@ -6,7 +6,7 @@ import { LangiumDefaultCoreServices, LangiumSharedCoreServices } from 'langium'; import { DeepPartial, DefaultTypirServiceModule, Module, PartialTypirServices, TypirServices } from 'typir'; -import { LangiumDomainElementInferenceCaching } from './features/langium-caching.js'; +import { LangiumLanguageNodeInferenceCaching } from './features/langium-caching.js'; import { LangiumProblemPrinter } from './features/langium-printing.js'; import { LangiumTypeCreator, PlaceholderLangiumTypeCreator } from './features/langium-type-creator.js'; import { DefaultLangiumTypirValidator, LangiumTypirValidator, registerTypirValidationChecks } from './features/langium-validation.js'; @@ -29,7 +29,7 @@ export function createLangiumSpecificTypirServicesModule(langiumServices: Langiu const LangiumSpecifics: Module = { Printer: () => new LangiumProblemPrinter(), caching: { - DomainElementInference: () => new LangiumDomainElementInferenceCaching(langiumServices), + LanguageNodeInference: () => new LangiumLanguageNodeInferenceCaching(langiumServices), }, }; return Module.merge( diff --git a/packages/typir/src/graph/type-node.ts b/packages/typir/src/graph/type-node.ts index 1e0e13f..d7b0757 100644 --- a/packages/typir/src/graph/type-node.ts +++ b/packages/typir/src/graph/type-node.ts @@ -38,9 +38,9 @@ export interface PreconditionsForInitializationState { * i.e. it is used for specifying details of all types to create. */ export interface TypeDetails { - /** An element from the domain might be associated with the new type to create, + /** A node from the language might be associated with the new type to create, * e.g. the declaration node in the AST (e.g. a FunctionDeclarationNode is associated with the corresponding FunctionType). */ - associatedDomainElement?: unknown; + associatedLanguageNode?: unknown; } /** @@ -64,16 +64,17 @@ export abstract class Type { protected readonly edgesOutgoing: Map = new Map(); /** - * The current type might be associated with an element from the domain, e.g. the corresponding declaration node in the AST. - * This domain element is _not_ used for managing the lifecycles of this type, + * A node from the language might be associated with the current type, + * e.g. the declaration node in the AST (e.g. a FunctionDeclarationNode is associated with the corresponding FunctionType) + * This language node is _not_ used for managing the lifecycles of this type, * since it should be usable for any domain-specific purpose. * Therefore, the use and update of this feature is under the responsibility of the user of Typir. */ - readonly associatedDomainElement: unknown | undefined; + readonly associatedLanguageNode: unknown | undefined; constructor(identifier: string | undefined, typeDetails: TypeDetails) { this.identifier = identifier; - this.associatedDomainElement = typeDetails.associatedDomainElement; + this.associatedLanguageNode = typeDetails.associatedLanguageNode; } @@ -88,7 +89,7 @@ export abstract class Type { } /** - * Returns a string value containing a short representation of the type to be shown to users of the type-checked elements. + * Returns a string value containing a short representation of the type to be shown to users of the type-checked language nodes. * This value don't need to be unique for all types. * This name should be quite short. * Services should not call this function directly, but typir.printer.printTypeName(...) instead. @@ -97,7 +98,7 @@ export abstract class Type { abstract getName(): string; /** - * Calculates a string value which might be shown to users of the type-checked elements. + * Calculates a string value which might be shown to users of the type-checked language nodes. * This value don't need to be unique for all types. * This representation might be longer and show lots of details of the type. * Services should not call this function directly, but typir.printer.printTypeUserRepresentation(...) instead. diff --git a/packages/typir/src/initialization/type-selector.ts b/packages/typir/src/initialization/type-selector.ts index 18b95a1..8100afd 100644 --- a/packages/typir/src/initialization/type-selector.ts +++ b/packages/typir/src/initialization/type-selector.ts @@ -15,7 +15,7 @@ export type BasicTypeSelector = | string // identifier of the type (in the type graph/map) | TypeInitializer // delayed creation of types | TypeReference // reference to a (maybe delayed) type - | unknown // domain node to infer the final type from + | unknown // language node to infer the final type from ; /** @@ -98,7 +98,7 @@ export class DefaultTypeResolver implements TypeResolvingService { if (isType(result)) { return result as T; } else { - throw new Error(`For '${this.services.Printer.printDomainElement(selector, false)}' as TypeSelector, no type can be inferred.`); + throw new Error(`For '${this.services.Printer.printLanguageNode(selector, false)}' as TypeSelector, no type can be inferred.`); } } } diff --git a/packages/typir/src/kinds/bottom/bottom-kind.ts b/packages/typir/src/kinds/bottom/bottom-kind.ts index d97a103..522de5c 100644 --- a/packages/typir/src/kinds/bottom/bottom-kind.ts +++ b/packages/typir/src/kinds/bottom/bottom-kind.ts @@ -20,7 +20,7 @@ export interface BottomKindOptions { name: string; } -export type InferBottomType = (domainElement: unknown) => boolean; +export type InferBottomType = (languageNode: unknown) => boolean; export const BottomKindName = 'BottomKind'; @@ -76,9 +76,9 @@ export class BottomKind implements Kind, BottomFactoryService { protected registerInferenceRules(typeDetails: BottomTypeDetails, bottomType: BottomType) { const rules = toArray(typeDetails.inferenceRules); if (rules.length >= 1) { - this.services.Inference.addInferenceRule((domainElement, _typir) => { + this.services.Inference.addInferenceRule((languageNode, _typir) => { for (const inferenceRule of rules) { - if (inferenceRule(domainElement)) { + if (inferenceRule(languageNode)) { return bottomType; } } diff --git a/packages/typir/src/kinds/class/class-initializer.ts b/packages/typir/src/kinds/class/class-initializer.ts index 68f108a..413ccf6 100644 --- a/packages/typir/src/kinds/class/class-initializer.ts +++ b/packages/typir/src/kinds/class/class-initializer.ts @@ -100,14 +100,14 @@ export class ClassTypeInitializer exten const result: TypeInferenceRule[] = []; if (typeDetails.inferenceRuleForDeclaration) { result.push({ - inferTypeWithoutChildren(domainElement, _typir) { - if (typeDetails.inferenceRuleForDeclaration!(domainElement)) { + inferTypeWithoutChildren(languageNode, _typir) { + if (typeDetails.inferenceRuleForDeclaration!(languageNode)) { return classType; } else { return InferenceRuleNotApplicable; } }, - inferTypeWithChildrensTypes(_domainElement, _childrenTypes, _typir) { + inferTypeWithChildrensTypes(_languageNode, _childrenTypes, _typir) { // TODO check values for fields for structual typing! return classType; }, @@ -120,8 +120,8 @@ export class ClassTypeInitializer exten result.push(this.createInferenceRuleForLiteral(typeDetails.inferenceRuleForReference, classType)); } if (typeDetails.inferenceRuleForFieldAccess) { - result.push((domainElement, _typir) => { - const result = typeDetails.inferenceRuleForFieldAccess!(domainElement); + result.push((languageNode, _typir) => { + const result = typeDetails.inferenceRuleForFieldAccess!(languageNode); if (result === InferenceRuleNotApplicable) { return InferenceRuleNotApplicable; } else if (typeof result === 'string') { @@ -132,14 +132,14 @@ export class ClassTypeInitializer exten } return { $problem: InferenceProblem, - domainElement, + languageNode: languageNode, inferenceCandidate: classType, location: `unknown field '${result}'`, // rule: this, // this does not work with functions ... subProblems: [], }; } else { - return result; // do the type inference for this element instead + return result; // do the type inference for this language node instead } }); } @@ -150,12 +150,12 @@ export class ClassTypeInitializer exten const mapListConverter = new MapListConverter(); const kind = this.kind; return { - inferTypeWithoutChildren(domainElement, _typir) { - const result = rule.filter(domainElement); + inferTypeWithoutChildren(languageNode, _typir) { + const result = rule.filter(languageNode); if (result) { - const matching = rule.matching(domainElement); + const matching = rule.matching(languageNode); if (matching) { - const inputArguments = rule.inputValuesForFields(domainElement); + const inputArguments = rule.inputValuesForFields(languageNode); if (inputArguments.size >= 1) { return mapListConverter.toList(inputArguments); } else { @@ -163,15 +163,15 @@ export class ClassTypeInitializer exten return classType; // this case occurs only, if the current class has no fields (including fields of super types) or is nominally typed } } else { - // the domain element is slightly different + // the language node is slightly different } } else { - // the domain element has a completely different purpose + // the language node has a completely different purpose } // does not match at all return InferenceRuleNotApplicable; }, - inferTypeWithChildrensTypes(domainElement, childrenTypes, typir) { + inferTypeWithChildrensTypes(languageNode, childrenTypes, typir) { const allExpectedFields = classType.getFields(true); // this class type might match, to be sure, resolve the types of the values for the parameters and continue to step 2 const checkedFieldsProblems = checkNameTypesMap( @@ -183,7 +183,7 @@ export class ClassTypeInitializer exten // (only) for overloaded functions, the types of the parameters need to be inferred in order to determine an exact match return { $problem: InferenceProblem, - domainElement, + languageNode: languageNode, inferenceCandidate: classType, location: 'values for fields', rule: this, diff --git a/packages/typir/src/kinds/class/class-kind.ts b/packages/typir/src/kinds/class/class-kind.ts index 53abdfc..daf9f4f 100644 --- a/packages/typir/src/kinds/class/class-kind.ts +++ b/packages/typir/src/kinds/class/class-kind.ts @@ -42,10 +42,10 @@ export interface ClassTypeDetails extends TypeDetails { methods: Array>, // all details of functions can be configured for methods as well, in particular, inference rules for function/method calls! } export interface CreateClassTypeDetails extends ClassTypeDetails { // TODO the generics look very bad! - inferenceRuleForDeclaration?: (domainElement: unknown) => boolean, + inferenceRuleForDeclaration?: (languageNode: unknown) => boolean, inferenceRuleForConstructor?: InferClassLiteral, // InferClassLiteral | Array>, does not work: https://stackoverflow.com/questions/65129070/defining-an-array-of-differing-generic-types-in-typescript inferenceRuleForReference?: InferClassLiteral, - inferenceRuleForFieldAccess?: (domainElement: unknown) => string | unknown | InferenceRuleNotApplicable, // name of the field | element to infer the type of the field (e.g. the type) | rule not applicable + inferenceRuleForFieldAccess?: (languageNode: unknown) => string | unknown | InferenceRuleNotApplicable, // name of the field | language node to infer the type of the field (e.g. the type) | rule not applicable // inference rules for Method calls are part of "methods: CreateFunctionTypeDetails[]" above! } @@ -54,9 +54,9 @@ export interface CreateClassTypeDetails * different values might be specified, e.g. 'inputValuesForFields' could be empty for nominal classes. */ export type InferClassLiteral = { - filter: (domainElement: unknown) => domainElement is T; - matching: (domainElement: T) => boolean; - inputValuesForFields: (domainElement: T) => Map; // simple field name (including inherited fields) => value for this field! + filter: (languageNode: unknown) => languageNode is T; + matching: (languageNode: T) => boolean; + inputValuesForFields: (languageNode: T) => Map; // simple field name (including inherited fields) => value for this field! }; diff --git a/packages/typir/src/kinds/class/class-validation.ts b/packages/typir/src/kinds/class/class-validation.ts index 829fd96..cc54ada 100644 --- a/packages/typir/src/kinds/class/class-validation.ts +++ b/packages/typir/src/kinds/class/class-validation.ts @@ -17,30 +17,30 @@ export class UniqueClassValidation implements ValidationRuleWithBeforeAfter { protected readonly foundDeclarations: Map = new Map(); protected readonly services: TypirServices; - protected readonly isRelevant: (domainElement: unknown) => boolean; // using this check improves performance a lot + protected readonly isRelevant: (languageNode: unknown) => boolean; // using this check improves performance a lot - constructor(services: TypirServices, isRelevant: (domainElement: unknown) => boolean) { + constructor(services: TypirServices, isRelevant: (languageNode: unknown) => boolean) { this.services = services; this.isRelevant = isRelevant; } - beforeValidation(_domainRoot: unknown, _typir: TypirServices): ValidationProblem[] { + beforeValidation(_languageRoot: unknown, _typir: TypirServices): ValidationProblem[] { this.foundDeclarations.clear(); return []; } - validation(domainElement: unknown, _typir: TypirServices): ValidationProblem[] { - if (this.isRelevant(domainElement)) { // improves performance, since type inference need to be done only for relevant elements - const type = this.services.Inference.inferType(domainElement); + validation(languageNode: unknown, _typir: TypirServices): ValidationProblem[] { + if (this.isRelevant(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes + const type = this.services.Inference.inferType(languageNode); if (isClassType(type)) { - // register domain elements which have ClassTypes with a key for their uniques + // register language nodes which have ClassTypes with a key for their uniques const key = this.calculateClassKey(type); let entries = this.foundDeclarations.get(key); if (!entries) { entries = []; this.foundDeclarations.set(key, entries); } - entries.push(domainElement); + entries.push(languageNode); } } return []; @@ -58,14 +58,14 @@ export class UniqueClassValidation implements ValidationRuleWithBeforeAfter { return `${clas.className}`; } - afterValidation(_domainRoot: unknown, _typir: TypirServices): ValidationProblem[] { + afterValidation(_languageRoot: unknown, _typir: TypirServices): ValidationProblem[] { const result: ValidationProblem[] = []; for (const [key, classes] of this.foundDeclarations.entries()) { if (classes.length >= 2) { for (const clas of classes) { result.push({ $problem: ValidationProblem, - domainElement: clas, + languageNode: clas, severity: 'error', message: `Declared classes need to be unique (${key}).`, }); @@ -84,7 +84,7 @@ export class UniqueClassValidation implements ValidationRuleWithBeforeAfter { } interface UniqueMethodValidationEntry { - domainElement: unknown; + languageNode: unknown; classType: ClassType; } @@ -95,15 +95,15 @@ export class UniqueMethodValidation implements ValidationRuleWithBeforeAfter protected readonly foundDeclarations: Map = new Map(); protected readonly services: TypirServices; - /** Determines domain elements which represent declared methods, improves performance a lot. */ - protected readonly isMethodDeclaration: (domainElement: unknown) => domainElement is T; - /** Determines the corresponding domain element of the class declaration, so that Typir can infer its ClassType */ - protected readonly getClassOfMethod: (domainElement: T, methodType: FunctionType) => unknown; + /** Determines language nodes which represent declared methods, improves performance a lot. */ + protected readonly isMethodDeclaration: (languageNode: unknown) => languageNode is T; + /** Determines the corresponding language node of the class declaration, so that Typir can infer its ClassType */ + protected readonly getClassOfMethod: (languageNode: T, methodType: FunctionType) => unknown; protected readonly uniqueClassValidator: UniqueClassValidation | undefined; constructor(services: TypirServices, - isMethodDeclaration: (domainElement: unknown) => domainElement is T, - getClassOfMethod: (domainElement: T, methodType: FunctionType) => unknown, + isMethodDeclaration: (languageNode: unknown) => languageNode is T, + getClassOfMethod: (languageNode: T, methodType: FunctionType) => unknown, uniqueClassValidator?: UniqueClassValidation, ) { this.services = services; @@ -112,16 +112,16 @@ export class UniqueMethodValidation implements ValidationRuleWithBeforeAfter this.uniqueClassValidator = uniqueClassValidator; } - beforeValidation(_domainRoot: unknown, _typir: TypirServices): ValidationProblem[] { + beforeValidation(_languageRoot: unknown, _typir: TypirServices): ValidationProblem[] { this.foundDeclarations.clear(); return []; } - validation(domainElement: unknown, _typir: TypirServices): ValidationProblem[] { - if (this.isMethodDeclaration(domainElement)) { // improves performance, since type inference need to be done only for relevant elements - const methodType = this.services.Inference.inferType(domainElement); + validation(languageNode: unknown, _typir: TypirServices): ValidationProblem[] { + if (this.isMethodDeclaration(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes + const methodType = this.services.Inference.inferType(languageNode); if (isFunctionType(methodType)) { - const classDeclaration = this.getClassOfMethod(domainElement, methodType); + const classDeclaration = this.getClassOfMethod(languageNode, methodType); const classType = this.services.Inference.inferType(classDeclaration); if (isClassType(classType)) { const key = this.calculateMethodKey(classType, methodType); @@ -131,7 +131,7 @@ export class UniqueMethodValidation implements ValidationRuleWithBeforeAfter this.foundDeclarations.set(key, entries); } entries.push({ - domainElement, + languageNode: languageNode, classType, }); } @@ -153,7 +153,7 @@ export class UniqueMethodValidation implements ValidationRuleWithBeforeAfter return `${clas.getIdentifier()}.${func.functionName}(${func.getInputs().map(param => param.type.getIdentifier())})`; } - afterValidation(_domainRoot: unknown, _typir: TypirServices): ValidationProblem[] { + afterValidation(_LanguageRoot: unknown, _typir: TypirServices): ValidationProblem[] { const result: ValidationProblem[] = []; for (const [key, methods] of this.foundDeclarations.entries()) { if (methods.length >= 2) { @@ -163,7 +163,7 @@ export class UniqueMethodValidation implements ValidationRuleWithBeforeAfter } else { result.push({ $problem: ValidationProblem, - domainElement: method.domainElement, + languageNode: method.languageNode, severity: 'error', message: `Declared methods need to be unique (${key}).`, }); @@ -184,17 +184,17 @@ export class UniqueMethodValidation implements ValidationRuleWithBeforeAfter * is parameter is the reasons, why this validation cannot be registered by default by Typir for classes, since this parameter is DSL-specific * @returns a validation rule which checks for any class declaration/type, whether they have no cycles in their sub-super-class-relationships */ -export function createNoSuperClassCyclesValidation(isRelevant: (domainElement: unknown) => boolean): ValidationRule { - return (domainElement: unknown, typir: TypirServices) => { +export function createNoSuperClassCyclesValidation(isRelevant: (languageNode: unknown) => boolean): ValidationRule { + return (languageNode: unknown, typir: TypirServices) => { const result: ValidationProblem[] = []; - if (isRelevant(domainElement)) { // improves performance, since type inference need to be done only for relevant elements - const classType = typir.Inference.inferType(domainElement); + if (isRelevant(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes + const classType = typir.Inference.inferType(languageNode); if (isClassType(classType) && classType.isInStateOrLater('Completed')) { // check for cycles in sub-type-relationships if (classType.hasSubSuperClassCycles()) { result.push({ $problem: ValidationProblem, - domainElement, + languageNode: languageNode, severity: 'error', message: `Cycles in super-sub-class-relationships are not allowed: ${classType.getName()}`, }); diff --git a/packages/typir/src/kinds/class/top-class-kind.ts b/packages/typir/src/kinds/class/top-class-kind.ts index 0114a19..60e9cda 100644 --- a/packages/typir/src/kinds/class/top-class-kind.ts +++ b/packages/typir/src/kinds/class/top-class-kind.ts @@ -15,7 +15,7 @@ export interface TopClassTypeDetails extends TypeDetails { inferenceRules?: InferTopClassType | InferTopClassType[] } -export type InferTopClassType = (domainElement: unknown) => boolean; +export type InferTopClassType = (anguageNode: unknown) => boolean; export interface TopClassKindOptions { name: string; @@ -71,9 +71,9 @@ export class TopClassKind implements Kind { protected registerInferenceRules(typeDetails: TopClassTypeDetails, topType: TopClassType) { const rules = toArray(typeDetails.inferenceRules); if (rules.length >= 1) { - this.services.Inference.addInferenceRule((domainElement, _typir) => { + this.services.Inference.addInferenceRule((languageNode, _typir) => { for (const inferenceRule of rules) { - if (inferenceRule(domainElement)) { + if (inferenceRule(languageNode)) { return topType; } } diff --git a/packages/typir/src/kinds/function/function-initializer.ts b/packages/typir/src/kinds/function/function-initializer.ts index f758065..13058f3 100644 --- a/packages/typir/src/kinds/function/function-initializer.ts +++ b/packages/typir/src/kinds/function/function-initializer.ts @@ -155,12 +155,12 @@ export class FunctionTypeInitializer extends TypeInitializer im // register inference rule for calls of the new function // TODO what about the case, that multiple variants match?? after implicit conversion for example?! => overload with the lowest number of conversions wins! result.inferenceForCall = { - inferTypeWithoutChildren(domainElement, _typir) { - const result = typeDetails.inferenceRuleForCalls!.filter(domainElement); + inferTypeWithoutChildren(languageNode, _typir) { + const result = typeDetails.inferenceRuleForCalls!.filter(languageNode); if (result) { - const matching = typeDetails.inferenceRuleForCalls!.matching(domainElement); + const matching = typeDetails.inferenceRuleForCalls!.matching(languageNode); if (matching) { - const inputArguments = typeDetails.inferenceRuleForCalls!.inputArguments(domainElement); + const inputArguments = typeDetails.inferenceRuleForCalls!.inputArguments(languageNode); if (inputArguments && inputArguments.length >= 1) { // this function type might match, to be sure, resolve the types of the values for the parameters and continue to step 2 const overloadInfos = mapNameTypes.get(functionName); @@ -182,15 +182,15 @@ export class FunctionTypeInitializer extends TypeInitializer im return check(outputTypeForFunctionCalls); } } else { - // the domain element is slightly different + // the language node is slightly different } } else { - // the domain element has a completely different purpose + // the language node has a completely different purpose } // does not match at all return InferenceRuleNotApplicable; }, - inferTypeWithChildrensTypes(domainElement, actualInputTypes, typir) { + inferTypeWithChildrensTypes(languageNode, actualInputTypes, typir) { const expectedInputTypes = typeDetails.inputParameters.map(p => typir.infrastructure.TypeResolver.resolve(p.type)); // all operands need to be assignable(! not equal) to the required types const comparisonConflicts = checkTypeArrays(actualInputTypes, expectedInputTypes, @@ -199,7 +199,7 @@ export class FunctionTypeInitializer extends TypeInitializer im // this function type does not match, due to assignability conflicts => return them as errors return { $problem: InferenceProblem, - domainElement, + languageNode: languageNode, inferenceCandidate: functionType, location: 'input parameters', rule: this, @@ -215,10 +215,10 @@ export class FunctionTypeInitializer extends TypeInitializer im } if (typeDetails.validationForCall) { - result.validationForCall = (domainElement, typir) => { - if (typeDetails.inferenceRuleForCalls!.filter(domainElement) && typeDetails.inferenceRuleForCalls!.matching(domainElement)) { + result.validationForCall = (languageNode, typir) => { + if (typeDetails.inferenceRuleForCalls!.filter(languageNode) && typeDetails.inferenceRuleForCalls!.matching(languageNode)) { // check the input arguments, required for overloaded functions - const inputArguments = typeDetails.inferenceRuleForCalls!.inputArguments(domainElement); + const inputArguments = typeDetails.inferenceRuleForCalls!.inputArguments(languageNode); if (inputArguments && inputArguments.length >= 1) { // this function type might match, to be sure, resolve the types of the values for the parameters and continue to step 2 const overloadInfos = mapNameTypes.get(functionName); @@ -235,18 +235,18 @@ export class FunctionTypeInitializer extends TypeInitializer im (t1, t2) => typir.Assignability.getAssignabilityProblem(t1, t2), true); if (comparisonConflicts.length <= 0) { // all arguments are assignable to the expected types of the parameters => this function is really called here => validate this call now - return typeDetails.validationForCall!(domainElement, functionType, typir); + return typeDetails.validationForCall!(languageNode, functionType, typir); } } else { // at least one argument could not be inferred } } else { // the current function is not overloaded, therefore, the types of their parameters are not required => save time - return typeDetails.validationForCall!(domainElement, functionType, typir); + return typeDetails.validationForCall!(languageNode, functionType, typir); } } else { // there are no operands to check - return typeDetails.validationForCall!(domainElement, functionType, typir); + return typeDetails.validationForCall!(languageNode, functionType, typir); } } return []; @@ -256,8 +256,8 @@ export class FunctionTypeInitializer extends TypeInitializer im // register inference rule for the declaration of the new function // (regarding overloaded function, for now, it is assumed, that the given inference rule itself is concrete enough to handle overloaded functions itself!) if (typeDetails.inferenceRuleForDeclaration) { - result.inferenceForDeclaration = (domainElement, _typir) => { - if (typeDetails.inferenceRuleForDeclaration!(domainElement)) { + result.inferenceForDeclaration = (languageNode, _typir) => { + if (typeDetails.inferenceRuleForDeclaration!(languageNode)) { return functionType; } else { return InferenceRuleNotApplicable; diff --git a/packages/typir/src/kinds/function/function-kind.ts b/packages/typir/src/kinds/function/function-kind.ts index d34dffc..2a62629 100644 --- a/packages/typir/src/kinds/function/function-kind.ts +++ b/packages/typir/src/kinds/function/function-kind.ts @@ -50,12 +50,12 @@ export interface FunctionTypeDetails extends TypeDetails { } export interface CreateFunctionTypeDetails extends FunctionTypeDetails { /** for function declarations => returns the funtion type (the whole signature including all names) */ - inferenceRuleForDeclaration?: (domainElement: unknown) => boolean, + inferenceRuleForDeclaration?: (languageNode: unknown) => boolean, /** for function calls => returns the return type of the function */ inferenceRuleForCalls?: InferFunctionCall, // TODO for function references (like the declaration, but without any names!) => returns signature (without any names) - /** This validation will be applied to all domain elements which represent calls of the functions. */ + /** This validation will be applied to all language nodes which represent calls of the functions. */ validationForCall?: FunctionCallValidationRule, } @@ -72,11 +72,11 @@ interface SingleFunctionDetails { inferenceRuleForCalls?: InferFunctionCall; } -// (domainElement: unknown) => boolean | unknown[] +// (languageNode: unknown) => boolean | unknown[] export type InferFunctionCall = { - filter: (domainElement: unknown) => domainElement is T; - matching: (domainElement: T) => boolean; - inputArguments: (domainElement: T) => unknown[]; + filter: (languageNode: unknown) => languageNode is T; + matching: (languageNode: T) => boolean; + inputArguments: (languageNode: T) => unknown[]; }; /** @@ -141,7 +141,7 @@ export class FunctionKind implements Kind, TypeGraphListener, FunctionFactorySer // register Validations for input arguments of function calls (must be done here to support overloaded functions) this.services.validation.Collector.addValidationRule( - (domainElement, typir) => { + (languageNode, typir) => { const resultAll: ValidationProblem[] = []; for (const [overloadedName, overloadedFunctions] of this.mapNameTypes.entries()) { const resultOverloaded: ValidationProblem[] = []; @@ -150,11 +150,11 @@ export class FunctionKind implements Kind, TypeGraphListener, FunctionFactorySer if (singleFunction.inferenceRuleForCalls === undefined) { continue; } - const filter = singleFunction.inferenceRuleForCalls.filter(domainElement); + const filter = singleFunction.inferenceRuleForCalls.filter(languageNode); if (filter) { - const matching = singleFunction.inferenceRuleForCalls.matching(domainElement); + const matching = singleFunction.inferenceRuleForCalls.matching(languageNode); if (matching) { - const inputArguments = singleFunction.inferenceRuleForCalls.inputArguments(domainElement); + const inputArguments = singleFunction.inferenceRuleForCalls.inputArguments(languageNode); if (inputArguments && inputArguments.length >= 1) { // partial match: const expectedParameterTypes = singleFunction.functionType.getInputs(); @@ -164,7 +164,7 @@ export class FunctionKind implements Kind, TypeGraphListener, FunctionFactorySer if (parameterLength.length >= 1) { currentProblems.push({ $problem: ValidationProblem, - domainElement, + languageNode: languageNode, severity: 'error', message: 'The number of given parameter values does not match the expected number of input parameters.', subProblems: parameterLength, @@ -181,7 +181,7 @@ export class FunctionKind implements Kind, TypeGraphListener, FunctionFactorySer // create one ValidationProblem for each problematic parameter! currentProblems.push({ $problem: ValidationProblem, - domainElement: inputArguments[i], + languageNode: inputArguments[i], severity: 'error', message: `The parameter '${expectedType.name}' at index ${i} got a value with a wrong type.`, subProblems: parameterProblems, @@ -196,7 +196,7 @@ export class FunctionKind implements Kind, TypeGraphListener, FunctionFactorySer // some problems with parameters => this signature does not match resultOverloaded.push({ $problem: ValidationProblem, - domainElement, + languageNode: languageNode, severity: 'error', message: `The given operands for the function '${this.services.Printer.printTypeName(singleFunction.functionType)}' match the expected types only partially.`, subProblems: currentProblems, @@ -220,7 +220,7 @@ export class FunctionKind implements Kind, TypeGraphListener, FunctionFactorySer if (isOverloaded) { resultAll.push({ $problem: ValidationProblem, - domainElement, + languageNode: languageNode, severity: 'error', message: `The given operands for the overloaded function '${overloadedName}' match the expected types only partially.`, subProblems: resultOverloaded, diff --git a/packages/typir/src/kinds/function/function-validation.ts b/packages/typir/src/kinds/function/function-validation.ts index 00ff9f5..2378ae7 100644 --- a/packages/typir/src/kinds/function/function-validation.ts +++ b/packages/typir/src/kinds/function/function-validation.ts @@ -14,30 +14,30 @@ import { isFunctionType, FunctionType } from './function-type.js'; export class UniqueFunctionValidation implements ValidationRuleWithBeforeAfter { protected readonly foundDeclarations: Map = new Map(); protected readonly services: TypirServices; - protected readonly isRelevant: (domainElement: unknown) => boolean; // using this check improves performance a lot + protected readonly isRelevant: (languageNode: unknown) => boolean; // using this check improves performance a lot - constructor(services: TypirServices, isRelevant: (domainElement: unknown) => boolean) { + constructor(services: TypirServices, isRelevant: (languageNode: unknown) => boolean) { this.services = services; this.isRelevant = isRelevant; } - beforeValidation(_domainRoot: unknown, _typir: TypirServices): ValidationProblem[] { + beforeValidation(_languageRoot: unknown, _typir: TypirServices): ValidationProblem[] { this.foundDeclarations.clear(); return []; } - validation(domainElement: unknown, _typir: TypirServices): ValidationProblem[] { - if (this.isRelevant(domainElement)) { // improves performance, since type inference need to be done only for relevant elements - const type = this.services.Inference.inferType(domainElement); + validation(languageNode: unknown, _typir: TypirServices): ValidationProblem[] { + if (this.isRelevant(languageNode)) { // improves performance, since type inference need to be done only for relevant language nodes + const type = this.services.Inference.inferType(languageNode); if (isFunctionType(type)) { - // register domain elements which have FunctionTypes with a key for their uniques + // register language nodes which have FunctionTypes with a key for their uniques const key = this.calculateFunctionKey(type); let entries = this.foundDeclarations.get(key); if (!entries) { entries = []; this.foundDeclarations.set(key, entries); } - entries.push(domainElement); + entries.push(languageNode); } } return []; @@ -54,14 +54,14 @@ export class UniqueFunctionValidation implements ValidationRuleWithBeforeAfter { return `${func.functionName}(${func.getInputs().map(param => param.type.getIdentifier())})`; } - afterValidation(_domainRoot: unknown, _typir: TypirServices): ValidationProblem[] { + afterValidation(_languageRoot: unknown, _typir: TypirServices): ValidationProblem[] { const result: ValidationProblem[] = []; for (const [key, functions] of this.foundDeclarations.entries()) { if (functions.length >= 2) { for (const func of functions) { result.push({ $problem: ValidationProblem, - domainElement: func, + languageNode: func, severity: 'error', message: `Declared functions need to be unique (${key}).`, }); diff --git a/packages/typir/src/kinds/primitive/primitive-kind.ts b/packages/typir/src/kinds/primitive/primitive-kind.ts index 339718a..b9d869b 100644 --- a/packages/typir/src/kinds/primitive/primitive-kind.ts +++ b/packages/typir/src/kinds/primitive/primitive-kind.ts @@ -21,7 +21,7 @@ export interface PrimitiveTypeDetails extends TypeDetails { inferenceRules?: InferPrimitiveType | InferPrimitiveType[]; } -export type InferPrimitiveType = (domainElement: unknown) => boolean; +export type InferPrimitiveType = (languageNode: unknown) => boolean; export const PrimitiveKindName = 'PrimitiveKind'; @@ -69,9 +69,9 @@ export class PrimitiveKind implements Kind, PrimitiveFactoryService { protected registerInferenceRules(typeDetails: PrimitiveTypeDetails, primitiveType: PrimitiveType) { const rules = toArray(typeDetails.inferenceRules); if (rules.length >= 1) { - this.services.Inference.addInferenceRule((domainElement, _typir) => { + this.services.Inference.addInferenceRule((languageNode, _typir) => { for (const inferenceRule of rules) { - if (inferenceRule(domainElement)) { + if (inferenceRule(languageNode)) { return primitiveType; } } diff --git a/packages/typir/src/kinds/top/top-kind.ts b/packages/typir/src/kinds/top/top-kind.ts index 3c4f71a..462fc5f 100644 --- a/packages/typir/src/kinds/top/top-kind.ts +++ b/packages/typir/src/kinds/top/top-kind.ts @@ -20,7 +20,7 @@ export interface TopKindOptions { name: string; } -export type InferTopType = (domainElement: unknown) => boolean; +export type InferTopType = (languageNode: unknown) => boolean; export const TopKindName = 'TopKind'; @@ -77,9 +77,9 @@ export class TopKind implements Kind, TopFactoryService { protected registerInferenceRules(typeDetails: TopTypeDetails, topType: TopType) { const rules = toArray(typeDetails.inferenceRules); if (rules.length >= 1) { - this.services.Inference.addInferenceRule((domainElement, _typir) => { + this.services.Inference.addInferenceRule((languageNode, _typir) => { for (const inferenceRule of rules) { - if (inferenceRule(domainElement)) { + if (inferenceRule(languageNode)) { return topType; } } diff --git a/packages/typir/src/services/caching.ts b/packages/typir/src/services/caching.ts index 63d1c25..a480306 100644 --- a/packages/typir/src/services/caching.ts +++ b/packages/typir/src/services/caching.ts @@ -102,20 +102,20 @@ export class DefaultTypeRelationshipCaching implements TypeRelationshipCaching { /** - * Domain element-to-Type caching for type inference. + * Language node-to-Type caching for type inference. */ -export interface DomainElementInferenceCaching { - cacheSet(domainElement: unknown, type: Type): void; - cacheGet(domainElement: unknown): Type | undefined; - pendingSet(domainElement: unknown): void; - pendingClear(domainElement: unknown): void; - pendingGet(domainElement: unknown): boolean; +export interface LanguageNodeInferenceCaching { + cacheSet(languageNode: unknown, type: Type): void; + cacheGet(languageNode: unknown): Type | undefined; + pendingSet(languageNode: unknown): void; + pendingClear(languageNode: unknown): void; + pendingGet(languageNode: unknown): boolean; } export type CachePending = 'CACHE_PENDING'; export const CachePending = 'CACHE_PENDING'; -export class DefaultDomainElementInferenceCaching implements DomainElementInferenceCaching { +export class DefaultLanguageNodeInferenceCaching implements LanguageNodeInferenceCaching { protected cache: Map; constructor() { @@ -126,32 +126,32 @@ export class DefaultDomainElementInferenceCaching implements DomainElementInfere this.cache = new Map(); } - cacheSet(domainElement: unknown, type: Type): void { - this.pendingClear(domainElement); - this.cache.set(domainElement, type); + cacheSet(languageNode: unknown, type: Type): void { + this.pendingClear(languageNode); + this.cache.set(languageNode, type); } - cacheGet(domainElement: unknown): Type | undefined { - if (this.pendingGet(domainElement)) { + cacheGet(languageNode: unknown): Type | undefined { + if (this.pendingGet(languageNode)) { return undefined; } else { - return this.cache.get(domainElement) as (Type | undefined); + return this.cache.get(languageNode) as (Type | undefined); } } - pendingSet(domainElement: unknown): void { - this.cache.set(domainElement, CachePending); + pendingSet(languageNode: unknown): void { + this.cache.set(languageNode, CachePending); } - pendingClear(domainElement: unknown): void { - if (this.cache.get(domainElement) !== CachePending) { + pendingClear(languageNode: unknown): void { + if (this.cache.get(languageNode) !== CachePending) { // do nothing } else { - this.cache.delete(domainElement); + this.cache.delete(languageNode); } } - pendingGet(domainElement: unknown): boolean { - return this.cache.has(domainElement) && this.cache.get(domainElement) === CachePending; + pendingGet(languageNode: unknown): boolean { + return this.cache.has(languageNode) && this.cache.get(languageNode) === CachePending; } } diff --git a/packages/typir/src/services/inference.ts b/packages/typir/src/services/inference.ts index fac8e9a..c544cce 100644 --- a/packages/typir/src/services/inference.ts +++ b/packages/typir/src/services/inference.ts @@ -10,11 +10,11 @@ import { TypeGraphListener } from '../graph/type-graph.js'; import { isType, Type } from '../graph/type-node.js'; import { TypirServices } from '../typir.js'; import { isSpecificTypirProblem, TypirProblem } from '../utils/utils-definitions.js'; -import { DomainElementInferenceCaching } from './caching.js'; +import { LanguageNodeInferenceCaching } from './caching.js'; export interface InferenceProblem extends TypirProblem { $problem: 'InferenceProblem'; - domainElement: unknown; + languageNode: unknown; inferenceCandidate?: Type; location: string; rule?: TypeInferenceRule; // for debugging only, since rules have no names (so far); TODO this does not really work with TypeInferenceRuleWithoutInferringChildren @@ -32,9 +32,9 @@ export const InferenceRuleNotApplicable = 'N/A'; // or 'undefined' instead? type TypeInferenceResultWithoutInferringChildren = /** the identified type */ Type | - /** 'N/A' to indicate, that the current inference rule is not applicable for the given domain element at all */ + /** 'N/A' to indicate, that the current inference rule is not applicable for the given language node at all */ InferenceRuleNotApplicable | - /** a domain element whose type should be inferred instead */ + /** a language node whose type should be inferred instead */ unknown | /** an inference problem */ InferenceProblem; @@ -46,8 +46,8 @@ type TypeInferenceResultWithInferringChildren = /** * Represents a single rule for inference, - * i.e. only a single type (or no type at all) can be inferred for a given domain element. - * There are inference rules which dependent on types of children of the given domain element (e.g. calls of overloaded functions depend on the types of the current arguments) + * i.e. only a single type (or no type at all) can be inferred for a given language node. + * There are inference rules which dependent on types of children of the given language node (e.g. calls of overloaded functions depend on the types of the current arguments) * and there are inference rules without this need. * * Within inference rules, don't take the initialization state of the inferred type into account, @@ -56,7 +56,7 @@ type TypeInferenceResultWithInferringChildren = export type TypeInferenceRule = TypeInferenceRuleWithoutInferringChildren | TypeInferenceRuleWithInferringChildren; /** Usual inference rule which don't depend on children's types. */ -export type TypeInferenceRuleWithoutInferringChildren = (domainElement: unknown, typir: TypirServices) => TypeInferenceResultWithoutInferringChildren; +export type TypeInferenceRuleWithoutInferringChildren = (languageNode: unknown, typir: TypirServices) => TypeInferenceResultWithoutInferringChildren; /** * Inference rule which requires for the type inference of the given parent to take the types of its children into account. @@ -64,26 +64,26 @@ export type TypeInferenceRuleWithoutInferringChildren = (domainElement: unknown, */ export interface TypeInferenceRuleWithInferringChildren { /** - * 1st step is to check, whether this inference rule is applicable to the given domain element. - * @param domainElement the element whose type shall be inferred + * 1st step is to check, whether this inference rule is applicable to the given language node. + * @param languageNode the language node whose type shall be inferred * @param typir the current Typir instance - * @returns Only in the case, that children elements are return, + * @returns Only in the case, that child language nodes are returned, * the other function will be called for step 2, otherwise, it is skipped. */ - inferTypeWithoutChildren(domainElement: unknown, typir: TypirServices): TypeInferenceResultWithInferringChildren; + inferTypeWithoutChildren(languageNode: unknown, typir: TypirServices): TypeInferenceResultWithInferringChildren; /** * 2nd step is to finally decide about the inferred type. - * When the 1st step returned a list of elements to resolve their types, + * When the 1st step returned a list of language nodes to resolve their types, * this function is called in order to complete this inference rule, otherwise, this step is not called. - * Advantage of this step is to split it to allow a postponed inferrence of the additional elements by Typir. - * Disadvantage of this step is, that already checked TS types of domainElement cannot be reused. - * @param domainElement the element whose type shall be inferred - * @param childrenTypes the types which are inferred from the elements of the 1st step (in the same order!) + * Advantage of this step is to split it to allow a postponed inferrence of the additional language nodes by Typir. + * Disadvantage of this step is, that already checked TS types of languageNode cannot be reused. + * @param languageNode the language node whose type shall be inferred + * @param childrenTypes the types which are inferred from the language nodes of the 1st step (in the same order!) * @param typir the current Typir instance * @returns the finally inferred type or a problem, why this inference rule is finally not applicable */ - inferTypeWithChildrensTypes(domainElement: unknown, childrenTypes: Array, typir: TypirServices): Type | InferenceProblem + inferTypeWithChildrensTypes(languageNode: unknown, childrenTypes: Array, typir: TypirServices): Type | InferenceProblem } @@ -94,19 +94,19 @@ export interface TypeInferenceCollectorListener { /** * Collects an arbitrary number of inference rules - * and allows to infer a type for a given domain element. + * and allows to infer a type for a given language node. */ export interface TypeInferenceCollector { /** - * Infers a type for the given element. - * @param domainElement the element whose type shall be inferred + * Infers a type for the given language node. + * @param languageNode the language node whose type shall be inferred * @returns the found Type or some inference problems (might be empty), when none of the inference rules were able to infer a type */ - inferType(domainElement: unknown): Type | InferenceProblem[] + inferType(languageNode: unknown): Type | InferenceProblem[] /** * Registers an inference rule. - * When inferring the type for an element, all registered inference rules are checked until the first match. + * When inferring the type for a language node, all registered inference rules are checked until the first match. * @param rule a new inference rule * @param boundToType an optional type, if the new inference rule is dedicated for exactly this type. * If the given type is removed from the type system, this rule will be automatically removed as well. @@ -121,13 +121,13 @@ export interface TypeInferenceCollector { export class DefaultTypeInferenceCollector implements TypeInferenceCollector, TypeGraphListener { protected readonly inferenceRules: Map = new Map(); // type identifier (otherwise '') -> inference rules - protected readonly domainElementInference: DomainElementInferenceCaching; + protected readonly languageNodeInference: LanguageNodeInferenceCaching; protected readonly services: TypirServices; protected readonly listeners: TypeInferenceCollectorListener[] = []; constructor(services: TypirServices) { this.services = services; - this.domainElementInference = services.caching.DomainElementInference; + this.languageNodeInference = services.caching.LanguageNodeInference; this.services.infrastructure.Graph.addListener(this); } @@ -157,46 +157,46 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty return boundToType?.getIdentifier() ?? ''; } - inferType(domainElement: unknown): Type | InferenceProblem[] { + inferType(languageNode: unknown): Type | InferenceProblem[] { // is the result already in the cache? - const cached = this.cacheGet(domainElement); + const cached = this.cacheGet(languageNode); if (cached) { return cached; } // handle recursion loops - if (this.pendingGet(domainElement)) { - throw new Error(`There is a recursion loop for inferring the type from ${domainElement}! Probably, there are multiple interfering inference rules.`); + if (this.pendingGet(languageNode)) { + throw new Error(`There is a recursion loop for inferring the type from ${languageNode}! Probably, there are multiple interfering inference rules.`); } - this.pendingSet(domainElement); + this.pendingSet(languageNode); // do the actual type inference - const result = this.inferTypeLogic(domainElement); + const result = this.inferTypeLogic(languageNode); // the calculation is done - this.pendingClear(domainElement); + this.pendingClear(languageNode); // remember the calculated type in the cache if (isType(result)) { - this.cacheSet(domainElement, result); + this.cacheSet(languageNode, result); } return result; } - protected checkForError(domainElement: unknown): void { - if (domainElement === undefined || domainElement === null) { - throw new Error('Element must be not undefined/null!'); + protected checkForError(languageNode: unknown): void { + if (languageNode === undefined || languageNode === null) { + throw new Error('Language node must be not undefined/null!'); } } - protected inferTypeLogic(domainElement: unknown): Type | InferenceProblem[] { - this.checkForError(domainElement); + protected inferTypeLogic(languageNode: unknown): Type | InferenceProblem[] { + this.checkForError(languageNode); // check all rules const collectedInferenceProblems: InferenceProblem[] = []; for (const rules of this.inferenceRules.values()) { for (const rule of rules) { - const result = this.executeSingleInferenceRuleLogic(rule, domainElement, collectedInferenceProblems); + const result = this.executeSingleInferenceRuleLogic(rule, languageNode, collectedInferenceProblems); if (result) { return result; // return the first inferred type } else { @@ -210,7 +210,7 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty // document the reason, why neither a type nor inference problems are found collectedInferenceProblems.push({ $problem: InferenceProblem, - domainElement, + languageNode: languageNode, location: 'found no applicable inference rules', subProblems: [], }); @@ -218,20 +218,20 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty return collectedInferenceProblems; } - protected executeSingleInferenceRuleLogic(rule: TypeInferenceRule, domainElement: unknown, collectedInferenceProblems: InferenceProblem[]): Type | undefined { + protected executeSingleInferenceRuleLogic(rule: TypeInferenceRule, languageNode: unknown, collectedInferenceProblems: InferenceProblem[]): Type | undefined { if (typeof rule === 'function') { // simple case without type inference for children - const ruleResult: TypeInferenceResultWithoutInferringChildren = rule(domainElement, this.services); + const ruleResult: TypeInferenceResultWithoutInferringChildren = rule(languageNode, this.services); this.checkForError(ruleResult); return this.inferTypeLogicWithoutChildren(ruleResult, collectedInferenceProblems); } else if (typeof rule === 'object') { // more complex case with inferring the type for children - const ruleResult: TypeInferenceResultWithInferringChildren = rule.inferTypeWithoutChildren(domainElement, this.services); + const ruleResult: TypeInferenceResultWithInferringChildren = rule.inferTypeWithoutChildren(languageNode, this.services); if (Array.isArray(ruleResult)) { // this rule might match => continue applying this rule // resolve the requested child types - const childElements = ruleResult; - const childTypes: Array = childElements.map(child => this.services.Inference.inferType(child)); + const childLanguageNodes = ruleResult; + const childTypes: Array = childLanguageNodes.map(child => this.services.Inference.inferType(child)); // check, whether inferring the children resulted in some other inference problems const childTypeProblems: InferenceProblem[] = []; for (let i = 0; i < childTypes.length; i++) { @@ -239,8 +239,8 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty if (Array.isArray(child)) { childTypeProblems.push({ $problem: InferenceProblem, - domainElement: childElements[i], - location: `child element ${i}`, + languageNode: childLanguageNodes[i], + location: `child language node ${i}`, rule, subProblems: child, }); @@ -249,7 +249,7 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty if (childTypeProblems.length >= 1) { collectedInferenceProblems.push({ $problem: InferenceProblem, - domainElement, + languageNode: languageNode, location: 'inferring depending children', rule, subProblems: childTypeProblems, @@ -257,7 +257,7 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty return undefined; } else { // the types of all children are successfully inferred - const finalInferenceResult = rule.inferTypeWithChildrensTypes(domainElement, childTypes as Type[], this.services); + const finalInferenceResult = rule.inferTypeWithChildrensTypes(languageNode, childTypes as Type[], this.services); if (isType(finalInferenceResult)) { // type is inferred! return finalInferenceResult; @@ -287,7 +287,7 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty collectedInferenceProblems.push(result); return undefined; } else { - // this 'result' domain element is used instead to infer its type, which is the type for the current domain element as well + // this 'result' language node is used instead to infer its type, which is the type for the current language node as well const recursiveResult = this.inferType(result); if (Array.isArray(recursiveResult)) { collectedInferenceProblems.push(...recursiveResult); @@ -333,22 +333,22 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty /* By default, the central cache of Typir is used. */ - protected cacheSet(domainElement: unknown, type: Type): void { - this.domainElementInference.cacheSet(domainElement, type); + protected cacheSet(languageNode: unknown, type: Type): void { + this.languageNodeInference.cacheSet(languageNode, type); } - protected cacheGet(domainElement: unknown): Type | undefined { - return this.domainElementInference.cacheGet(domainElement); + protected cacheGet(languageNode: unknown): Type | undefined { + return this.languageNodeInference.cacheGet(languageNode); } - protected pendingSet(domainElement: unknown): void { - this.domainElementInference.pendingSet(domainElement); + protected pendingSet(languageNode: unknown): void { + this.languageNodeInference.pendingSet(languageNode); } - protected pendingClear(domainElement: unknown): void { - this.domainElementInference.pendingClear(domainElement); + protected pendingClear(languageNode: unknown): void { + this.languageNodeInference.pendingClear(languageNode); } - protected pendingGet(domainElement: unknown): boolean { - return this.domainElementInference.pendingGet(domainElement); + protected pendingGet(languageNode: unknown): boolean { + return this.languageNodeInference.pendingGet(languageNode); } } @@ -362,13 +362,13 @@ export class DefaultTypeInferenceCollector implements TypeInferenceCollector, Ty export class CompositeTypeInferenceRule extends DefaultTypeInferenceCollector implements TypeInferenceRuleWithInferringChildren { // do not check "pending" (again), since it is already checked by the "parent" DefaultTypeInferenceCollector! - override pendingGet(_domainElement: unknown): boolean { + override pendingGet(_languageNode: unknown): boolean { return false; } - inferTypeWithoutChildren(domainElement: unknown, _typir: TypirServices): TypeInferenceResultWithInferringChildren { + inferTypeWithoutChildren(languageNode: unknown, _typir: TypirServices): TypeInferenceResultWithInferringChildren { // do the type inference - const result = this.inferType(domainElement); + const result = this.inferType(languageNode); if (isType(result)) { return result; } else { @@ -379,7 +379,7 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferenceCollector im } else { return { $problem: InferenceProblem, - domainElement, + languageNode: languageNode, location: 'sub-rules for inference', rule: this, subProblems: result, @@ -388,7 +388,7 @@ export class CompositeTypeInferenceRule extends DefaultTypeInferenceCollector im } } - inferTypeWithChildrensTypes(_domainElement: unknown, _childrenTypes: Array, _typir: TypirServices): Type | InferenceProblem { + inferTypeWithChildrensTypes(_languageNode: unknown, _childrenTypes: Array, _typir: TypirServices): Type | InferenceProblem { throw new Error('This function will not be called.'); } } diff --git a/packages/typir/src/services/operator.ts b/packages/typir/src/services/operator.ts index 043ed02..796f070 100644 --- a/packages/typir/src/services/operator.ts +++ b/packages/typir/src/services/operator.ts @@ -13,17 +13,17 @@ import { NameTypePair, TypeInitializers } from '../utils/utils-definitions.js'; import { toArray } from '../utils/utils.js'; import { ValidationProblem } from './validation.js'; -// export type InferOperatorWithSingleOperand = (domainElement: unknown, operatorName: string) => boolean | unknown; +// export type InferOperatorWithSingleOperand = (languageNode: unknown, operatorName: string) => boolean | unknown; export type InferOperatorWithSingleOperand = { - filter: (domainElement: unknown, operatorName: string) => domainElement is T; - matching: (domainElement: T, operatorName: string) => boolean; - operand: (domainElement: T, operatorName: string) => unknown; + filter: (languageNode: unknown, operatorName: string) => languageNode is T; + matching: (languageNode: T, operatorName: string) => boolean; + operand: (languageNode: T, operatorName: string) => unknown; }; -// export type InferOperatorWithMultipleOperands = (domainElement: unknown, operatorName: string) => boolean | unknown[]; +// export type InferOperatorWithMultipleOperands = (languageNode: unknown, operatorName: string) => boolean | unknown[]; export type InferOperatorWithMultipleOperands = { - filter: (domainElement: unknown, operatorName: string) => domainElement is T; - matching: (domainElement: T, operatorName: string) => boolean; - operands: (domainElement: T, operatorName: string) => unknown[]; + filter: (languageNode: unknown, operatorName: string) => languageNode is T; + matching: (languageNode: T, operatorName: string) => boolean; + operands: (languageNode: T, operatorName: string) => unknown[]; }; export type OperatorValidationRule = (operatorCall: T, operatorName: string, operatorType: Type, typir: TypirServices) => ValidationProblem[]; @@ -174,9 +174,9 @@ export class DefaultOperatorFactory implements OperatorFactoryService { inferenceRuleForDeclaration: undefined, // operators have no declaration in the code => no inference rule for the operator declaration! inferenceRuleForCalls: typeDetails.inferenceRule // but infer the operator when the operator is called! ? { - filter: (domainElement: unknown): domainElement is T => typeDetails.inferenceRule!.filter(domainElement, typeDetails.name), - matching: (domainElement: T) => typeDetails.inferenceRule!.matching(domainElement, typeDetails.name), - inputArguments: (domainElement: T) => this.getInputArguments(typeDetails, domainElement), + filter: (languageNode: unknown): languageNode is T => typeDetails.inferenceRule!.filter(languageNode, typeDetails.name), + matching: (languageNode: T) => typeDetails.inferenceRule!.matching(languageNode, typeDetails.name), + inputArguments: (languageNode: T) => this.getInputArguments(typeDetails, languageNode), } : undefined, validationForCall: typeDetails.validationRule @@ -187,10 +187,10 @@ export class DefaultOperatorFactory implements OperatorFactoryService { return newOperatorType as unknown as TypeInitializer; } - protected getInputArguments(typeDetails: GenericOperatorDetails, domainElement: unknown): unknown[] { + protected getInputArguments(typeDetails: GenericOperatorDetails, languageNode: unknown): unknown[] { return 'operands' in typeDetails.inferenceRule! - ? (typeDetails.inferenceRule as InferOperatorWithMultipleOperands).operands(domainElement, typeDetails.name) - : [(typeDetails.inferenceRule as InferOperatorWithSingleOperand).operand(domainElement, typeDetails.name)]; + ? (typeDetails.inferenceRule as InferOperatorWithMultipleOperands).operands(languageNode, typeDetails.name) + : [(typeDetails.inferenceRule as InferOperatorWithSingleOperand).operand(languageNode, typeDetails.name)]; } protected getFunctionFactory(): FunctionFactoryService { diff --git a/packages/typir/src/services/printing.ts b/packages/typir/src/services/printing.ts index 9851c69..5f895d2 100644 --- a/packages/typir/src/services/printing.ts +++ b/packages/typir/src/services/printing.ts @@ -26,7 +26,7 @@ export interface ProblemPrinter { printTypirProblem(problem: TypirProblem): string; printTypirProblems(problems: TypirProblem[]): string; - printDomainElement(domainElement: unknown, sentenceBegin: boolean): string; + printLanguageNode(languageNode: unknown, sentenceBegin: boolean): string; /** * This function should be used by other services, instead of using type.getName(). * This enables to customize the printing of type names by overriding only this implementation. @@ -116,7 +116,7 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter { } printInferenceProblem(problem: InferenceProblem, level: number = 0): string { - let result = `While inferring the type for ${this.printDomainElement(problem.domainElement)}, at ${problem.location}`; + let result = `While inferring the type for ${this.printLanguageNode(problem.languageNode)}, at ${problem.location}`; if (problem.inferenceCandidate) { result += ` of the type '${this.printTypeName(problem.inferenceCandidate)}' as candidate to infer`; } @@ -128,7 +128,7 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter { } printValidationProblem(problem: ValidationProblem, level: number = 0): string { - let result = `While validating ${this.printDomainElement(problem.domainElement)}, this ${problem.severity} is found: ${problem.message}`.trim(); + let result = `While validating ${this.printLanguageNode(problem.languageNode)}, this ${problem.severity} is found: ${problem.message}`.trim(); result = this.printIndentation(result, level); result = this.printSubProblems(result, problem.subProblems, level); return result; @@ -158,8 +158,8 @@ export class DefaultTypeConflictPrinter implements ProblemPrinter { return problems.map(p => this.printTypirProblem(p, level)).join('\n'); } - printDomainElement(domainElement: unknown, sentenceBegin: boolean = false): string { - return `${sentenceBegin ? 'T' : 't'}he domain element '${domainElement}'`; + printLanguageNode(languageNode: unknown, sentenceBegin: boolean = false): string { + return `${sentenceBegin ? 'T' : 't'}he language node '${languageNode}'`; } printTypeName(type: Type): string { diff --git a/packages/typir/src/services/validation.ts b/packages/typir/src/services/validation.ts index e108ab2..adfd1d7 100644 --- a/packages/typir/src/services/validation.ts +++ b/packages/typir/src/services/validation.ts @@ -16,9 +16,9 @@ import { ProblemPrinter } from './printing.js'; export type Severity = 'error' | 'warning' | 'info' | 'hint'; export interface ValidationMessageDetails { - domainElement: unknown; - domainProperty?: string; // name of a property of the domain element; TODO make this type-safe! - domainIndex?: number; // index, if this property is an Array property + languageNode: unknown; + languageProperty?: string; // name of a property of the language node; TODO make this type-safe! + languageIndex?: number; // index, if this property is an Array property severity: Severity; message: string; } @@ -32,12 +32,12 @@ export function isValidationProblem(problem: unknown): problem is ValidationProb return isSpecificTypirProblem(problem, ValidationProblem); } -export type ValidationRule = (domainElement: T, typir: TypirServices) => ValidationProblem[]; +export type ValidationRule = (languageNode: T, typir: TypirServices) => ValidationProblem[]; -export interface ValidationRuleWithBeforeAfter { - beforeValidation(domainRoot: RootType, typir: TypirServices): ValidationProblem[]; - validation: ValidationRule; - afterValidation(domainRoot: RootType, typir: TypirServices): ValidationProblem[]; +export interface ValidationRuleWithBeforeAfter { + beforeValidation(languageRoot: RootType, typir: TypirServices): ValidationProblem[]; + validation: ValidationRule; + afterValidation(languageRoot: RootType, typir: TypirServices): ValidationProblem[]; } /** Annotate types after the validation with additional information in order to ease the creation of usefull messages. */ @@ -56,7 +56,7 @@ export interface ValidationConstraints { ensureNodeHasNotType(sourceNode: unknown | undefined, notExpected: Type | undefined | unknown, message: ValidationMessageProvider): ValidationProblem[]; - ensureNodeRelatedWithType(domainNode: unknown | undefined, expected: Type | undefined | unknown, strategy: TypeCheckStrategy, negated: boolean, + ensureNodeRelatedWithType(languageNode: unknown | undefined, expected: Type | undefined | unknown, strategy: TypeCheckStrategy, negated: boolean, message: ValidationMessageProvider): ValidationProblem[]; } @@ -86,10 +86,10 @@ export class DefaultValidationConstraints implements ValidationConstraints { return this.ensureNodeRelatedWithType(sourceNode, notExpected, 'EQUAL_TYPE', true, message); } - ensureNodeRelatedWithType(domainNode: unknown | undefined, expected: Type | undefined | unknown, strategy: TypeCheckStrategy, negated: boolean, + ensureNodeRelatedWithType(languageNode: unknown | undefined, expected: Type | undefined | unknown, strategy: TypeCheckStrategy, negated: boolean, message: ValidationMessageProvider): ValidationProblem[] { - if (domainNode !== undefined && expected !== undefined) { - const actualType = isType(domainNode) ? domainNode : this.inference.inferType(domainNode); + if (languageNode !== undefined && expected !== undefined) { + const actualType = isType(languageNode) ? languageNode : this.inference.inferType(languageNode); const expectedType = isType(expected) ? expected : this.inference.inferType(expected); if (isType(actualType) && isType(expectedType)) { const strategyLogic = createTypeCheckStrategy(strategy, this.services); @@ -101,9 +101,9 @@ export class DefaultValidationConstraints implements ValidationConstraints { const details = message(this.annotateType(actualType), this.annotateType(expectedType)); return [{ $problem: ValidationProblem, - domainElement: details.domainElement ?? domainNode, - domainProperty: details.domainProperty, - domainIndex: details.domainIndex, + languageNode: details.languageNode ?? languageNode, + languageProperty: details.languageProperty, + languageIndex: details.languageIndex, severity: details.severity ?? 'error', message: details.message ?? `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, subProblems: [comparisonResult] @@ -114,9 +114,9 @@ export class DefaultValidationConstraints implements ValidationConstraints { const details = message(this.annotateType(actualType), this.annotateType(expectedType)); return [{ $problem: ValidationProblem, - domainElement: details.domainElement ?? domainNode, - domainProperty: details.domainProperty, - domainIndex: details.domainIndex, + languageNode: details.languageNode ?? languageNode, + languageProperty: details.languageProperty, + languageIndex: details.languageIndex, severity: details.severity ?? 'error', message: details.message ?? `'${actualType.getIdentifier()}' is ${negated ? '' : 'not '}related to '${expectedType.getIdentifier()}' regarding ${strategy}.`, subProblems: [] // no sub-problems are available! @@ -142,10 +142,10 @@ export class DefaultValidationConstraints implements ValidationConstraints { } -export interface ValidationCollector { - validateBefore(domainRoot: RootType): ValidationProblem[]; - validate(domainElement: ElementType): ValidationProblem[]; - validateAfter(domainRoot: RootType): ValidationProblem[]; +export interface ValidationCollector { + validateBefore(languageNode: RootType): ValidationProblem[]; + validate(languageNode: LanguageType): ValidationProblem[]; + validateAfter(languageNode: RootType): ValidationProblem[]; /** * Registers a validation rule. @@ -153,8 +153,8 @@ export interface ValidationCollector, boundToType?: Type): void; - removeValidationRule(rule: ValidationRule, boundToType?: Type): void; + addValidationRule(rule: ValidationRule, boundToType?: Type): void; + removeValidationRule(rule: ValidationRule, boundToType?: Type): void; /** * Registers a validation rule which will be called once before and once after the whole validation. @@ -162,56 +162,56 @@ export interface ValidationCollector, boundToType?: Type): void; - removeValidationRuleWithBeforeAndAfter(rule: ValidationRuleWithBeforeAfter, boundToType?: Type): void; + addValidationRuleWithBeforeAndAfter(rule: ValidationRuleWithBeforeAfter, boundToType?: Type): void; + removeValidationRuleWithBeforeAndAfter(rule: ValidationRuleWithBeforeAfter, boundToType?: Type): void; } -export class DefaultValidationCollector implements ValidationCollector, TypeGraphListener { +export class DefaultValidationCollector implements ValidationCollector, TypeGraphListener { protected readonly services: TypirServices; - protected readonly validationRules: Map>> = new Map(); // type identifier (otherwise '') -> validation rules - protected readonly validationRulesBeforeAfter: Map>> = new Map(); // type identifier (otherwise '') -> validation rules + protected readonly validationRules: Map>> = new Map(); // type identifier (otherwise '') -> validation rules + protected readonly validationRulesBeforeAfter: Map>> = new Map(); // type identifier (otherwise '') -> validation rules constructor(services: TypirServices) { this.services = services; this.services.infrastructure.Graph.addListener(this); } - validateBefore(domainRoot: RootType): ValidationProblem[] { + validateBefore(languageRoot: RootType): ValidationProblem[] { const problems: ValidationProblem[] = []; for (const rules of this.validationRulesBeforeAfter.values()) { for (const rule of rules) { - problems.push(...rule.beforeValidation(domainRoot, this.services)); + problems.push(...rule.beforeValidation(languageRoot, this.services)); } } return problems; } - validate(domainElement: ElementType): ValidationProblem[] { + validate(languageNode: LanguageType): ValidationProblem[] { const problems: ValidationProblem[] = []; for (const rules of this.validationRules.values()) { for (const rule of rules) { - problems.push(...rule(domainElement, this.services)); + problems.push(...rule(languageNode, this.services)); } } for (const rules of this.validationRulesBeforeAfter.values()) { for (const rule of rules) { - problems.push(...rule.validation(domainElement, this.services)); + problems.push(...rule.validation(languageNode, this.services)); } } return problems; } - validateAfter(domainRoot: RootType): ValidationProblem[] { + validateAfter(languageRoot: RootType): ValidationProblem[] { const problems: ValidationProblem[] = []; for (const rules of this.validationRulesBeforeAfter.values()) { for (const rule of rules) { - problems.push(...rule.afterValidation(domainRoot, this.services)); + problems.push(...rule.afterValidation(languageRoot, this.services)); } } return problems; } - addValidationRule(rule: ValidationRule, boundToType?: Type): void { + addValidationRule(rule: ValidationRule, boundToType?: Type): void { const key = this.getBoundToTypeKey(boundToType); let rules = this.validationRules.get(key); if (!rules) { @@ -221,7 +221,7 @@ export class DefaultValidationCollector, boundToType?: Type): void { + removeValidationRule(rule: ValidationRule, boundToType?: Type): void { const key = this.getBoundToTypeKey(boundToType); const rules = this.validationRules.get(key); if (rules) { @@ -232,7 +232,7 @@ export class DefaultValidationCollector, boundToType?: Type): void { + addValidationRuleWithBeforeAndAfter(rule: ValidationRuleWithBeforeAfter, boundToType?: Type): void { const key = this.getBoundToTypeKey(boundToType); let rules = this.validationRulesBeforeAfter.get(key); if (!rules) { @@ -242,7 +242,7 @@ export class DefaultValidationCollector, boundToType?: Type): void { + removeValidationRuleWithBeforeAndAfter(rule: ValidationRuleWithBeforeAfter, boundToType?: Type): void { const key = this.getBoundToTypeKey(boundToType); const rules = this.validationRulesBeforeAfter.get(key); if (rules) { diff --git a/packages/typir/src/typir.ts b/packages/typir/src/typir.ts index 239c98c..8d035ad 100644 --- a/packages/typir/src/typir.ts +++ b/packages/typir/src/typir.ts @@ -12,7 +12,7 @@ import { FunctionFactoryService, FunctionKind, FunctionKindName } from './kinds/ 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 { DefaultDomainElementInferenceCaching, DefaultTypeRelationshipCaching, DomainElementInferenceCaching, TypeRelationshipCaching } from './services/caching.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'; @@ -48,7 +48,7 @@ export type TypirServices = { readonly Inference: TypeInferenceCollector; readonly caching: { readonly TypeRelationships: TypeRelationshipCaching; - readonly DomainElementInference: DomainElementInferenceCaching; + readonly LanguageNodeInference: LanguageNodeInferenceCaching; }; readonly Printer: ProblemPrinter; readonly validation: { @@ -78,7 +78,7 @@ export const DefaultTypirServiceModule: Module = { Inference: (services) => new DefaultTypeInferenceCollector(services), caching: { TypeRelationships: (services) => new DefaultTypeRelationshipCaching(services), - DomainElementInference: () => new DefaultDomainElementInferenceCaching(), + LanguageNodeInference: () => new DefaultLanguageNodeInferenceCaching(), }, Printer: () => new DefaultTypeConflictPrinter(), validation: { diff --git a/packages/typir/test/type-definitions.test.ts b/packages/typir/test/type-definitions.test.ts index 72b7e36..3586dd6 100644 --- a/packages/typir/test/type-definitions.test.ts +++ b/packages/typir/test/type-definitions.test.ts @@ -31,7 +31,7 @@ describe('Tests for Typir', () => { // create some primitive types const typeInt = typir.factory.Primitives.create({ primitiveName: 'Integer' }); const typeString = typir.factory.Primitives.create({ primitiveName: 'String', - inferenceRules: domainElement => typeof domainElement === 'string'}); // combine type definition with a dedicated inference rule for it + inferenceRules: languageNode => typeof languageNode === 'string'}); // combine type definition with a dedicated inference rule for it const typeBoolean = typir.factory.Primitives.create({ primitiveName: 'Boolean' }); // create class type Person with 1 firstName and 1..2 lastNames and an age properties @@ -71,9 +71,9 @@ describe('Tests for Typir', () => { const opLess = typir.factory.Operators.createBinary({ name: '<', signature: { left: typeInt, right: typeInt, return: typeBoolean } }); const opEqualInt = typir.factory.Operators.createBinary({ name: '==', signature: { left: typeInt, right: typeInt, return: typeBoolean }, inferenceRule: { - filter: (domainElement): domainElement is string => typeof domainElement === 'string', - matching: domainElement => domainElement.includes('=='), - operands: domainElement => [] + filter: (languageNode): languageNode is string => typeof languageNode === 'string', + matching: languageNode => languageNode.includes('=='), + operands: languageNode => [] }}); // binary operators on Booleans const opEqualBool = typir.factory.Operators.createBinary({ name: '==', signature: { left: typeBoolean, right: typeBoolean, return: typeBoolean } }); @@ -81,9 +81,9 @@ describe('Tests for Typir', () => { // unary operators const opNotBool = typir.factory.Operators.createUnary({ name: '!', signature: { operand: typeBoolean, return: typeBoolean }, inferenceRule: { - filter: (domainElement): domainElement is string => typeof domainElement === 'string', - matching: domainElement => domainElement.includes('NOT'), - operand: domainElement => [] + filter: (languageNode): languageNode is string => typeof languageNode === 'string', + matching: languageNode => languageNode.includes('NOT'), + operand: languageNode => [] }}); // ternary operator const opTernaryIf = typir.factory.Operators.createTernary({ name: 'if', signature: { first: typeBoolean, second: typeInt, third: typeInt, return: typeInt } });