Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional AST node type #1760

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/arithmetics/src/language-server/arithmetics-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import type { ResolvedReference } from 'langium';
import type { OptionalAstNode, ResolvedReference } from 'langium';
import { isDefinition, type BinaryExpression, type Definition, type FunctionCall } from './generated/ast.js';

export function applyOp(op: BinaryExpression['operator']): (x: number, y: number) => number {
Expand All @@ -28,6 +28,6 @@ export type ResolvedFunctionCall = FunctionCall & {
func: ResolvedReference<Definition>
}

export function isResolvedFunctionCall(functionCall: FunctionCall): functionCall is ResolvedFunctionCall {
return isDefinition(functionCall.func.ref);
export function isResolvedFunctionCall(functionCall: OptionalAstNode<FunctionCall>): functionCall is ResolvedFunctionCall {
return !!functionCall.func && isDefinition(functionCall.func.ref);
}
24 changes: 12 additions & 12 deletions examples/arithmetics/src/language-server/arithmetics-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import type { ValidationAcceptor, ValidationChecks } from 'langium';
import type { ValidationAcceptor, ValidationChecks, OptionalAstNode } from 'langium';
import { MultiMap, stream } from 'langium';
import { evalExpression } from './arithmetics-evaluator.js';
import type { ArithmeticsServices } from './arithmetics-module.js';
Expand All @@ -26,16 +26,16 @@ export function registerValidationChecks(services: ArithmeticsServices): void {
}

export class ArithmeticsValidator {
checkDivByZero(binExpr: BinaryExpression, accept: ValidationAcceptor): void {
if ((binExpr.operator === '/' || binExpr.operator === '%') && evalExpression(binExpr.right) === 0) {
checkDivByZero(binExpr: OptionalAstNode<BinaryExpression>, accept: ValidationAcceptor): void {
if (binExpr.right && (binExpr.operator === '/' || binExpr.operator === '%') && evalExpression(binExpr.right) === 0) {
accept('error', 'Division by zero is detected.', { node: binExpr, property: 'right' });
}
}

checkNormalisable(def: Definition, accept: ValidationAcceptor): void {
const context = new Map<Expression, number>();
checkNormalisable(def: OptionalAstNode<Definition>, accept: ValidationAcceptor): void {
const context = new Map<OptionalAstNode<Expression> | undefined, number>();

const makeOp = (expr: BinaryExpression, op: (x: number, y: number) => number): void => {
const makeOp = (expr: OptionalAstNode<BinaryExpression>, op: (x: number, y: number) => number): void => {
const subExprs = [expr.left, expr.right];
subExprs.forEach(e => evalExpr(e));
const [left, right] = subExprs.map(e => isNumberLiteral(e) ? e.value : context.get(e));
Expand All @@ -45,21 +45,21 @@ export class ArithmeticsValidator {
}
};

const evalExpr = (expr: Expression): void => {
const evalExpr = (expr?: Expression): void => {
if (isBinaryExpression(expr)) {
makeOp(expr, applyOp(expr.operator));
}
};

evalExpr(def.expr);
for (const [expr, result] of context) {
if (result) {
if (expr && result) {
accept('warning', 'Expression could be normalized to constant ' + result, { node: expr });
}
}
}

checkUniqueDefinitions(module: Module, accept: ValidationAcceptor): void {
checkUniqueDefinitions(module: OptionalAstNode<Module>, accept: ValidationAcceptor): void {
const names = new MultiMap<string, Definition>();
for (const def of module.statements) {
if (isDefinition(def) && def.name) {
Expand All @@ -75,7 +75,7 @@ export class ArithmeticsValidator {
}
}

checkFunctionRecursion(module: Module, accept: ValidationAcceptor): void {
checkFunctionRecursion(module: OptionalAstNode<Module>, accept: ValidationAcceptor): void {
const traversedFunctions = new Set<Definition>();
function* getNotTraversedNestedCalls(func: Definition): Generator<NestedFunctionCall> {
if (!traversedFunctions.has(func)) {
Expand Down Expand Up @@ -122,7 +122,7 @@ export class ArithmeticsValidator {
}
}

checkUniqueParameters(definition: Definition, accept: ValidationAcceptor): void {
checkUniqueParameters(definition: OptionalAstNode<Definition>, accept: ValidationAcceptor): void {
const names = new MultiMap<string, DeclaredParameter>();
for (const def of definition.args) {
if (def.name) {
Expand All @@ -138,7 +138,7 @@ export class ArithmeticsValidator {
}
}

checkMatchingParameters(functionCall: FunctionCall, accept: ValidationAcceptor): void {
checkMatchingParameters(functionCall: OptionalAstNode<FunctionCall>, accept: ValidationAcceptor): void {
if (!isResolvedFunctionCall(functionCall) || !functionCall.func.ref.args) {
return;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/langium/src/syntax-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export interface GenericAstNode extends AstNode {
[key: string]: unknown
}

export type OptionalAstNode<T extends AstNode> = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[P in keyof T]: P extends keyof AstNode ? T[P] : T[P] extends any[] | boolean ? T[P] : T[P] | undefined;
Copy link

@sorawee sorawee Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Would it make sense to make this recursive too? I believe a child node that we want to recur downward is either AstNode[] or AstNode | undefined or AstNode. Do I miss anything else?

Here's my attempt to take a stab at it. (This is the first time in my life that I do type-level programming actually! Apologies if anything is off)

export type OptionalAstNode<T extends AstNode> = {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [P in keyof T]: 
    P extends keyof AstNode ? 
      T[P] : 
      T[P] extends AstNode[] ? 
        OptionalAstNode<T[P][number]>[] : 
        T[P] extends any[] | boolean ? 
          T[P] : 
          T[P] extends AstNode | undefined ? 
            OptionalAstNode<Extract<T[P], AstNode>> | undefined :  
            (T[P] extends AstNode ? OptionalAstNode<T[P]> : T[P]) | undefined;
};

};

type SpecificNodeProperties<N extends AstNode> = keyof Omit<N, keyof AstNode | number | symbol>;

/**
Expand Down