From c3319441c3eb3f6b98826990c91682029b1acc12 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Wed, 8 Jun 2022 17:36:13 +0200 Subject: [PATCH 1/7] big mess workspace, but leaving for thoughts --- .../src/grammar/langium-grammar-validator.ts | 25 +++++++++ .../src/grammar/type-system/type-collector.ts | 1 - .../src/grammar/type-system/type-validator.ts | 51 +++++++++++++++++++ .../type-system/type-validator.test.ts | 24 +++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/packages/langium/src/grammar/langium-grammar-validator.ts b/packages/langium/src/grammar/langium-grammar-validator.ts index 076c39cdb..8f80328bc 100644 --- a/packages/langium/src/grammar/langium-grammar-validator.ts +++ b/packages/langium/src/grammar/langium-grammar-validator.ts @@ -409,13 +409,38 @@ export class LangiumGrammarValidator { accept('error', 'Rules are not allowed to return union types.', { node: rule, property: 'returnType' }); } } + + //const astResources = collectAllAstResources([grammar]); + //const inferredTypes = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); + + // Okay, this doesn't work because I don't yet have the information to determine that this interface is a problem... + for (const interfaceType of grammar.interfaces) { interfaceType.superTypes.forEach((superType, i) => { if (superType.ref && ast.isType(superType.ref)) { accept('error', 'Interfaces cannot extend union types.', { node: interfaceType, property: 'superTypes', index: i }); } + // TODO: Could is be a solution to actually disallow extending from things that are NOT interfaces directly... + // i.e., interfaces build off of other concrete interfaces. That would improve the base reasoning, and make things a lot simpler... + // + else if(superType.ref && !ast.isInterface(superType.ref)) { + accept('error', 'Interfaces cannot extend parser rules.', { node: interfaceType, property: 'superTypes', index: i }); + } + // + // else if(superType.ref && ast.isParserRule(superType.ref) && ast.isType(superType.ref.inferredType)) { + // // thinking of looking here instead, since this process is tiresome... + // console.info('>>>> Super type ref not defined for ....>>>' + interfaceType.name); + // console.info(superType.ref?.inferredType); + // accept('error', 'Interfaces cannot extend union types!!!!', { node: interfaceType, property: 'superTypes', index: i }); + // } }); } + + // TEMPORARY, not a good idea in the long run + //const astResources = collectAllAstResources([grammar], this.documents); + //const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); + // ... + //inferred.unions } checkActionTypeUnions(action: ast.Action, accept: ValidationAcceptor): void { diff --git a/packages/langium/src/grammar/type-system/type-collector.ts b/packages/langium/src/grammar/type-system/type-collector.ts index 46e9699ba..9572cd207 100644 --- a/packages/langium/src/grammar/type-system/type-collector.ts +++ b/packages/langium/src/grammar/type-system/type-collector.ts @@ -20,7 +20,6 @@ export function collectAst(documents: LangiumDocuments, grammars: Grammar[]): As const astResources = collectAllAstResources(grammars, documents); const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); const declared = collectDeclaredTypes(Array.from(astResources.interfaces), Array.from(astResources.types), inferred); - const interfaces: InterfaceType[] = inferred.interfaces.concat(declared.interfaces); const types: UnionType[] = inferred.unions.concat(declared.unions); diff --git a/packages/langium/src/grammar/type-system/type-validator.ts b/packages/langium/src/grammar/type-system/type-validator.ts index e42217b4e..63dc0ae84 100644 --- a/packages/langium/src/grammar/type-system/type-validator.ts +++ b/packages/langium/src/grammar/type-system/type-validator.ts @@ -40,6 +40,7 @@ export function validateTypesConsistency(grammar: Grammar, accept: ValidationAcc } const validationResources = collectValidationResources(grammar); + for (const [typeName, typeInfo] of validationResources.entries()) { if (!isInferredAndDeclared(typeInfo)) continue; const errorToRuleNodes = applyErrorToRuleNodes(typeInfo.nodes, typeName); @@ -61,6 +62,51 @@ export function validateTypesConsistency(grammar: Grammar, accept: ValidationAcc ); } } + + // TODO around here is where I need to validate the interfaces that are using inferred type information... + // The data coming back from 'collectValidationResources' is only declared, and does not suffice. + // >>>>> This is where I should be doing further validation. Right after the validation of type consistency, I need to generate inferred types + // and THEN check all interfaces, and verify their superTypes are solid (inferred or NOT) + // the one thing that makes me hesitate is that we already have a superType check, but it's just for whether inferred matches declared...that's it + // cool, so after this do the change + /// >>>>>>> + + // DUPLICATED FROM TYPE COLLECTOR FOR TESTING + // const astResources = collectAllAstResources([grammar]); + // const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); + // //const declared = collectDeclaredTypes(Array.from(astResources.interfaces), Array.from(astResources.types), inferred); + // //const interfaces: InterfaceType[] = inferred.interfaces.concat(declared.interfaces); + // //const types: UnionType[] = inferred.unions.concat(declared.unions); + + // const typeNameToRules = getTypeNameToRules(astResources); + // const inferredInfo = mergeTypesAndInterfaces(inferred) + // .reduce((acc, type) => acc.set(type.name, { inferred: type, nodes: typeNameToRules.get(type.name) }), + // new Map() + // ); + + // // for every interface + // // verify that each super types is NOT a union type (isType) + // // if it is, report the error that 'Interfaces cannot extend union types' + + // for(const [,i] of inferredInfo.entries()) { + + // // for(const node of i.nodes) { + // // if(isInterface(node.)) { + + // // } + // // } + + // if(inferred.unions.some(unionType => i.inferred.superTypes.has(unionType.name) && isInterface(i.inferred))) { + // // TODO index 0 is not correct + // accept('error', 'Interfaces cannot extend union types.', { node: i.nodes[0], property: 'inferredType', index: 0 }); + // // accept('error', ``, + // // { node: typeInfo.node, property: 'name' } + // // ); + // } + // } + + //const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); + //inferred.unions; } export function applyErrorToAssignment(nodes: readonly ParserRule[], accept: ValidationAcceptor): (propertyName: string, errorMessage: string) => void { @@ -106,6 +152,11 @@ export function collectValidationResources(grammar: Grammar): ValidationResource const astResources = collectAllAstResources([grammar]); const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); const declared = collectDeclaredTypes(Array.from(astResources.interfaces), Array.from(astResources.types), inferred); + // ^^^ This section above is exactly what I want in terms of getting access for validation, both inferred & declared types together + // just missing the type collector to get the resources I need combined... + + // TODO 'inferred' has the union data I need to extending is done correctly... + // This seems like the right kind of information, but the spot needs to be just above this function (separate comment) const typeNameToRules = getTypeNameToRules(astResources); const inferredInfo = mergeTypesAndInterfaces(inferred) diff --git a/packages/langium/test/grammar/type-system/type-validator.test.ts b/packages/langium/test/grammar/type-system/type-validator.test.ts index c287b4c79..11bbe3f6d 100644 --- a/packages/langium/test/grammar/type-system/type-validator.test.ts +++ b/packages/langium/test/grammar/type-system/type-validator.test.ts @@ -95,3 +95,27 @@ describe('validate inferred types', () => { }); }); + +// Helps enforce how inferred types can be used +describe('valid proper usage of inferred types', () => { + + test('interface cannot extend inferred union type', async () => { + const prog = ` + Statement: S1 | S2; + S1: 's1' s1=ID; + S2: 's2' s2=ID; + interface Def extends Statement {} + hidden terminal WS: /\\s+/; + terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/; + `.trim(); + + const document = await parseDocument(grammarServices, prog); + const diagnostics: Diagnostic[] = await grammarServices.validation.DocumentValidator.validateDocument(document); + expect(diagnostics.filter(d => d.severity === DiagnosticSeverity.Error)).toHaveLength(1); + + // verify the location of the single diagnostic error, should be only for the 2nd rule + // const d = diagnostics[0]; + // expect(d.range.start).toEqual({character: 8, line: 5}); + // expect(d.range.end).toEqual({character: 34, line: 5}); + }); +}); From 0f6d9099e15239b3c557eb7857352cc6357ee6fd Mon Sep 17 00:00:00 2001 From: "Benjamin F. Wilson" Date: Fri, 17 Jun 2022 14:03:30 +0200 Subject: [PATCH 2/7] adds warning on extending parser rule, error if it infers a type union --- .../src/grammar/langium-grammar-validator.ts | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/packages/langium/src/grammar/langium-grammar-validator.ts b/packages/langium/src/grammar/langium-grammar-validator.ts index 681402f47..57dd11888 100644 --- a/packages/langium/src/grammar/langium-grammar-validator.ts +++ b/packages/langium/src/grammar/langium-grammar-validator.ts @@ -21,6 +21,7 @@ import * as ast from './generated/ast'; import { isParserRule, isRuleCall } from './generated/ast'; import { findKeywordNode, findNameAssignment, getEntryRule, getTypeName, isDataTypeRule, isOptional, resolveImport, resolveTransitiveImports, terminalRegex } from './grammar-util'; import type { LangiumGrammarServices } from './langium-grammar-module'; +import { collectInferredTypes } from './type-system/inferred-types'; import { applyErrorToAssignment, collectAllInterfaces, InterfaceInfo, validateTypesConsistency } from './type-system/type-validator'; export class LangiumGrammarValidationRegistry extends ValidationRegistry { @@ -416,37 +417,24 @@ export class LangiumGrammarValidator { } } - //const astResources = collectAllAstResources([grammar]); - //const inferredTypes = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); - - // Okay, this doesn't work because I don't yet have the information to determine that this interface is a problem... - for (const interfaceType of grammar.interfaces) { interfaceType.superTypes.forEach((superType, i) => { if (superType.ref && ast.isType(superType.ref)) { accept('error', 'Interfaces cannot extend union types.', { node: interfaceType, property: 'superTypes', index: i }); + } else if(superType.ref && ast.isParserRule(superType.ref)) { + // collect just the beginning of whatever inferred types this standalone rule produces + // looking to exclude anything that would be a union down the line + const inferred = collectInferredTypes([superType.ref as ast.ParserRule], []); + if(inferred.unions.length > 0) { + // inferred union type also cannot be extended + accept('error', `An interface cannot extend a union type, which was inferred from parser rule ${superType.ref.name}.`, { node: interfaceType, property: 'superTypes', index: i }); + } else { + // otherwise we'll allow it, but issue a warning against basing declared off of inferred types + accept('warning', 'Extending an interface by a parser rule gives an ambiguous type, instead of the expected declared type.', { node: interfaceType, property: 'superTypes', index: i }); + } } - // TODO: Could is be a solution to actually disallow extending from things that are NOT interfaces directly... - // i.e., interfaces build off of other concrete interfaces. That would improve the base reasoning, and make things a lot simpler... - // - else if(superType.ref && !ast.isInterface(superType.ref)) { - accept('error', 'Interfaces cannot extend parser rules.', { node: interfaceType, property: 'superTypes', index: i }); - } - // - // else if(superType.ref && ast.isParserRule(superType.ref) && ast.isType(superType.ref.inferredType)) { - // // thinking of looking here instead, since this process is tiresome... - // console.info('>>>> Super type ref not defined for ....>>>' + interfaceType.name); - // console.info(superType.ref?.inferredType); - // accept('error', 'Interfaces cannot extend union types!!!!', { node: interfaceType, property: 'superTypes', index: i }); - // } }); } - - // TEMPORARY, not a good idea in the long run - //const astResources = collectAllAstResources([grammar], this.documents); - //const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); - // ... - //inferred.unions } checkActionTypeUnions(action: ast.Action, accept: ValidationAcceptor): void { From b88f09ee9f735bff9e5d630a5f5f4245ad5b81df Mon Sep 17 00:00:00 2001 From: "Benjamin F. Wilson" Date: Fri, 17 Jun 2022 14:21:16 +0200 Subject: [PATCH 3/7] adds tests, cleans up leftovers --- .../src/grammar/type-system/type-collector.ts | 1 + .../src/grammar/type-system/type-validator.ts | 50 ------------------- .../grammar/langium-grammar-validator.test.ts | 33 +++++++++++- .../type-system/type-validator.test.ts | 24 --------- 4 files changed, 33 insertions(+), 75 deletions(-) diff --git a/packages/langium/src/grammar/type-system/type-collector.ts b/packages/langium/src/grammar/type-system/type-collector.ts index 9572cd207..46e9699ba 100644 --- a/packages/langium/src/grammar/type-system/type-collector.ts +++ b/packages/langium/src/grammar/type-system/type-collector.ts @@ -20,6 +20,7 @@ export function collectAst(documents: LangiumDocuments, grammars: Grammar[]): As const astResources = collectAllAstResources(grammars, documents); const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); const declared = collectDeclaredTypes(Array.from(astResources.interfaces), Array.from(astResources.types), inferred); + const interfaces: InterfaceType[] = inferred.interfaces.concat(declared.interfaces); const types: UnionType[] = inferred.unions.concat(declared.unions); diff --git a/packages/langium/src/grammar/type-system/type-validator.ts b/packages/langium/src/grammar/type-system/type-validator.ts index 83a0ab51c..f52e47651 100644 --- a/packages/langium/src/grammar/type-system/type-validator.ts +++ b/packages/langium/src/grammar/type-system/type-validator.ts @@ -73,51 +73,6 @@ export function validateTypesConsistency(grammar: Grammar, accept: ValidationAcc ); } } - - // TODO around here is where I need to validate the interfaces that are using inferred type information... - // The data coming back from 'collectValidationResources' is only declared, and does not suffice. - // >>>>> This is where I should be doing further validation. Right after the validation of type consistency, I need to generate inferred types - // and THEN check all interfaces, and verify their superTypes are solid (inferred or NOT) - // the one thing that makes me hesitate is that we already have a superType check, but it's just for whether inferred matches declared...that's it - // cool, so after this do the change - /// >>>>>>> - - // DUPLICATED FROM TYPE COLLECTOR FOR TESTING - // const astResources = collectAllAstResources([grammar]); - // const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); - // //const declared = collectDeclaredTypes(Array.from(astResources.interfaces), Array.from(astResources.types), inferred); - // //const interfaces: InterfaceType[] = inferred.interfaces.concat(declared.interfaces); - // //const types: UnionType[] = inferred.unions.concat(declared.unions); - - // const typeNameToRules = getTypeNameToRules(astResources); - // const inferredInfo = mergeTypesAndInterfaces(inferred) - // .reduce((acc, type) => acc.set(type.name, { inferred: type, nodes: typeNameToRules.get(type.name) }), - // new Map() - // ); - - // // for every interface - // // verify that each super types is NOT a union type (isType) - // // if it is, report the error that 'Interfaces cannot extend union types' - - // for(const [,i] of inferredInfo.entries()) { - - // // for(const node of i.nodes) { - // // if(isInterface(node.)) { - - // // } - // // } - - // if(inferred.unions.some(unionType => i.inferred.superTypes.has(unionType.name) && isInterface(i.inferred))) { - // // TODO index 0 is not correct - // accept('error', 'Interfaces cannot extend union types.', { node: i.nodes[0], property: 'inferredType', index: 0 }); - // // accept('error', ``, - // // { node: typeInfo.node, property: 'name' } - // // ); - // } - // } - - //const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); - //inferred.unions; } function checkConsistentlyDeclaredType(declaredInfo: DeclaredInfo, properties: MultiMap, accept: ValidationAcceptor): void { @@ -266,11 +221,6 @@ export function collectValidationResources(grammar: Grammar): ValidationResource const astResources = collectAllAstResources([grammar]); const inferred = collectInferredTypes(Array.from(astResources.parserRules), Array.from(astResources.datatypeRules)); const declared = collectDeclaredTypes(Array.from(astResources.interfaces), Array.from(astResources.types), inferred); - // ^^^ This section above is exactly what I want in terms of getting access for validation, both inferred & declared types together - // just missing the type collector to get the resources I need combined... - - // TODO 'inferred' has the union data I need to extending is done correctly... - // This seems like the right kind of information, but the spot needs to be just above this function (separate comment) const typeNameToRules = getTypeNameToRules(astResources); const inferredInfo = mergeTypesAndInterfaces(inferred) diff --git a/packages/langium/test/grammar/langium-grammar-validator.test.ts b/packages/langium/test/grammar/langium-grammar-validator.test.ts index 3f8ae631b..b3154e4c5 100644 --- a/packages/langium/test/grammar/langium-grammar-validator.test.ts +++ b/packages/langium/test/grammar/langium-grammar-validator.test.ts @@ -6,7 +6,7 @@ import { createLangiumGrammarServices } from '../../src'; import { Assignment, Grammar, ParserRule } from '../../src/grammar/generated/ast'; -import { expectError, validationHelper } from '../../src/test'; +import { expectError, expectWarning, validationHelper } from '../../src/test'; const services = createLangiumGrammarServices(); const validate = validationHelper(services.grammar); @@ -30,4 +30,35 @@ describe('Langium grammar validation', () => { property: {name: 'terminal'} }); }); + + test('Interfaces should only be able to extend inferred interfaces', async () => { + const validationResult = await validate(` + grammar G + + entry SA: S1 | S2; + SB: S1; + + S1: 's1' s1=ID; + S2: 's2' s2=ID; + + interface DA extends SA {} + interface D2 extends S2 {} + interface DB extends SB {} + + hidden terminal WS: /\\s+/; + terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/; + `); + expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule SA./, { + node: validationResult.document.parseResult.value.interfaces[0], + property: {name: 'superTypes'} + }); + expectWarning(validationResult, /Extending an interface by a parser rule gives an ambiguous type, instead of the expected declared type./, { + node: validationResult.document.parseResult.value.interfaces[1], + property: {name: 'superTypes'} + }); + expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule SB./, { + node: validationResult.document.parseResult.value.interfaces[2], + property: {name: 'superTypes'} + }); + }); }); \ No newline at end of file diff --git a/packages/langium/test/grammar/type-system/type-validator.test.ts b/packages/langium/test/grammar/type-system/type-validator.test.ts index 11bbe3f6d..c287b4c79 100644 --- a/packages/langium/test/grammar/type-system/type-validator.test.ts +++ b/packages/langium/test/grammar/type-system/type-validator.test.ts @@ -95,27 +95,3 @@ describe('validate inferred types', () => { }); }); - -// Helps enforce how inferred types can be used -describe('valid proper usage of inferred types', () => { - - test('interface cannot extend inferred union type', async () => { - const prog = ` - Statement: S1 | S2; - S1: 's1' s1=ID; - S2: 's2' s2=ID; - interface Def extends Statement {} - hidden terminal WS: /\\s+/; - terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/; - `.trim(); - - const document = await parseDocument(grammarServices, prog); - const diagnostics: Diagnostic[] = await grammarServices.validation.DocumentValidator.validateDocument(document); - expect(diagnostics.filter(d => d.severity === DiagnosticSeverity.Error)).toHaveLength(1); - - // verify the location of the single diagnostic error, should be only for the 2nd rule - // const d = diagnostics[0]; - // expect(d.range.start).toEqual({character: 8, line: 5}); - // expect(d.range.end).toEqual({character: 34, line: 5}); - }); -}); From 3fed178fae4c999ec3d5e6634f717cbe9064ebdf Mon Sep 17 00:00:00 2001 From: "Benjamin F. Wilson" Date: Thu, 30 Jun 2022 14:33:47 +0200 Subject: [PATCH 4/7] fix test naming, and ref comment --- .../langium/test/grammar/langium-grammar-validator.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/langium/test/grammar/langium-grammar-validator.test.ts b/packages/langium/test/grammar/langium-grammar-validator.test.ts index b3154e4c5..b37dcff44 100644 --- a/packages/langium/test/grammar/langium-grammar-validator.test.ts +++ b/packages/langium/test/grammar/langium-grammar-validator.test.ts @@ -31,7 +31,8 @@ describe('Langium grammar validation', () => { }); }); - test('Interfaces should only be able to extend inferred interfaces', async () => { + // verifies that interfaces can't extend type unions, especially inferred ones + test('Interfaces extend only interfaces', async () => { const validationResult = await validate(` grammar G From 7193a810476a1b9a97d87df48d4bd941afcff267 Mon Sep 17 00:00:00 2001 From: "Benjamin F. Wilson" Date: Thu, 30 Jun 2022 16:40:43 +0200 Subject: [PATCH 5/7] opts for more verbose naming, and purposeful description --- .../grammar/langium-grammar-validator.test.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/langium/test/grammar/langium-grammar-validator.test.ts b/packages/langium/test/grammar/langium-grammar-validator.test.ts index b37dcff44..ddf1df9d9 100644 --- a/packages/langium/test/grammar/langium-grammar-validator.test.ts +++ b/packages/langium/test/grammar/langium-grammar-validator.test.ts @@ -36,28 +36,43 @@ describe('Langium grammar validation', () => { const validationResult = await validate(` grammar G - entry SA: S1 | S2; - SB: S1; + // generates an inferred union of 2 other inferred interfaces + entry InferredUnion: InferredI1 | InferredI2; - S1: 's1' s1=ID; - S2: 's2' s2=ID; + // for the case where an inferred interface extends another inferred interface + InferredI0: InferredI1; - interface DA extends SA {} - interface D2 extends S2 {} - interface DB extends SB {} + // just for the sake of generating a pair of inferred interfaces + InferredI1: 'InferredI1' InferredI1=ID; + InferredI2: 'InferredI2' InferredI2=ID; + + // should fail... + interface DeclaredExtendsUnion extends InferredUnion {} + + // we can extend an inferred type that has no parent + interface DeclaredExtendsInferred1 extends InferredI2 {} + + // we can extend an inferred type that extends other inferred types + interface DeclaredExtendsInferred2 extends InferredI0 {} hidden terminal WS: /\\s+/; terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/; `); - expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule SA./, { + + // should get an error on DeclaredExtendsUnion, since it cannot extend an inferred union + expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule InferredUnion./, { node: validationResult.document.parseResult.value.interfaces[0], property: {name: 'superTypes'} }); + + // should get a warning when basing declared types on inferred types expectWarning(validationResult, /Extending an interface by a parser rule gives an ambiguous type, instead of the expected declared type./, { node: validationResult.document.parseResult.value.interfaces[1], property: {name: 'superTypes'} }); - expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule SB./, { + + // same warning, but being sure that this holds when an inferred type extends another inferred type + expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule InferredI0./, { node: validationResult.document.parseResult.value.interfaces[2], property: {name: 'superTypes'} }); From 100f7138d23ec7ab7843539f1a9bf5dd595ece58 Mon Sep 17 00:00:00 2001 From: "Benjamin F. Wilson" Date: Wed, 6 Jul 2022 11:57:28 +0200 Subject: [PATCH 6/7] midwork --- packages/langium/src/test/langium-test.ts | 2 +- .../grammar/langium-grammar-validator.test.ts | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/langium/src/test/langium-test.ts b/packages/langium/src/test/langium-test.ts index daa217e88..957981370 100644 --- a/packages/langium/src/test/langium-test.ts +++ b/packages/langium/src/test/langium-test.ts @@ -244,7 +244,7 @@ function filterByOptions> = []; if ('node' in options) { const cstNode = options.property - ? findNodeForFeature(options.node.$cstNode, options.property.name, options.property.index) + ? findNodeForFeature(options.node?.$cstNode, options.property.name, options.property.index) : options.node.$cstNode; if (!cstNode) { throw new Error('Cannot find the node!'); diff --git a/packages/langium/test/grammar/langium-grammar-validator.test.ts b/packages/langium/test/grammar/langium-grammar-validator.test.ts index ddf1df9d9..2167c4c5e 100644 --- a/packages/langium/test/grammar/langium-grammar-validator.test.ts +++ b/packages/langium/test/grammar/langium-grammar-validator.test.ts @@ -31,6 +31,42 @@ describe('Langium grammar validation', () => { }); }); + test('Declared interfaces cannot extend inferred unions', async () => { + const validationResult = await validate(` + grammar G + + InferredUnion: InferredI1 | InferredI2; + + InferredI1: prop1=ID; + InferredI2: prop2=ID; + + interface DeclaredExtendsUnion extends InferredUnion {} + `); + + // should get an error on DeclaredExtendsUnion, since it cannot extend an inferred union + expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule InferredUnion./, { + node: validationResult.document.parseResult.value.interfaces[0], + property: {name: 'superTypes'} + }); + }); + + test('Declared interfaces warn when extending inferred interfaces', async () => { + const validationResult = await validate(` + grammar G + + InferredT: prop=ID; + + interface DeclaredExtendsInferred extends InferredT {}`); + + // should get a warning when basing declared types on inferred types + expectWarning(validationResult, /Extending an interface by a parser rule gives an ambiguous type, instead of the expected declared type./, { + node: validationResult.document.parseResult.value.interfaces[0], + property: {name: 'superTypes'} + }); + }); + + // TODO needs one more test, and also needs a little fixing on the warning above + // verifies that interfaces can't extend type unions, especially inferred ones test('Interfaces extend only interfaces', async () => { const validationResult = await validate(` From 337a2e1c7e45beda992168e67dd6ddfa270f2941 Mon Sep 17 00:00:00 2001 From: "Benjamin F. Wilson" Date: Mon, 11 Jul 2022 14:06:08 +0200 Subject: [PATCH 7/7] factors out tests a bit more, cleaner approach --- .../grammar/langium-grammar-validator.test.ts | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/packages/langium/test/grammar/langium-grammar-validator.test.ts b/packages/langium/test/grammar/langium-grammar-validator.test.ts index 2167c4c5e..c2969d537 100644 --- a/packages/langium/test/grammar/langium-grammar-validator.test.ts +++ b/packages/langium/test/grammar/langium-grammar-validator.test.ts @@ -12,6 +12,20 @@ const services = createLangiumGrammarServices(); const validate = validationHelper(services.grammar); describe('Langium grammar validation', () => { + + test('Declared interfaces warn when extending inferred interfaces', async () => { + const validationResult = await validate(` + InferredT: prop=ID; + + interface DeclaredExtendsInferred extends InferredT {}`); + + // should get a warning when basing declared types on inferred types + expectWarning(validationResult, /Extending an interface by a parser rule gives an ambiguous type, instead of the expected declared type./, { + node: validationResult.document.parseResult.value.interfaces[0], + property: {name: 'superTypes'} + }); + }); + test('Parser rule should not assign fragments', async () => { // arrange const grammarText = ` @@ -31,10 +45,8 @@ describe('Langium grammar validation', () => { }); }); - test('Declared interfaces cannot extend inferred unions', async () => { + test('Declared interfaces cannot extend inferred unions directly', async () => { const validationResult = await validate(` - grammar G - InferredUnion: InferredI1 | InferredI2; InferredI1: prop1=ID; @@ -50,67 +62,23 @@ describe('Langium grammar validation', () => { }); }); - test('Declared interfaces warn when extending inferred interfaces', async () => { - const validationResult = await validate(` - grammar G - - InferredT: prop=ID; - - interface DeclaredExtendsInferred extends InferredT {}`); - - // should get a warning when basing declared types on inferred types - expectWarning(validationResult, /Extending an interface by a parser rule gives an ambiguous type, instead of the expected declared type./, { - node: validationResult.document.parseResult.value.interfaces[0], - property: {name: 'superTypes'} - }); - }); - - // TODO needs one more test, and also needs a little fixing on the warning above + test('Declared interfaces cannot extend inferred unions via indirect inheritance', async () => { - // verifies that interfaces can't extend type unions, especially inferred ones - test('Interfaces extend only interfaces', async () => { const validationResult = await validate(` - grammar G - - // generates an inferred union of 2 other inferred interfaces - entry InferredUnion: InferredI1 | InferredI2; - - // for the case where an inferred interface extends another inferred interface - InferredI0: InferredI1; - - // just for the sake of generating a pair of inferred interfaces - InferredI1: 'InferredI1' InferredI1=ID; - InferredI2: 'InferredI2' InferredI2=ID; - - // should fail... - interface DeclaredExtendsUnion extends InferredUnion {} + InferredUnion: InferredI1 | InferredI2; - // we can extend an inferred type that has no parent - interface DeclaredExtendsInferred1 extends InferredI2 {} + InferredI1: prop1=ID; + InferredI2: prop2=ID; - // we can extend an inferred type that extends other inferred types - interface DeclaredExtendsInferred2 extends InferredI0 {} + Intermediary: InferredUnion; - hidden terminal WS: /\\s+/; - terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/; + interface DeclaredExtendsInferred extends Intermediary {} `); - // should get an error on DeclaredExtendsUnion, since it cannot extend an inferred union - expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule InferredUnion./, { + // same error, but being sure that this holds when an inferred type extends another inferred type + expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule Intermediary./, { node: validationResult.document.parseResult.value.interfaces[0], property: {name: 'superTypes'} }); - - // should get a warning when basing declared types on inferred types - expectWarning(validationResult, /Extending an interface by a parser rule gives an ambiguous type, instead of the expected declared type./, { - node: validationResult.document.parseResult.value.interfaces[1], - property: {name: 'superTypes'} - }); - - // same warning, but being sure that this holds when an inferred type extends another inferred type - expectError(validationResult, /An interface cannot extend a union type, which was inferred from parser rule InferredI0./, { - node: validationResult.document.parseResult.value.interfaces[2], - property: {name: 'superTypes'} - }); }); }); \ No newline at end of file