From 38864ce8677c93191990cc8f8c04ee0ec7977d3c Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Mon, 20 May 2024 19:06:57 +0200 Subject: [PATCH 1/2] Merge inferred array types --- .../type-system/type-collector/plain-types.ts | 46 ++++++++++++++----- .../src/grammar/validation/validator.ts | 2 +- .../type-system/inferred-types.test.ts | 24 ++++++++++ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/packages/langium/src/grammar/type-system/type-collector/plain-types.ts b/packages/langium/src/grammar/type-system/type-collector/plain-types.ts index 9406cac50..49df6981c 100644 --- a/packages/langium/src/grammar/type-system/type-collector/plain-types.ts +++ b/packages/langium/src/grammar/type-system/type-collector/plain-types.ts @@ -210,18 +210,30 @@ function plainToPropertyType(type: PlainPropertyType, union: UnionType | undefin } export function mergePropertyTypes(first: PlainPropertyType, second: PlainPropertyType): PlainPropertyType { - const flattenedFirst = flattenPlainType(first); - const flattenedSecond = flattenPlainType(second); - for (const second of flattenedSecond) { - if (!includesType(flattenedFirst, second)) { - flattenedFirst.push(second); + const { union: flattenedFirstUnion, array: flattenedFirstArray } = flattenPlainType(first); + const { union: flattenedSecondUnion, array: flattenedSecondArray } = flattenPlainType(second); + for (const second of flattenedSecondUnion) { + if (!includesType(flattenedFirstUnion, second)) { + flattenedFirstUnion.push(second); } } - if (flattenedFirst.length === 1) { - return flattenedFirst[0]; + for (const second of flattenedSecondArray) { + if (!includesType(flattenedFirstArray, second)) { + flattenedFirstArray.push(second); + } + } + if (flattenedFirstArray.length > 0) { + flattenedFirstUnion.push({ + elementType: flattenedFirstArray.length === 1 ? flattenedFirstArray[0] : { + types: flattenedFirstArray + } + }); + } + if (flattenedFirstUnion.length === 1) { + return flattenedFirstUnion[0]; } else { return { - types: flattenedFirst + types: flattenedFirstUnion }; } } @@ -244,10 +256,22 @@ function typeEquals(first: PlainPropertyType, second: PlainPropertyType): boolea } } -export function flattenPlainType(type: PlainPropertyType): PlainPropertyType[] { +export function flattenPlainType(type: PlainPropertyType): { union: PlainPropertyType[], array: PlainPropertyType[] } { if (isPlainPropertyUnion(type)) { - return type.types.flatMap(e => flattenPlainType(e)); + const flattened = type.types.flatMap(e => flattenPlainType(e)); + return { + union: flattened.map(e => e.union).flat(), + array: flattened.map(e => e.array).flat() + }; + } else if (isPlainArrayType(type)) { + return { + array: flattenPlainType(type.elementType).union, + union: [] + }; } else { - return [type]; + return { + array: [], + union: [type] + }; } } diff --git a/packages/langium/src/grammar/validation/validator.ts b/packages/langium/src/grammar/validation/validator.ts index 8d1cf4b67..c8d250526 100644 --- a/packages/langium/src/grammar/validation/validator.ts +++ b/packages/langium/src/grammar/validation/validator.ts @@ -823,7 +823,7 @@ export class LangiumGrammarValidator { const flattened = flattenPlainType(plainType); let hasRef = false; let hasNonRef = false; - for (const flat of flattened) { + for (const flat of flattened.union.concat(flattened.array)) { if (isPlainReferenceType(flat)) { hasRef = true; } else if (!isPlainReferenceType(flat)) { diff --git a/packages/langium/test/grammar/type-system/inferred-types.test.ts b/packages/langium/test/grammar/type-system/inferred-types.test.ts index 337123f66..7c5df5c5f 100644 --- a/packages/langium/test/grammar/type-system/inferred-types.test.ts +++ b/packages/langium/test/grammar/type-system/inferred-types.test.ts @@ -798,6 +798,30 @@ describe('types of `$container` and `$type` are correct', () => { } `); }); + + test('Should merge types of array properties', async () => { + const expected = expandToString` + export interface A extends AstNode { + readonly $type: 'A'; + values: Array; + } + `; + await expectTypes(` + A: values+=ID values+=NUMBER; + terminal ID returns string: /string/; + terminal NUMBER returns number: /number/; + `, expected); + await expectTypes(` + A: (values+=ID | values+=NUMBER)+; + terminal ID returns string: /string/; + terminal NUMBER returns number: /number/; + `, expected); + await expectTypes(` + A: values+=(ID | NUMBER)+; + terminal ID returns string: /string/; + terminal NUMBER returns number: /number/; + `, expected); + }); }); // https://github.com/eclipse-langium/langium/issues/744 From 9a4f366888e0d60cc9faf3686a29b087d780ef93 Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Wed, 5 Jun 2024 11:58:13 +0000 Subject: [PATCH 2/2] Review comments --- .../type-system/type-collector/plain-types.ts | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/langium/src/grammar/type-system/type-collector/plain-types.ts b/packages/langium/src/grammar/type-system/type-collector/plain-types.ts index 49df6981c..05eea5504 100644 --- a/packages/langium/src/grammar/type-system/type-collector/plain-types.ts +++ b/packages/langium/src/grammar/type-system/type-collector/plain-types.ts @@ -212,32 +212,34 @@ function plainToPropertyType(type: PlainPropertyType, union: UnionType | undefin export function mergePropertyTypes(first: PlainPropertyType, second: PlainPropertyType): PlainPropertyType { const { union: flattenedFirstUnion, array: flattenedFirstArray } = flattenPlainType(first); const { union: flattenedSecondUnion, array: flattenedSecondArray } = flattenPlainType(second); - for (const second of flattenedSecondUnion) { - if (!includesType(flattenedFirstUnion, second)) { - flattenedFirstUnion.push(second); - } - } - for (const second of flattenedSecondArray) { - if (!includesType(flattenedFirstArray, second)) { - flattenedFirstArray.push(second); - } - } - if (flattenedFirstArray.length > 0) { - flattenedFirstUnion.push({ - elementType: flattenedFirstArray.length === 1 ? flattenedFirstArray[0] : { - types: flattenedFirstArray + const flattenedUnion = mergeTypeUnion(flattenedFirstUnion, flattenedSecondUnion); + const flattenedArray = mergeTypeUnion(flattenedFirstArray, flattenedSecondArray); + if (flattenedArray.length > 0) { + flattenedUnion.push({ + elementType: flattenedArray.length === 1 ? flattenedArray[0] : { + types: flattenedArray } }); } - if (flattenedFirstUnion.length === 1) { - return flattenedFirstUnion[0]; + if (flattenedUnion.length === 1) { + return flattenedUnion[0]; } else { return { - types: flattenedFirstUnion + types: flattenedUnion }; } } +function mergeTypeUnion(first: PlainPropertyType[], second: PlainPropertyType[]): PlainPropertyType[] { + const result = [...first]; + for (const type of second) { + if (!includesType(result, type)) { + result.push(type); + } + } + return result; +} + function includesType(list: PlainPropertyType[], value: PlainPropertyType): boolean { return list.some(e => typeEquals(e, value)); }