Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi-target references #1509

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

msujew
Copy link
Member

@msujew msujew commented May 23, 2024

Adds support for multi-target references. These are references that have the possibility to target multiple elements at once. See following illustration for an example:

image

Note that by default, only elements that are defined on the same scope can be targeted. I.e. a multi reference as in the following will only target one element:

image

This behavior can be freely overwritten by adopters of Langium. However, due to the inherent complexity of aligning the References service implementation and the scoping wrt their behavior, this feature is intended for proficient Langium developers.

The intended use cases for this feature are stuff like declaration merging in TypeScript or partial classes in C#.

Contains a bunch of breaking changes:

  • The whole References interface now returns arrays instead of T | undefined.
  • Every service/interface/class that works with Reference in a generic manner (linker mostly) now also works with MultiReference.
  • LinkingError no longer inherits ReferenceInfo (which didn't make a lot of sense in the first place), but contains a info property.

@msujew msujew added the linking Linking related issue label May 23, 2024
@msujew
Copy link
Member Author

msujew commented May 23, 2024

cc @cdietrich since you were interested in this feature.

@spoenemann spoenemann self-requested a review May 23, 2024 15:00
Copy link
Contributor

@Lotes Lotes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good, I like especially that one can now ask for an array and do not need to check for undefined when getting references :D.
All in all I added some code improvement suggestions.
I would suggest to wait for more feedback, since I am not so deep in this area of the code, yet.

const document = await parse(text);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const model = document.parseResult.value as any;
const alice2 = model.persons[2];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future work it would be nice to have a type instead of any.

includeDeclaration: true
}).toArray().sort((a, b) => a.segment.offset - b.segment.offset);
expect(references).toHaveLength(3);
for (let i = 0; i < references.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing, decide yourself, if you want to take it: For-loops in tests, similar to branching, make it harder to read the code. Because you have to evaluate values in your mind. But I also see that this test is quite simple. I see two options:

  1. with a references.forEach((reference, index) => ...) you remove the need to think about references[index]. And the second parameter is the index which you can reuse for the line computation.
  2. Roll out the for loop to only have expect().toBe.... lines.

expect(model.persons).toHaveLength(3);
expect(model.greetings).toHaveLength(1);
const greeting = model.greetings[0];
const ref = greeting.person as MultiReference;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Unfortunately we cannot do a expect(isMultiReference(greeting.person)).toBeTruthy()... T_T
You can add an assertion. It is not so important to me, I thought it would be great to have an error in the opposite case.

grammar test
entry Model: (persons+=Person | greetings+=Greeting)*;
Person: 'person' name=ID;
Greeting: 'hello' person=[*Person:ID];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking loud: * stands normally for "zero or more". Is it possible here to have zero references? Maybe + will lead to a better association... on the other hand it is just syntax. Maybe I get some ideas later in this review...

if (description) {
descr.push(description);
streamReferences(astNode).forEach(refInfo => {
if (!refInfo.reference.error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the diff: I would prefer a !isLinkingError(refInfo). Looks more official.

} else if (isMultiReference(reference)) {
return reference.items.map(item => item.ref);
}
return [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this line ever be reached? What do you think about assertUnreachable?

if (sourceCstNode) {
const assignment = findAssignment(sourceCstNode);
const nodeElem = sourceCstNode.astNode;
if (assignment && nodeElem) {
const reference = (nodeElem as GenericAstNode)[assignment.feature];

if (isReference(reference)) {
return reference.ref;
if (isReference(reference) || isMultiReference(reference)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I saw this double call several times now. Would it make sense to introduce something that addresses both?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or breaking the API by introducing isReference (for both; or isAnyReference), isSingleReference and isMultiReference

@msujew msujew mentioned this pull request Oct 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linking Linking related issue
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants