Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f5eef85
make LanguageKeys type-safe
JohannesMeierSE Aug 14, 2025
ea6f410
exploit Specifics['LanguageKeys'] to simplify inference rules for fun…
JohannesMeierSE Aug 15, 2025
8e305f0
improved the changelog, wrote documentation
JohannesMeierSE Sep 1, 2025
65d6bf8
refactorings: moved existing definitions into another file, used exis…
JohannesMeierSE Sep 1, 2025
7a9db69
some renamings to make names more clear
JohannesMeierSE Sep 1, 2025
6587704
refactoring: moved definitions into another file
JohannesMeierSE Sep 1, 2025
7ca4027
renamed internal <TypeType> generics
JohannesMeierSE Sep 1, 2025
66edf95
investigated a strange TSC issue
JohannesMeierSE Sep 1, 2025
4c10c76
`languageProperty` supports only valid property names of the given `l…
JohannesMeierSE Sep 4, 2025
82148ac
fixes according to the review
JohannesMeierSE Feb 11, 2026
a5e7ea1
calculate union of language types for multiple language keys (as deve…
JohannesMeierSE Feb 11, 2026
4cf9897
made registration options more explicit (result of joint discussion w…
JohannesMeierSE Feb 11, 2026
f915b86
make the properties of language nodes, which shall be hidden, customi…
JohannesMeierSE Feb 13, 2026
2fc4542
fixed watch script
JohannesMeierSE Feb 13, 2026
051bb92
documented the case, if there is no list of concrete language keys
JohannesMeierSE Feb 13, 2026
bea149b
Provide `addValidationRulesForLanguageNodes` in core Typir
JohannesMeierSE Feb 13, 2026
f0a419e
Provide `addInferenceRulesForLanguageNodes` in core Typir
JohannesMeierSE Feb 13, 2026
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
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,29 @@ Note that the versions "0.x.0" probably will include breaking changes.
For each minor and major version, there is a corresponding [milestone on GitHub](https://github.com/TypeFox/typir/milestones).



## v0.4.0 (2025-??-??)

[Linked issues and PRs for v0.4.0](https://github.com/TypeFox/typir/milestone/5)

### New features

- Introduced `TypirSpecifics['LanguageKeys']` (in `typir.ts`) to make the available language nodes with their keys and TypeScript types explicit:
- By default, Typir predefines neither language nodes nor language keys in advance, i.e. any `string` values are usable as language key.
- By default, Typir-Langium supports exactly the language nodes and language types, which are derived from the grammar and generated by Langium into the `ast.ts`. Looking into the LOX example, `LoxAstType` inside `examples/lox/src/language/generated/ast.ts` lists all language keys (on the left of colon) and maps them to the (TypeScript interfaces/types of the) language nodes (on the right of the colon).
- When restricting the possible language keys, the TypeScript compiler shows errors, if invalid language keys are used. This happens, among others, inside inference rules or for registering validation rules. This improves type safety on TypeScript level for developers applying Typir.
- If language keys are restricted, inside an inference rule with a value for `languageKey` and without a value for `filter`, it is possible now to skip the expected TypeScript type for the input node of the `matching` property, as demonstrated in the updated examples for (L)OX. This improves the usability and type-safety of the API.
- When reporting validation issues, `languageProperty` accepts only valid property names of the given `languageNode`.

### Breaking changes

- Renamed `TypirLangiumSpecifics['AstTypes']` to `TypirLangiumSpecifics['LanguageKeys']` to align it with the new `TypirSpecifics['LanguageKeys']`, as described above (#93)

### Fixed bugs

-



## v0.3.3 (2026-02-10)

Expand All @@ -23,13 +36,15 @@ For each minor and major version, there is a corresponding [milestone on GitHub]
- Updated Typir-Langium to Langium v4.2.0 (#103).



## v0.3.2 (2026-01-13)

### Fixed bugs

- Use browser-safe `isSet` and `isMap` implementation to fix #96 (#98, #97).



## v0.3.1 (2025-11-27)

### New features
Expand All @@ -43,6 +58,7 @@ For each minor and major version, there is a corresponding [milestone on GitHub]
- When checking the equality of custom types, the values for the same property might have different TypeScript types, since optional properties might be set to `undefined` (#94).



## v0.3.0 (2025-08-15)

[Linked issues and PRs for v0.3.0](https://github.com/TypeFox/typir/milestone/4)
Expand Down Expand Up @@ -74,7 +90,6 @@ For each minor and major version, there is a corresponding [milestone on GitHub]
LanguageType: unknown;
}
```

- `TypirLangiumSpecifics` extends the Typir specifics for Langium, concretizes the language type and enables to register the available AST types of the current Langium grammar as `AstTypes`:

```typescript
Expand Down Expand Up @@ -106,18 +121,21 @@ For each minor and major version, there is a corresponding [milestone on GitHub]
- Fixed the implementation for merging modules for dependency injection (DI), it is exactly the same fix from [Langium](https://github.com/eclipse-langium/langium/pull/1939), since we reused its DI implementation (#79).



## v0.2.2 (2025-08-01)

- Fixed wrong imports of `assertUnreachable` (#86)
- Copy instead of reuse arrays with language keys to prevent side effects (#87)
- Updated Typir-Langium to Langium v3.5 (#88)



## v0.2.1 (2025-04-09)

- Export `test-utils.ts` which are using `vitest` via the new namespace `'typir/test'` in order to not pollute production code with vitest dependencies (#68)



## v0.2.0 (2025-03-31)

[Linked issues and PRs for v0.2.0](https://github.com/TypeFox/typir/milestone/3)
Expand Down Expand Up @@ -174,19 +192,22 @@ For each minor and major version, there is a corresponding [milestone on GitHub]
- The inference logic in case of zero arguments (e.g. for function calls or class literals) was not accurate enough (#64).



## v0.1.2 (2024-12-20)

- Replaced absolute paths in READMEs by relative paths, which is a requirement for correct links on NPM
- Edit: Note that the tag for this release was accidentally added on the branch `jm/v0.1.2`, not on the `main` branch.



## v0.1.1 (2024-12-20)

- Improved the READMEs in the packages `typir` and `typir-langium`.
- Improved the CONTRIBUTING.md.
- Improved source code for Tiny Typir in `api-example.test.ts`.



## v0.1.0 (2024-12-20)

This is the first official release of Typir.
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ The roadmap includes, among other, these features:

- More predefined types: structurally typed classes, lambdas, generics, constrained primitive types (e.g. numbers with upper and lower bound), ...
- Calculate types, e.g. operators whose return types depend on their current input types
- Simplified API for custom types

For the released versions of Typir, see the [CHANGELOG.md](./CHANGELOG.md).

Expand Down
6 changes: 6 additions & 0 deletions documentation/bindings/binding-langium.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ Typir-Langium is a dedicated binding of Typir for languages and DSLs which are d
the language workbench for developing textual domain-specific languages (DSLs) in the web.

TODO

## Validation

All properties of usual diagnostics in Langium (as defined in `DiagnosticInfo`) are supported, when creating validation issues in Typir-Langiums.
This enables, among other use cases, to register code actions for type-related validation issues (see `lox-code-actions.ts` for an example).
Note that `node`, `property` and `index` are renamed to `languageNode`, `languageProperty` and `languageIndex` to be in sync with Typir core.
3 changes: 2 additions & 1 deletion documentation/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ As described in the [design section](./design.md), nearly all features of Typir
for which Typir provides classes implementing these interfaces as default implementations. These interfaces and implementations are composed in ...

- `typir.ts` for Typir (core)
- `typir-langium.ts` for Typir-Langium
- `typir-langium.ts` for Typir-Langium, reusing and adjusting the Typir core services

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

Expand All @@ -28,6 +28,7 @@ const customizedTypir = createTypirServices({
});
```


## Add additional services

Additional services need to be explicitly specified.
Expand Down
81 changes: 63 additions & 18 deletions documentation/design.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Design

This describes the main design principles of Typir.
This describes the main design principles of and the terminology used by Typir.

## Core principles

### Type
## Type

Each type exists only once in a Typir instance. Types at runtime are instances of a sub-class of the TypeScript class `Type`.
Two different instances of `Type` represent two different types in Typir.

All types need to have *unique identifiers* in order to identifier duplicate types and to access types by their identifier.
If Typir reports errors regarding non-unique identifiers, check the following possibles reasons for colliding identifiers:
Expand All @@ -14,37 +16,80 @@ If Typir reports errors regarding non-unique identifiers, check the following po

Types also have a *name*, which is used as a short name for types, e.g. used to be shown in error messages to users. Names don't need to be unique.

TODO:
TODO: states/lifecycle of a type

Each type has exactly one kind, as explained below.


- single instances
- kind
## Kind / Factory

### Kind

### Type graph
## Type graph

Each type system, i.e. each instance of the `TypirServices`, has one type graph:
Each type system, i.e. each instance of the `TypirServices`, has one type graph, which stores the available types and their relationships:

- nodes are types, e.g. primitive types and function types
- edges are relationships between types, e.g. edges representing implicit conversion between two types

### Incrementality (under construction)

## Incrementality (under construction)

- add/remove types
- add/remove rules and relationships

### Services and default implementations

## Language

Usually, type systems are created to do some type checking on textual languages, including domain-specific languages (DSLs) and general-purpose programming languages. Programs respective text conforming to these languages are parsed and provided as abstract syntax trees (ASTs) in-memory.
ASTs usually consist of a tree of nodes (realized as JavaScript objects at runtime), which represent a small part of the program/text after parsing.
During linking, cross-references between the nodes of the tree are established, i.e. the tree becomes a graph.
Type checking is done on these ASTs.

### Language node

Since Typir has no preconditions regarding the structure of the AST or the technical details of the AST nodes in order to provide type checking for any data structure,
the term *language node* is used to describe a single node in the AST or a single element in a complex data structure.
As an example, in the context of Langium each `AstNode` is a language node in Typir.

While the definition of types and their relationships is independent from the AST,
type inference and validations are done on language nodes,
e.g. an inference rule gets a language node as input and returns its inferred Typir type.
All information Typir needs to know about language nodes is specified in the APIs, including the APIs for inference rules, validations rules and the [language service](./services/language.md).

### Language type

The TypeScript type of a language node is called *language type*.

### Language key

Each language node might have a *language key*.
Language keys are `string` values and are used to increase performance by registering rules for inference and validation not for all language nodes,
but only for language nodes with a particular language key.
Rules associated to no language key are applied to all language nodes.
Rules might be associated to multiple language keys.
Getting the language key of a language node is done by the [language service](./services/language.md).
The available language keys could be restricted by customizing the specifics of your language in this way:

```typescript
export type MyAstTypes = {
LanguageKey1: LanguageType1;
LanguageKey2: LanguageType2;
// ...
}

export interface MySpecifics extends TypirSpecifics {
LanguageKeys: MyAstTypes;
}
```


## Services and default implementations

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


## Terminology / Glossary

- inference: inference rule, type inference
- language node, language key
- ...
- cyclic dependencies
- compile time vs runtime
2 changes: 1 addition & 1 deletion documentation/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ After creating the Langium services (which contain the Typir serivces now) and s

```typescript
export interface MyDSLSpecifics extends TypirLangiumSpecifics {
AstTypes: MyDSLAstType; // all AST types from the generated `ast.ts`
LanguageKeys: MyDSLAstType; // all AST types from the generated `ast.ts`
// ... more could be customized here ...
}
```
Expand Down
1 change: 1 addition & 0 deletions documentation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This describes the structure and the main content of the documentation for Typir
- [Assignability](./services/assignability.md)
- [Language](./services/language.md): Don't interchange "language service" and "language server"!
- [Type inference](./services/inference.md)
- [Validation](./services/validation.md)
- ...


Expand Down
2 changes: 1 addition & 1 deletion documentation/services/inference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Type inference infers a Typir type for a given language node, i.e. it answers the question, which Tyir type has a language node.
Therefore type inference is the central part which connects the type system and its type graph with an AST consisting of language nodes.
These relationships are defined with *inference rules*, which identify the type for some language nodes.
These relationships are defined with *inference rules*, which identify the type for a given language node.


## API
Expand Down
2 changes: 1 addition & 1 deletion documentation/services/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ these rules are applied only to those language nodes which have this language ke
It is possible to associate rules to multiple language keys.
Rules which are associated to no language key, are applied to all language nodes.

Language keys are represented by string values and might be depending on the DSL implementation/language workbench,
Language keys are represented by `string` values and might be depending on the DSL implementation/language workbench,
class names or `$type`-property-information of the language node implementations.
Language keys might have sub/super language keys ("sub-type relationship of language keys").

Expand Down
89 changes: 89 additions & 0 deletions documentation/services/validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Validation

Typir provides some services and concepts, to create validation checks, which check some type-related constraints on language nodes.

## API

### Validation rules

Validation rules are single checks, which are executed on a given language node and result in an arbitrary number of validation issues.
Simple validation rules are realized as TypeScript functions:

```typescript
type ValidationRuleFunctional = (languageNode: LanguageType, accept: ValidationProblemAcceptor, typir: TypirServices) => void;
```

The given `languageNode` is the starting point for doing some type-related checks on the AST.
Found validation issues are not returned, but reported to the `ValidationProblemAcceptor` by calling it with `accept({ ... })`.
The properties to specify in the given object are described in the next section.

To realize more advanced checks in more performant way, there is also `ValidationRuleLifecycle`.

### Validation collector

The `ValidationCollector` is the central place for managing the validation.
Validation rules are registered at and collected by the validation collector with `typir.validation.Collector.addValidationRule(rule, { ... })`.
Some options might be given in the options object as second argument:

- `boundToType`: If the given type is removed from the type system, this rule will be automatically removed as well.
- `languageKey`: By default, all validation rules are performed for all language nodes.
In order to improve performance, validation rules with a given language key are executed only for language nodes with this language key.

The call `const issues: ValidationProblem = typir.validation.Collector.validate(languageNode)` validates a language node
by executing all validation rules which are applicable to the given language node and returns all found validation issues.
Since Typir doesn't know the structure of the AST, there is *no* automatic traversal of the AST, i.e. *only* the given language node is validated.

Bindings of Typir for concrete language workbenches might behave differently,
e.g. Typir-Langium hooks into the regular validation mechanisms of Langium.
Therefore neither direct calls of `validate()` nor traversals of the Langium AST are required.

### Validation issues

When reporting some issues, different information can be reported.
All values are put into an object representing the validation issue.
This validation issue object is given to the validation problem acceptor, e.g.

```typescript
accept({
severity: 'error',
message: 'An error occurred',
languageNode: myCheckedNode,
// ...
});
```

The following properties are supported by default by Typir (core):

* The `severity` describes, how critical the found issue is, e.g. whether its an error or only a hint.
* The `message` is some text to describe the issue in a human-readable way.
* Optionally, `subProblems` allows to attach some sub-problems, which might give some more details or reasons for the reported validation issue.
* The `languageNode` can be used to specify, where in the validation issue occurred in the validated AST. This "source of the issue" might be different than the language node which was given as input to the validation rule.
* A `languageProperty` can be specified only, if the `languageNode` is specified, and marks a property as more fine-grained source of the issue.
* The `languageIndex` makes only sense, if the `languageProperty` is specified, and gives even more details for the source of the issue.

The available properties can be customized via `TypirSpecifics['ValidationMessageProperties']`, which is useful for supporting new language workbenches.
Don't forget to store or apply the values for the customized properties,
which requires some more customizations when postprocessing the validtion issues returned by Typir.
As an example, Typir-Langium provides some properties for validation issues, which are specific for Langium.

### Predefined constraints

To simplify the checking and creating of validation issues,
the `ValidationConstraints` service available via `typir.validation.Constraints` provides some constraints as short-cuts for recurring validation checks,
which can be used inside validation rules.

As an example, if you have a `node` which represents a `VariableDeclaration`, you could validate, whether the given initial `value` is assignable to the declared `type` of the variable in this way:

```typescript
typir.validation.Constraints.ensureNodeIsAssignable(
node.value, // the initial value, its Typir type is inferred internally
node.type, // the declared (language) type, the corresponding Typir type is inferred internally
accept, // the validation acceptor
(actual, expected) => ({ // callback to create a meaningful validation issue, if the value does not fit to the type
message: `The initial value of type '${actual.name}' is not assignable to '${node.name}' of type '${expected.name}'.`,
// more properties might be specified
})
);
```

See (L)OX for some more examples.
Loading