Summary
Here's a simple working example in the TypeScript playground:
type SimpleExpression = number | string | AddOperator<SimpleExpression> | PrintOperator<SimpleExpression>;
type ExtendedExpression = number | string | AddOperator<ExtendedExpression> | PrintOperator<ExtendedExpression> | PowerOperator<ExtendedExpression>;
However, attempting to extract a common sub-type leads to errors in the playground:
type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;
Is there a solution to this issue?
Detailed Description
With the recent update to TypeScript (3.7), recursive types are now supported, but limitations still exist.
While there are many resources explaining recursive types on StackOverflow, a specific pattern poses a challenge.
The initial concept is an Expression tree, where operators have child expressions. This initial setup works:
type Expression = number | string | AddOperator | PrintOperator;
interface AddOperator {
'first': Expression;
'second': Expression;
}
interface PrintOperator {
'value': Expression;
}
The goal is to create a more generic structure, with SimpleExpression (with 'add' and 'print' operations) and ExtendedExpression (additionally supporting 'power' operations). While this is achievable and functional, the attempt to define a common generic type for both expressions fails in the playground:
type CommonExpression<E> = number | string | AddOperator<E> | PrintOperator<E>;
type SimpleExpression = CommonExpression<SimpleExpression>;
type ExtendedExpression = CommonExpression<ExtendedExpression> | PowerOperator<ExtendedExpression>;
The errors encountered are:
Error: Type alias 'SimpleExpression' circularly references itself.
Error: Type alias 'ExtendedExpression' circularly references itself.
The primary question posed here is: Is there a method to define two distinct expression types that share a common core?
Some context on the usage scenario: the aim is to offer
SimpleExpression
in a library while enabling users to define additional operators and register handlers for them. The idea is to allow users to easily define theirExtendedExpression
type without excessive manual input.