langium icon indicating copy to clipboard operation
langium copied to clipboard

Incorrect types inferrence for parents with own properties

Open pluralia opened this issue 3 years ago • 6 comments

When a parent has no own properties, we generate a type Parent :

Parent:
    ChildA | ChildB;

ChildA:
    'a' a=NUMBER commonProp=ID;

ChildB:
    'b' b=NUMBER commonProp=ID;

generates

export type Parent = ChildA | ChildB;

export interface ChildA extends AstNode {
    a: number
    commonProp: string
}

export interface ChildB extends AstNode {
    b: number
    commonProp: string
}

Besides, common children properties like commonProp will be lifted automatically. However, as only a parent has own properties, we generate an interface Parent:

Parent:
    parentProp=ID (ChildA | ChildB);

// ChildA and ChildB are without changes

generates

export interface Parent extends AstNode {
    parentProp: string
}

export interface ChildA extends AstNode {
    a: number
    commonProp: string
}

export interface ChildB extends AstNode {
    b: number
    commonProp: string
}

You can notice, that ChildA and ChildB don't extend Parent and their commonProp is not lifted in Parent.

Langium version: 0.5.0 Package name: langium

The current behavior

Inferred Parent -- ChildA -- ChildB hierarchy is wrong when Parent has own properties (look above).

The expected behavior

export interface Parent extends AstNode {
    parentProp: string
    commonProp: string
}

export interface ChildA extends Parent {
    a: number
}

export interface ChildB extends Parent {
    b: number
}

The solution to the problem requires restoring properties lifting algorithm.

pluralia avatar Nov 04 '22 15:11 pluralia

However, as only a parent has own properties, we generate an interface Parent:

Parent:
    parentProp=ID (ChildA | ChildB);

// ChildA and ChildB are without changes

generates

export interface Parent extends AstNode {
    parentProp: string
}

export interface ChildA extends AstNode {
    a: number
    commonProp: string
}

export interface ChildB extends AstNode {
    b: number
    commonProp: string
}

You can notice, that ChildA and ChildB don't extend Parent and their commonProp is not lifted in Parent.

Langium version: 0.5.0 Package name: langium

@pluralia IMO the behavior is totally correct. Actually you grammar is incorrect, as the rule

Parent:
    parentProp=ID (ChildA | ChildB);

needs to instantiate an object of type Parent once it consumes an ID in order to assign it to prop parentProp. The result of ChildA | ChildB is then unassigned (and eventually lost).

If you can write your grammar like

Parent:
    (ChildA | ChildB) parentProp=ID;

you should obtain your desired result.

Alternatively you could use assigned actions, like

Parent:
    parentProp=ID (ChildA.props = current | ChildB.props = current);

(in order to keep your syntax, of course the AST will still look different).

The current behavior

Inferred Parent -- ChildA -- ChildB hierarchy is wrong when Parent has own properties (look above).

The expected behavior

export interface Parent extends AstNode {
    parentProp: string
    commonProp: string
}

export interface ChildA extends Parent {
    a: number
}

export interface ChildB extends Parent {
    b: number
}

sailingKieler avatar Nov 04 '22 16:11 sailingKieler

If you can write your grammar like

Parent:
    (ChildA | ChildB) parentProp=ID;

you should obtain your desired result.

@sailingKieler, could you explain, why this has to get different result with?

Parent:
    (ChildA | ChildB) parentProp=ID;

pluralia avatar Nov 04 '22 16:11 pluralia

Parent:
    (ChildA | ChildB) parentProp=ID;

In this case the rule doesn't instantiate an object itself, but proceeds with the result of (ChildA | ChildB). It's the same pattern as for rules like:

Expression:
   Literal | UnaryOp | BinaryOp
;

plus the additional property assignment

sailingKieler avatar Nov 04 '22 16:11 sailingKieler

Thank you for the explanation!

If you can write your grammar like

Parent:
    (ChildA | ChildB) parentProp=ID;

you should obtain your desired result.

This infers the same types and the issue is still here.

pluralia avatar Nov 04 '22 16:11 pluralia

This infers the same types and the issue is still here.

Ok, then it's really a mistake ;-). Does Xtext's type inference test contain a corresponding test case?

sailingKieler avatar Nov 04 '22 17:11 sailingKieler

Yes, in Xtext it's forbidden to call unassigned rules after object creation (action or property assignment). But in Langium we are not forced to do the same, see discussion in https://github.com/langium/langium/discussions/205.

spoenemann avatar Nov 07 '22 12:11 spoenemann