Skip to content

A "too specific" type gets inferred for a field in a TypeScript interface when using tree-rewriting actions #2004

@haukepribnow

Description

@haukepribnow

In at least one situation (that I don't know how to generalize yet), an incorrect TypeScript interface gets generated. This happens for an inferred type from a parser rule that uses tree-rewriting actions. The result is a TypeScript interface that contains a field with a type that is too specific, i.e. the field type was not "abstract enough" to properly represent what the field could actually contain.

I guess, the example below is more telling than this description.


Langium version: 4.0.0
Package name: langium

Steps To Reproduce

  1. yo langium
  2. Replace the .langium file with the code below
  3. npm run langium:generate
  4. Check the code in the ast.ts file

Code example:

grammar Demo

entry Model:
    Expression;

Expression infers AbstractExpressionChainMember:
    {infer AtomicExpression} atom=Atom (
        ({infer MemberAccess.predecessor=current} '.' member=ID)
    )*
;

Atom:
    name=ID
;

terminal ID: /[_a-zA-Z][\w_]*/;
hidden terminal WS: /\s+/;

The current behavior

For the MemberAccess parser rule, the following interface gets generated - note the type of the predecessor field:

export interface MemberAccess extends langium.AstNode {
    readonly $type: 'MemberAccess';
    member: string;
    predecessor: AtomicExpression;
}

The expected behavior

The predecessor field should actually be of type AbstractExpressionChainMember.

Reason for why the expected behavior is expected

The reason can be demonstrated with this input example:
test.test.test.test

In this example, the syntax tree looks like this:

{
    $type:"MemberAccess",
    predecessor: {
        $type:"MemberAccess",
        predecessor: {
            $type:"MemberAccess",
            predecessor: {
                $type:"AtomicExpression",
                atom: {
                    $type:"Atom",
                    name:"test"
                }
            },
            member:"test"
        },
        member:"test"
    },
    member:"test"
}

You can see: The predecessor field will points to objects that are either of type MemberAccess or of type AtomicExpression. So this example disproves the assumption that the predecessor field will always point to AtomicExpression-typed objects. But this is what the generated TypeScript interface definition implies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions