Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
42 changes: 42 additions & 0 deletions hugo/content/docs/learn/workflow/resolve_cross_references.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,45 @@ expect(model.greetings[1].person.ref).toBe(model.persons[1]);
```

The `expect` function can be any assertion library you like. The `Hello world` example uses Vitest.

## Handling multi-target references

With [Langium 4.0.0](https://github.com/eclipse-langium/langium/blob/main/packages/langium/CHANGELOG.md#multi-target-references) the handling of multi-target references is supported.

Multi-target references are cross-references that can point to multiple targets instead of just one. The AST representation changes from a single `Reference` to an `MultiReference` objects. The mechanism to resolve multi-target references is similar to single-target references. The scope provider still provides a scope for each cross-reference, but the linker now can link multiple targets from the scope to the multi-reference.

All you need to do to change a single-target reference to a multi-target reference is to write:

```langium
... person=[+Person:ID] ...
```

...instead of `person=[Person:ID]` in your grammar.

The resolution mechanism stays the same. But be aware of the AST structure change: every `...ast.person.ref` becomes an array of references: `...ast.person.items` where each `item` is a `Reference`-like object (it has a `ref` field again).

### When to use multi-target references?

A typical use case for multi-target references are namespaces or partial classes. The core idea is that you want to declare multiple symbols under the same scope. Typically, these declarations are split into different files. Think of C++ `std` namespace:

```cpp
//vector.hpp
namespace std {
class vector { ... };
}

//map.hpp
namespace std {
class map { ... };
}

//main.cpp
#include <vector>
#include <map>
std::vector<int> v;
std::map<int, int> m;
...
```

As you can see, the `std` namespace is used to group related classes together, allowing for better organization and avoiding naming conflicts. In this case, both `vector` and `map` are part of the `std` namespace, and they can be used in `main.cpp` without any issues.
The multi-target reference would be used inside of the variable declarations of `v` and `m` to point to the same `std` namespace declarations.
28 changes: 28 additions & 0 deletions hugo/content/docs/reference/grammar-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,35 @@ Hello Sara !
```
will result in an error message since the cross reference resolution will fail because a `Person` object with the name 'Sara' has not been defined, even though 'Sara' is a valid `ID`.

##### Multi-Target Cross-References

Some language concepts are able to be defined multiple times, for example namespaces or partial classes - symbols that are the same, but are normally distributed over multiple files or locations in the same file. In such cases, it is useful to be able to reference all definitions of such a symbol instead of just one.

Langium supports multi-target cross-references by using the `[+Type:ID]` syntax:

```langium
entry Model:
(persons+=Person | greetings+=Greeting)*;
Person:
'person' name=ID;
Greeting:
'Hello' person=[+Person:ID] '!';
```

Given the following file content you will get two `Person` objects created, both named "Bob", and a `Greeting` object that references both of them:

```plain
person Bob
person Bob
Hello Bob !
```

Programmatically, the `person` property of the `Greeting` object will be an array of references to both `Person` objects `greeting.person.items[0].ref` and `greeting.person.items[1].ref`.

Resolution-wise, single-target and multi-target cross-references behave the same: if no matching target is found, a diagnostic error is reported. The resolution itself uses the `ScopeProvider` and the `ScopeComputation` services, which can be customized as needed.

#### Unassigned Rule Calls

Parser rules do not necessarily need to create an object, they can also refer to other parser rules which in turn will be responsible for returning the object.
For example, in the [Arithmetics example](https://github.com/eclipse-langium/langium/blob/main/examples/arithmetics/src/language-server/arithmetics.langium):
```langium
Expand Down