Skip to content

Commit 34017fd

Browse files
montymxbmsujew
andauthored
Fix isAstType checking logic for cycles (#1563)
Co-authored-by: Mark Sujew <[email protected]>
1 parent 8aa69e1 commit 34017fd

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

packages/langium/src/grammar/type-system/types-util.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,27 +231,34 @@ function findAstTypesInternal(type: PropertyType, visited: Set<PropertyType>): s
231231
}
232232

233233
export function isAstType(type: PropertyType): boolean {
234-
return isAstTypeInternal(type, new Set());
234+
return isAstTypeInternal(type, new Map());
235235
}
236236

237-
export function isAstTypeInternal(type: PropertyType, visited: Set<PropertyType>): boolean {
237+
export function isAstTypeInternal(type: PropertyType, visited: Map<PropertyType, boolean>): boolean {
238238
if (visited.has(type)) {
239-
return false;
240-
} else {
241-
visited.add(type);
239+
return visited.get(type)!;
242240
}
241+
// This is supposed to prevent infinite recursion.
242+
// Setting this to true is a pretty safe assumption.
243+
// Setting it to false might lead to false negatives for property unions.
244+
visited.set(type, true);
245+
246+
let result = false;
247+
243248
if (isPropertyUnion(type)) {
244-
return type.types.every(e => isAstTypeInternal(e, visited));
249+
result = type.types.every(e => isAstTypeInternal(e, visited));
245250
} else if (isValueType(type)) {
246251
const value = type.value;
247252
if ('type' in value) {
248-
return isAstTypeInternal(value.type, visited);
253+
result = isAstTypeInternal(value.type, visited);
249254
} else {
250255
// Is definitely an interface type
251-
return true;
256+
result = true;
252257
}
253258
}
254-
return false;
259+
260+
visited.set(type, result);
261+
return result;
255262
}
256263

257264
export function escapeQuotes(str: string, type: '"' | "'" = '"'): string {

packages/langium/test/grammar/type-system/types-util.test.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
******************************************************************************/
66

77
import { describe, expect, test } from 'vitest';
8-
import { InterfaceType, isAstType } from 'langium/grammar';
8+
import type { ValueType } from 'langium/grammar';
9+
import { InterfaceType, UnionType, isAstType } from 'langium/grammar';
910

1011
describe('isAstType', () => {
1112

@@ -74,4 +75,73 @@ describe('isAstType', () => {
7475
})).toBeFalsy();
7576
});
7677

78+
test('Should return false for primitive union type with cycles', () => {
79+
const unionType = new UnionType('Test');
80+
const valueType: ValueType = {
81+
value: unionType
82+
};
83+
unionType.type = {
84+
types: [
85+
{
86+
primitive: 'string'
87+
},
88+
valueType
89+
]
90+
};
91+
expect(isAstType(unionType.type)).toBeFalsy();
92+
});
93+
94+
test('Should return true for interface union type with cycles', () => {
95+
const unionType = new UnionType('A');
96+
const valueType: ValueType = {
97+
value: unionType
98+
};
99+
unionType.type = {
100+
types: [
101+
{
102+
value: new InterfaceType('B', true, false)
103+
},
104+
valueType
105+
]
106+
};
107+
expect(isAstType(unionType.type)).toBeTruthy();
108+
});
109+
110+
test('Should return true for nested duplicate union types', () => {
111+
const unionType1 = new UnionType('A');
112+
const unionType2 = new UnionType('B');
113+
const interfaceType1 = new InterfaceType('C', true, false);
114+
const interfaceType2 = new InterfaceType('D', true, false);
115+
unionType1.type = {
116+
types: [
117+
{
118+
value: interfaceType1
119+
},
120+
{
121+
value: unionType2
122+
}
123+
]
124+
};
125+
unionType2.type = {
126+
types: [
127+
{
128+
value: interfaceType1 // duplicate
129+
},
130+
{
131+
value: interfaceType2
132+
}
133+
]
134+
};
135+
expect(isAstType(unionType1.type)).toBeTruthy();
136+
});
137+
138+
test('Should return true for cyclic AST type', () => {
139+
const unionType = new UnionType('Test');
140+
const type: ValueType = {
141+
value: unionType
142+
};
143+
unionType.type = type;
144+
expect(isAstType(type)).toBeTruthy();
145+
});
146+
77147
});

0 commit comments

Comments
 (0)