From 7c499a18e0541abc2bd65323f989304e1a0c06e0 Mon Sep 17 00:00:00 2001 From: Johannes Meier <johannes.meier@typefox.io> Date: Sat, 14 Dec 2024 13:10:18 +0100 Subject: [PATCH] refactoring for LOX and OX: don't merge Langium+Typir services, but provide the Typir services via an "typir"-property/service within the Langium services --- examples/lox/src/language/lox-module.ts | 41 +++++----- .../language/type-system/lox-type-checking.ts | 7 +- .../test/lox-type-checking-classes.test.ts | 30 ++++---- .../lox/test/lox-type-checking-cycles.test.ts | 76 +++++++++---------- .../test/lox-type-checking-functions.test.ts | 8 +- examples/lox/test/lox-type-checking-utils.ts | 4 +- examples/ox/src/language/ox-module.ts | 33 ++++---- examples/ox/src/language/ox-type-checking.ts | 7 +- examples/ox/test/ox-type-checking-utils.ts | 2 +- .../src/features/langium-validation.ts | 6 +- packages/typir-langium/src/typir-langium.ts | 6 +- 11 files changed, 112 insertions(+), 108 deletions(-) diff --git a/examples/lox/src/language/lox-module.ts b/examples/lox/src/language/lox-module.ts index 3254ccf..c4ac538 100644 --- a/examples/lox/src/language/lox-module.ts +++ b/examples/lox/src/language/lox-module.ts @@ -4,7 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Module, PartialLangiumCoreServices, createDefaultCoreModule, inject } from 'langium'; +import { LangiumSharedCoreServices, Module, PartialLangiumCoreServices, createDefaultCoreModule, inject } from 'langium'; import { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, createDefaultSharedModule } from 'langium/lsp'; import { LangiumServicesForTypirBinding, createLangiumModuleForTypirBinding, initializeLangiumTypirServices } from 'typir-langium'; import { LoxGeneratedModule, LoxGeneratedSharedModule } from './generated/module.js'; @@ -18,29 +18,37 @@ import { createLoxTypirModule } from './type-system/lox-type-checking.js'; export type LoxAddedServices = { validation: { LoxValidator: LoxValidator - } + }, + typir: LangiumServicesForTypirBinding, } /** * Union of Langium default services and your custom services - use this as constructor parameter * of custom service classes. */ -export type LoxServices = LangiumServices & LoxAddedServices & LangiumServicesForTypirBinding +export type LoxServices = LangiumServices & LoxAddedServices /** * Dependency injection module that overrides Langium default services and contributes the * declared custom services. The Langium defaults can be partially specified to override only * selected services, while the custom services must be fully specified. */ -export const LoxModule: Module<LoxServices, PartialLangiumCoreServices & LoxAddedServices> = { - validation: { - ValidationRegistry: (services) => new LoxValidationRegistry(services), - LoxValidator: () => new LoxValidator() - }, - references: { - ScopeProvider: (services) => new LoxScopeProvider(services) - } -}; +export function createLoxModule(shared: LangiumSharedCoreServices): Module<LoxServices, PartialLangiumCoreServices & LoxAddedServices> { + return { + validation: { + ValidationRegistry: (services) => new LoxValidationRegistry(services), + LoxValidator: () => new LoxValidator() + }, + // For type checking with Typir, inject and merge these modules: + typir: () => inject(Module.merge( + createLangiumModuleForTypirBinding(shared), // the Typir default services + createLoxTypirModule(shared), // custom Typir services for LOX + )), + references: { + ScopeProvider: (services) => new LoxScopeProvider(services) + }, + }; +} /** * Create the full set of services required by Langium. @@ -53,9 +61,6 @@ export const LoxModule: Module<LoxServices, PartialLangiumCoreServices & LoxAdde * - Langium default language-specific services * - Services generated by langium-cli * - Services specified in this file - * For type checking with Typir, merge two more modules: - * - Typir default services - * - custom services for LOX * * @param context Optional module context with the LSP connection * @returns An object wrapping the shared services and the language-specific services @@ -71,11 +76,9 @@ export function createLoxServices(context: DefaultSharedModuleContext): { const Lox = inject( createDefaultCoreModule({ shared }), LoxGeneratedModule, - LoxModule, - createLangiumModuleForTypirBinding(shared), - createLoxTypirModule(shared), + createLoxModule(shared), ); shared.ServiceRegistry.register(Lox); - initializeLangiumTypirServices(Lox); + initializeLangiumTypirServices(Lox, Lox.typir); return { shared, Lox }; } diff --git a/examples/lox/src/language/type-system/lox-type-checking.ts b/examples/lox/src/language/type-system/lox-type-checking.ts index d229b8c..90c1c19 100644 --- a/examples/lox/src/language/type-system/lox-type-checking.ts +++ b/examples/lox/src/language/type-system/lox-type-checking.ts @@ -4,8 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, AstUtils, Module, assertUnreachable } from 'langium'; -import { LangiumSharedServices } from 'langium/lsp'; +import { AstNode, AstUtils, LangiumSharedCoreServices, Module, assertUnreachable } from 'langium'; import { CreateFieldDetails, CreateFunctionTypeDetails, CreateParameterDetails, InferOperatorWithMultipleOperands, InferOperatorWithSingleOperand, InferenceRuleNotApplicable, NO_PARAMETER_NAME, TypirServices, UniqueClassValidation, UniqueFunctionValidation, UniqueMethodValidation, ValidationMessageDetails, createNoSuperClassCyclesValidation } from 'typir'; import { AbstractLangiumTypeCreator, LangiumServicesForTypirBinding, PartialTypirLangiumServices } from 'typir-langium'; import { BinaryExpression, FunctionDeclaration, MemberCall, MethodMember, TypeReference, UnaryExpression, isBinaryExpression, isBooleanLiteral, isClass, isFieldMember, isForStatement, isFunctionDeclaration, isIfStatement, isMemberCall, isMethodMember, isNilLiteral, isNumberLiteral, isParameter, isPrintStatement, isReturnStatement, isStringLiteral, isTypeReference, isUnaryExpression, isVariableDeclaration, isWhileStatement } from '../generated/ast.js'; @@ -14,7 +13,7 @@ import { BinaryExpression, FunctionDeclaration, MemberCall, MethodMember, TypeRe export class LoxTypeCreator extends AbstractLangiumTypeCreator { protected readonly typir: TypirServices; - constructor(typirServices: TypirServices, langiumServices: LangiumSharedServices) { + constructor(typirServices: TypirServices, langiumServices: LangiumSharedCoreServices) { super(typirServices, langiumServices); this.typir = typirServices; } @@ -259,7 +258,7 @@ function createFunctionDetails(node: FunctionDeclaration | MethodMember): Create }; } -export function createLoxTypirModule(langiumServices: LangiumSharedServices): Module<LangiumServicesForTypirBinding, PartialTypirLangiumServices> { +export function createLoxTypirModule(langiumServices: LangiumSharedCoreServices): Module<LangiumServicesForTypirBinding, PartialTypirLangiumServices> { return { // specific configurations for LOX TypeCreator: (typirServices) => new LoxTypeCreator(typirServices, langiumServices), diff --git a/examples/lox/test/lox-type-checking-classes.test.ts b/examples/lox/test/lox-type-checking-classes.test.ts index 9a0cd3f..d24453d 100644 --- a/examples/lox/test/lox-type-checking-classes.test.ts +++ b/examples/lox/test/lox-type-checking-classes.test.ts @@ -17,7 +17,7 @@ describe('Test type checking for classes', () => { class MyClass2 < MyClass1 {} var v1: MyClass1 = MyClass2(); `, 0); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); }); test('Class inheritance for assignments: wrong', async () => { @@ -26,7 +26,7 @@ describe('Test type checking for classes', () => { class MyClass2 < MyClass1 {} var v1: MyClass2 = MyClass1(); `, 1); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); }); test('Class fields: correct values', async () => { @@ -36,7 +36,7 @@ describe('Test type checking for classes', () => { v1.name = "Bob"; v1.age = 42; `, 0); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class fields: wrong values', async () => { @@ -46,7 +46,7 @@ describe('Test type checking for classes', () => { v1.name = 42; v1.age = "Bob"; `, 2); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Classes must be unique by name 2', async () => { @@ -57,7 +57,7 @@ describe('Test type checking for classes', () => { 'Declared classes need to be unique (MyClass1).', 'Declared classes need to be unique (MyClass1).', ]); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Classes must be unique by name 3', async () => { @@ -70,7 +70,7 @@ describe('Test type checking for classes', () => { 'Declared classes need to be unique (MyClass2).', 'Declared classes need to be unique (MyClass2).', ]); - expectTypirTypes(loxServices, isClassType, 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass2'); }); test('Class methods: OK', async () => { @@ -83,7 +83,7 @@ describe('Test type checking for classes', () => { var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(456); `, []); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class methods: wrong return value', async () => { @@ -96,7 +96,7 @@ describe('Test type checking for classes', () => { var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(456); `, 1); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class methods: method return type does not fit to variable type', async () => { @@ -109,7 +109,7 @@ describe('Test type checking for classes', () => { var v1: MyClass1 = MyClass1(); var v2: boolean = v1.method1(456); `, 1); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class methods: value for input parameter does not fit to the type of the input parameter', async () => { @@ -122,7 +122,7 @@ describe('Test type checking for classes', () => { var v1: MyClass1 = MyClass1(); var v2: number = v1.method1(true); `, 1); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); test('Class methods: methods are not distinguishable', async () => { @@ -139,7 +139,7 @@ describe('Test type checking for classes', () => { 'Declared methods need to be unique (class-MyClass1.method1(number)).', 'Declared methods need to be unique (class-MyClass1.method1(number)).', ]); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); }); @@ -151,7 +151,7 @@ describe('Class literals', () => { class MyClass { name: string age: number } var v1 = MyClass(); // constructor call `, []); - expectTypirTypes(loxServices, isClassType, 'MyClass'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); }); test('Class literals 2', async () => { @@ -159,7 +159,7 @@ describe('Class literals', () => { class MyClass { name: string age: number } var v1: MyClass = MyClass(); // constructor call `, []); - expectTypirTypes(loxServices, isClassType, 'MyClass'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass'); }); test('Class literals 3', async () => { @@ -168,7 +168,7 @@ describe('Class literals', () => { class MyClass2 {} var v1: boolean = MyClass1() == MyClass2(); // comparing objects with each other `, [], 1); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); }); test('nil is assignable to any Class', async () => { @@ -180,7 +180,7 @@ describe('Class literals', () => { v1 = nil; v2 = nil; `, []); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); }); }); diff --git a/examples/lox/test/lox-type-checking-cycles.test.ts b/examples/lox/test/lox-type-checking-cycles.test.ts index b87455d..9aae69e 100644 --- a/examples/lox/test/lox-type-checking-cycles.test.ts +++ b/examples/lox/test/lox-type-checking-cycles.test.ts @@ -17,7 +17,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( children: Node } `, []); - expectTypirTypes(loxServices, isClassType, 'Node'); + expectTypirTypes(loxServices.typir, isClassType, 'Node'); }); test('Two Classes with fields with the other Class as type', async () => { @@ -29,7 +29,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( prop2: A } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); test('Three Classes with fields with one of the other Classes as type', async () => { @@ -44,7 +44,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( prop3: A } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B', 'C'); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B', 'C'); }); test('Three Classes with fields with two of the other Classes as type', async () => { @@ -62,7 +62,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( prop6: B } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B', 'C'); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B', 'C'); }); test('Class with field of its own type and another dependency', async () => { @@ -75,7 +75,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( children: Node } `, []); - expectTypirTypes(loxServices, isClassType, 'Node', 'Another'); + expectTypirTypes(loxServices.typir, isClassType, 'Node', 'Another'); }); test('Two Classes with a field of its own type and cyclic dependencies to each other', async () => { @@ -89,7 +89,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( another: Node } `, []); - expectTypirTypes(loxServices, isClassType, 'Node', 'Another'); + expectTypirTypes(loxServices.typir, isClassType, 'Node', 'Another'); }); test('Having two declarations for the delayed class A, but only one type A in the type system', async () => { @@ -106,7 +106,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( 'Declared classes need to be unique (A).', ]); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); test('Having three declarations for the delayed class A, but only one type A in the type system', async () => { @@ -127,7 +127,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( 'Declared classes need to be unique (A).', ]); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); test('Having two declarations for class A waiting for B, while B itself depends on A', async () => { @@ -146,7 +146,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( 'Declared classes need to be unique (A).', ]); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); }); test('Class with method: cycle with return type', async () => { @@ -155,8 +155,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( myMethod(input: number): Node {} } `, []); - expectTypirTypes(loxServices, isClassType, 'Node'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'Node'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Class with method: cycle with input parameter type', async () => { @@ -165,8 +165,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( myMethod(input: Node): number {} } `, []); - expectTypirTypes(loxServices, isClassType, 'Node'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'Node'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Two different Classes with the same method (type) should result in only one method type', async () => { @@ -180,8 +180,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( myMethod(input: number): boolean {} } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Two different Classes depend on each other regarding their methods return type', async () => { @@ -195,8 +195,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( myMethod(input: number): A {} } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', 'myMethod', ...operatorNames); }); test('Two different Classes with the same method which has one of these classes as return type', async () => { @@ -210,8 +210,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( myMethod(input: number): B {} } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Same delayed function type is used by a function declaration and a method declaration', async () => { @@ -222,8 +222,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( fun myMethod(input: number): B {} class B { } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Two class declarations A with the same delayed method which depends on the class B', async () => { @@ -240,8 +240,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( 'Declared classes need to be unique (A).', ]); // check, that there is only one class type A in the type graph: - expectTypirTypes(loxServices, isClassType, 'A', 'B'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Mix of dependencies in classes: 1 method and 1 field', async () => { @@ -253,8 +253,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( propB1: A } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B1'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Mix of dependencies in classes: 1 method and 2 fields (order 1)', async () => { @@ -269,8 +269,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( myMethod(input: number): B1 {} } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B1', 'B2'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('Mix of dependencies in classes: 1 method and 2 fields (order 2)', async () => { @@ -285,8 +285,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( propB1: A } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B1', 'B2'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', ...operatorNames); }); test('The same class is involved into two dependency cycles', async () => { @@ -308,8 +308,8 @@ describe('Cyclic type definitions where a Class is declared and already used', ( methodC2(p: A): void {} } `, []); - expectTypirTypes(loxServices, isClassType, 'A', 'B1', 'B2', 'C1', 'C2'); - expectTypirTypes(loxServices, isFunctionType, 'myMethod', 'methodC1', 'methodC2', ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType, 'A', 'B1', 'B2', 'C1', 'C2'); + expectTypirTypes(loxServices.typir, isFunctionType, 'myMethod', 'methodC1', 'methodC2', ...operatorNames); }); test('Class inheritance and the order of type definitions', async () => { @@ -318,7 +318,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class MyClass1 {} class MyClass2 < MyClass1 {} `, []); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); }); test('Class inheritance and the order of type definitions', async () => { @@ -327,7 +327,7 @@ describe('Cyclic type definitions where a Class is declared and already used', ( class MyClass2 < MyClass1 {} class MyClass1 {} `, []); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); }); }); @@ -342,7 +342,7 @@ describe('Test internal validation of Typir for cycles in the class inheritance 'Cycles in super-sub-class-relationships are not allowed: MyClass2', 'Cycles in super-sub-class-relationships are not allowed: MyClass3', ]); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2', 'MyClass3'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2', 'MyClass3'); }); test('Two involved classes: 1 -> 2 -> 1', async () => { @@ -353,14 +353,14 @@ describe('Test internal validation of Typir for cycles in the class inheritance 'Cycles in super-sub-class-relationships are not allowed: MyClass1', 'Cycles in super-sub-class-relationships are not allowed: MyClass2', ]); - expectTypirTypes(loxServices, isClassType, 'MyClass1', 'MyClass2'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1', 'MyClass2'); }); test('One involved class: 1 -> 1', async () => { await validateLox(` class MyClass1 < MyClass1 { } `, 'Cycles in super-sub-class-relationships are not allowed: MyClass1'); - expectTypirTypes(loxServices, isClassType, 'MyClass1'); + expectTypirTypes(loxServices.typir, isClassType, 'MyClass1'); }); }); @@ -402,7 +402,7 @@ describe('longer LOX examples with classes regarding ordering', () => { var superType: SuperClass = x; print superType.a; `, []); - expectTypirTypes(loxServices, isClassType, 'SuperClass', 'SubClass', 'NestedClass'); + expectTypirTypes(loxServices.typir, isClassType, 'SuperClass', 'SubClass', 'NestedClass'); }); test('complete with easy order of classes', async () => { @@ -442,6 +442,6 @@ describe('longer LOX examples with classes regarding ordering', () => { var superType: SuperClass = x; print superType.a; `, []); - expectTypirTypes(loxServices, isClassType, 'SuperClass', 'SubClass', 'NestedClass'); + expectTypirTypes(loxServices.typir, isClassType, 'SuperClass', 'SubClass', 'NestedClass'); }); }); diff --git a/examples/lox/test/lox-type-checking-functions.test.ts b/examples/lox/test/lox-type-checking-functions.test.ts index 44193bb..169b464 100644 --- a/examples/lox/test/lox-type-checking-functions.test.ts +++ b/examples/lox/test/lox-type-checking-functions.test.ts @@ -16,7 +16,7 @@ describe('Test type checking for user-defined functions', () => { await validateLox('fun myFunction2() : boolean { return 2; }', 1); await validateLox('fun myFunction3() : number { return 2; }', 0); await validateLox('fun myFunction4() : number { return true; }', 1); - expectTypirTypes(loxServices, isFunctionType, 'myFunction1', 'myFunction2', 'myFunction3', 'myFunction4', ...operatorNames); + expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction1', 'myFunction2', 'myFunction3', 'myFunction4', ...operatorNames); }); test('overloaded function: different return types are not enough', async () => { @@ -24,7 +24,7 @@ describe('Test type checking for user-defined functions', () => { fun myFunction() : boolean { return true; } fun myFunction() : number { return 2; } `, 2); - expectTypirTypes(loxServices, isFunctionType, 'myFunction', 'myFunction', ...operatorNames); // the types are different nevertheless! + expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction', 'myFunction', ...operatorNames); // the types are different nevertheless! }); test('overloaded function: different parameter names are not enough', async () => { @@ -32,7 +32,7 @@ describe('Test type checking for user-defined functions', () => { fun myFunction(input: boolean) : boolean { return true; } fun myFunction(other: boolean) : boolean { return true; } `, 2); - expectTypirTypes(loxServices, isFunctionType, 'myFunction', ...operatorNames); // but both functions have the same type! + expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction', ...operatorNames); // but both functions have the same type! }); test('overloaded function: but different parameter types are fine', async () => { @@ -40,7 +40,7 @@ describe('Test type checking for user-defined functions', () => { fun myFunction(input: boolean) : boolean { return true; } fun myFunction(input: number) : boolean { return true; } `, 0); - expectTypirTypes(loxServices, isFunctionType, 'myFunction', 'myFunction', ...operatorNames); + expectTypirTypes(loxServices.typir, isFunctionType, 'myFunction', 'myFunction', ...operatorNames); }); }); diff --git a/examples/lox/test/lox-type-checking-utils.ts b/examples/lox/test/lox-type-checking-utils.ts index be7114b..94e9ef2 100644 --- a/examples/lox/test/lox-type-checking-utils.ts +++ b/examples/lox/test/lox-type-checking-utils.ts @@ -20,8 +20,8 @@ export const operatorNames = ['-', '*', '/', '+', '+', '+', '+', '<', '<=', '>', afterEach(async () => { await deleteAllDocuments(loxServices.shared); // check, that there are no user-defined classes and functions after clearing/invalidating all LOX documents - expectTypirTypes(loxServices, isClassType); - expectTypirTypes(loxServices, isFunctionType, ...operatorNames); + expectTypirTypes(loxServices.typir, isClassType); + expectTypirTypes(loxServices.typir, isFunctionType, ...operatorNames); }); export async function validateLox(lox: string, errors: number | string | string[], warnings: number | string | string[] = 0) { diff --git a/examples/ox/src/language/ox-module.ts b/examples/ox/src/language/ox-module.ts index 7861bef..f4d6715 100644 --- a/examples/ox/src/language/ox-module.ts +++ b/examples/ox/src/language/ox-module.ts @@ -4,7 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { Module, inject } from 'langium'; +import { LangiumSharedCoreServices, Module, inject } from 'langium'; import { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, PartialLangiumServices, createDefaultModule, createDefaultSharedModule } from 'langium/lsp'; import { LangiumServicesForTypirBinding, createLangiumModuleForTypirBinding, initializeLangiumTypirServices } from 'typir-langium'; import { OxGeneratedModule, OxGeneratedSharedModule } from './generated/module.js'; @@ -17,25 +17,33 @@ import { OxValidator, registerValidationChecks } from './ox-validator.js'; export type OxAddedServices = { validation: { OxValidator: OxValidator - } + }, + typir: LangiumServicesForTypirBinding, } /** * Union of Langium default services and your custom services - use this as constructor parameter * of custom service classes. */ -export type OxServices = LangiumServices & OxAddedServices & LangiumServicesForTypirBinding +export type OxServices = LangiumServices & OxAddedServices /** * Dependency injection module that overrides Langium default services and contributes the * declared custom services. The Langium defaults can be partially specified to override only * selected services, while the custom services must be fully specified. */ -export const OxModule: Module<OxServices, PartialLangiumServices & OxAddedServices> = { - validation: { - OxValidator: () => new OxValidator() - } -}; +export function createOxModule(shared: LangiumSharedCoreServices): Module<OxServices, PartialLangiumServices & OxAddedServices> { + return { + validation: { + OxValidator: () => new OxValidator() + }, + // For type checking with Typir, inject and merge these modules: + typir: () => inject(Module.merge( + createLangiumModuleForTypirBinding(shared), // the Typir default services + createOxTypirModule(shared), // custom Typir services for LOX + )), + }; +} /** * Create the full set of services required by Langium. @@ -48,9 +56,6 @@ export const OxModule: Module<OxServices, PartialLangiumServices & OxAddedServic * - Langium default language-specific services * - Services generated by langium-cli * - Services specified in this file - * For type checking with Typir, merge two more modules: - * - Typir default services - * - custom services for OX * * @param context Optional module context with the LSP connection * @returns An object wrapping the shared services and the language-specific services @@ -66,12 +71,10 @@ export function createOxServices(context: DefaultSharedModuleContext): { const Ox = inject( createDefaultModule({ shared }), OxGeneratedModule, - OxModule, - createLangiumModuleForTypirBinding(shared), - createOxTypirModule(shared), + createOxModule(shared), ); shared.ServiceRegistry.register(Ox); registerValidationChecks(Ox); - initializeLangiumTypirServices(Ox); + initializeLangiumTypirServices(Ox, Ox.typir); return { shared, Ox }; } diff --git a/examples/ox/src/language/ox-type-checking.ts b/examples/ox/src/language/ox-type-checking.ts index 271988a..e9d4e74 100644 --- a/examples/ox/src/language/ox-type-checking.ts +++ b/examples/ox/src/language/ox-type-checking.ts @@ -4,8 +4,7 @@ * terms of the MIT License, which is available in the project root. ******************************************************************************/ -import { AstNode, AstUtils, Module, assertUnreachable } from 'langium'; -import { LangiumSharedServices } from 'langium/lsp'; +import { AstNode, AstUtils, LangiumSharedCoreServices, Module, assertUnreachable } from 'langium'; import { CreateParameterDetails, InferOperatorWithMultipleOperands, InferOperatorWithSingleOperand, InferenceRuleNotApplicable, NO_PARAMETER_NAME, TypirServices, UniqueFunctionValidation } from 'typir'; import { AbstractLangiumTypeCreator, LangiumServicesForTypirBinding, PartialTypirLangiumServices } from 'typir-langium'; import { ValidationMessageDetails } from '../../../../packages/typir/lib/services/validation.js'; @@ -14,7 +13,7 @@ import { BinaryExpression, MemberCall, UnaryExpression, isAssignmentStatement, i export class OxTypeCreator extends AbstractLangiumTypeCreator { protected readonly typir: TypirServices; - constructor(typirServices: TypirServices, langiumServices: LangiumSharedServices) { + constructor(typirServices: TypirServices, langiumServices: LangiumSharedCoreServices) { super(typirServices, langiumServices); this.typir = typirServices; } @@ -187,7 +186,7 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator { } -export function createOxTypirModule(langiumServices: LangiumSharedServices): Module<LangiumServicesForTypirBinding, PartialTypirLangiumServices> { +export function createOxTypirModule(langiumServices: LangiumSharedCoreServices): Module<LangiumServicesForTypirBinding, PartialTypirLangiumServices> { return { // specific configurations for OX TypeCreator: (typirServices) => new OxTypeCreator(typirServices, langiumServices), diff --git a/examples/ox/test/ox-type-checking-utils.ts b/examples/ox/test/ox-type-checking-utils.ts index 7efbb7a..1fa4c3d 100644 --- a/examples/ox/test/ox-type-checking-utils.ts +++ b/examples/ox/test/ox-type-checking-utils.ts @@ -19,7 +19,7 @@ export const operatorNames = ['-', '*', '/', '+', '<', '<=', '>', '>=', 'and', ' afterEach(async () => { await deleteAllDocuments(oxServices.shared); // check, that there are no user-defined classes and functions after clearing/invalidating all LOX documents - expectTypirTypes(oxServices, isFunctionType, ...operatorNames); + expectTypirTypes(oxServices.typir, isFunctionType, ...operatorNames); }); export async function validateOx(ox: string, errors: number | string | string[]) { diff --git a/packages/typir-langium/src/features/langium-validation.ts b/packages/typir-langium/src/features/langium-validation.ts index 5887d31..ae5cf6c 100644 --- a/packages/typir-langium/src/features/langium-validation.ts +++ b/packages/typir-langium/src/features/langium-validation.ts @@ -8,9 +8,9 @@ import { AstNode, LangiumDefaultCoreServices, ValidationAcceptor, ValidationChec import { TypirServices, ValidationProblem } from 'typir'; import { LangiumServicesForTypirBinding } from '../typir-langium.js'; -export function registerTypirValidationChecks(services: LangiumDefaultCoreServices & LangiumServicesForTypirBinding) { - const registry = services.validation.ValidationRegistry; - const validator = services.TypeValidation; +export function registerTypirValidationChecks(langiumServices: LangiumDefaultCoreServices, typirServices: LangiumServicesForTypirBinding) { + const registry = langiumServices.validation.ValidationRegistry; + const validator = typirServices.TypeValidation; registry.registerBeforeDocument(validator.checkTypingProblemsWithTypirBeforeDocument, validator); const checks: ValidationChecks<object> = { AstNode: validator.checkTypingProblemsWithTypir, // checking each node is not performant, improve the API, see below! diff --git a/packages/typir-langium/src/typir-langium.ts b/packages/typir-langium/src/typir-langium.ts index 122c55e..021b896 100644 --- a/packages/typir-langium/src/typir-langium.ts +++ b/packages/typir-langium/src/typir-langium.ts @@ -60,12 +60,12 @@ export function createLangiumModuleForTypirBinding(langiumServices: LangiumShare ); } -export function initializeLangiumTypirServices(services: LangiumDefaultCoreServices & LangiumServicesForTypirBinding): void { +export function initializeLangiumTypirServices(langiumServices: LangiumDefaultCoreServices, typirServices: LangiumServicesForTypirBinding): void { // register the type-related validations of Typir at the Langium validation registry - registerTypirValidationChecks(services); + registerTypirValidationChecks(langiumServices, typirServices); // initialize the type creation (this is not done automatically by dependency injection!) - services.TypeCreator.triggerInitialization(); + typirServices.TypeCreator.triggerInitialization(); /* Don't use the following code ...