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 ...