Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 68 additions & 4 deletions documentation/customization.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,75 @@
# Customize default implementations
# Customize Typir

This describes how the default implementations of Typir can be customized.
This describes how the default behaviour of Typir can be customized.
How to use custom types in Typir is described [in this section](./kinds/custom-types.md).

If you are already familar with Langium and its [strategies for customization](https://langium.org/docs/reference/configuration-services/#customization), feel free to skip this section, since the strategies and even the implementation are nearly the same.

As described in the [design section](./design.md), nearly all features of Typir are exposed by APIs in form of interfaces,
for which Typir provides classes implementing these interfaces as default implementations. These interfaces and implementations are composed in `typir.ts`.
for which Typir provides classes implementing these interfaces as default implementations. These interfaces and implementations are composed in ...

TODO
- `typir.ts` for Typir (core)
- `typir-langium.ts` for Typir-Langium

Some examples how to customize existing services and how to add new services are sketched in `customization-example.test.ts`.


## Customize the implementation of existing services

To customize or replace the default implementation for an existing Typir service, just provide another implementation when initializing the Typir services.
As an example, the existing factory to create classes is replaced to allow two super classes now (default is one super class only):

```typescript
const customizedTypir = createTypirServices({
factory: {
Classes: services => new ClassKind(services, { maximumNumberOfSuperClasses: 2 }),
},
// ... customize as many existing services as you like ...
});
```

## Add additional services

Additional services need to be explicitly specified.
In general, you can add an arbitrary number of services, which might be deeply grouped.
It is even possible to add new services to already existing groups.
In the following example, an additional factory for classes is exposed as service:

```typescript
type AdditionalExampleTypirServices = {
readonly factory: {
readonly OtherClasses: ClassFactoryService<TestLanguageNode>;
},
};
```

Provide implementations for all added services when you instantiate the Typir services.
Instead of `createTypirServices`, use `createTypirServicesWithAdditionalServices` instead:

```typescript
const customizedTypir: TypirServices<TestLanguageNode> & AdditionalExampleTypirServices = createTypirServicesWithAdditionalServices<TestLanguageNode, AdditionalExampleTypirServices>({
factory: {
OtherClasses: services => new ClassKind(services, { maximumNumberOfSuperClasses: 2, $name: 'OtherClass' }),
},
});
```

TypeScript don't force you to write `TypirServices<TestLanguageNode> & AdditionalExampleTypirServices` in the code snipped above, but makes explicit what is going on here.
Comment thread
JohannesMeierSE marked this conversation as resolved.
Outdated
To simplify the code, it is possible (but not mandatory) to introduce a TypeScript type like the following and to use it instead:

```typescript
type ExampleTypirServices = TypirServices<TestLanguageNode> & AdditionalExampleTypirServices;
```

Newly added services are usable by all other services, including new services and existing services.
The latter is important when customizing default implementations, when the custom implementation depends on the new services.

It is possible to provide implementations for new services together with customizations for existing services:

```typescript
const customizedTypir: ExampleTypirServices = createTypirServicesWithAdditionalServices<TestLanguageNode, AdditionalExampleTypirServices>({
// 1st argument: mandatory implementations for all new services
}, {
// 2nd argument: customize some existing services here
});
```
2 changes: 2 additions & 0 deletions documentation/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Each type system, i.e. each instance of the `TypirServices`, has one type graph:
- services
- (default) implementations
- Typir module in `typir.ts`: assembles services and implementations
- It is possible to group services
- Names of services start with an uppercase letter, names of groups start with a lowercase letter
- Dependency injection (DI)


Expand Down
25 changes: 24 additions & 1 deletion documentation/kinds/custom-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Then you create a new factory for these matrix types:
```typescript
const matrixFactory = new CustomKind<MatrixType, TestLanguageNode>(typir, {
name: 'Matrix',
// ... here you can specify some optional rules for type names, conversion, sub-types, ... for all matrix types:
calculateTypeName: properties => `My${properties.width}x${properties.height}Matrix`,
// ... here you can specify additional rules for conversion, sub-types, ... for all matrix types ...
});
```

Expand All @@ -38,6 +38,28 @@ const matrix2x3 = matrixFactory

See `custom-example-restricted.test.ts` for another application example.

In order to provide the matrix factory like the other predefined factories for primitives, functions and so on,
read the [section about customization](../customization.md), summarized as follows:

```typescript
// define your custom factory as additional Typir service
type AdditionalMatrixTypirServices = {
readonly factory: {
readonly Matrix: CustomKind<MatrixType, TestLanguageNode>;
}
}

// specify the additional services as TypeScript generic when initializing the Typir services and provide the custom factory
const typir = createTypirServicesWithAdditionalServices<TestLanguageNode, AdditionalMatrixTypirServices>({
factory: {
Matrix: services => new CustomKind<MatrixType, TestLanguageNode>(services, { ... })
}
});

// now the custom matrix factory is usable like the predefined factories
typir.factory.Matrix.create({ ... }).finish().getTypeFinal()!;
```


## Features

Expand Down Expand Up @@ -90,5 +112,6 @@ See `custom-independent.test.ts` for an example.

- You cannot use simple string values for `TypeSelector`s (in order to specify custom properties of type `Type`), since they cannot be distinguished from string values for primitive custom properties.
Therefore, only the restricted `TypeSelectorForCustomTypes` is supported by custom types instead of the usual `TypeSelector`.
As a workaround for the identifier `'MyIdentifier'`, use `() => 'MyIdentifier'` instead.
- Even if your custom type does not depend on other types or if you know, that the types your custom type depends on are already available,
you need to call `getTypeFinal()`, e.g. `const myCustomType = customKind.create({...}).finish().getTypeFinal()!;`.
6 changes: 3 additions & 3 deletions packages/typir-langium/src/typir-langium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { LangiumAstTypes } from './utils/typir-langium-utils.js';
*/
export type TypirLangiumAddedServices<AstTypes extends LangiumAstTypes> = {
readonly Inference: LangiumTypeInferenceCollector<AstTypes>; // concretizes the TypeInferenceCollector for Langium
readonly langium: { // new services which are specific for Langium
readonly LangiumServices: LangiumSharedCoreServices; // store the Langium services to make them usable for all Typir services
readonly langium: { // all new services which are specific for Langium
readonly LangiumServices: LangiumSharedCoreServices; // store the Langium services to make them available for all Typir services
readonly TypeCreator: LangiumTypeCreator;
readonly TypeSystemDefinition: LangiumTypeSystemDefinition<AstTypes>;
};
Expand Down Expand Up @@ -121,7 +121,7 @@ export function createTypirLangiumServices<AstTypes extends LangiumAstTypes>(
* @param langiumServices Typir-Langium needs to interact with the Langium lifecycle
* @param reflection Typir-Langium needs to know the existing AstNode$.types in order to do some performance optimizations
* @param typeSystemDefinition the actual definition of the type system
* @param moduleForAdditionalServices contains configurations for all added services
* @param moduleForAdditionalServices contains the configurations for all added services
* @param customization1 some optional customizations of the Typir-Langium and Typir(-core) services, e.g. for production
* @param customization2 some optional customizations of the Typir-Langium and Typir(-core) services, e.g. for testing
* @param customization3 some optional customizations of the Typir-Langium and Typir(-core) services, e.g. for testing
Expand Down
4 changes: 2 additions & 2 deletions packages/typir-langium/src/utils/typir-langium-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export async function deleteAllDocuments(services: LangiumSharedCoreServices) {
.map((x) => x.uri)
.toArray();
await services.workspace.DocumentBuilder.update(
[], // update no documents
docsToDelete // delete all documents
[], // update no documents, but ...
docsToDelete // delete all documents
);
}

Expand Down