Currently, we generate code for the AST tree and the element tree by using the TreeGenerator class.
It helps us to not have to change the visitors and other methods manually for every change we do on these trees.
However, this script is complex and has its limitations.
With the upcoming macros feature in Dart, it would be better to migrate this system to generate the code this way.
The results will be better development ergonomics (as we can avoid much of the declarative API we currently have, as specifying which node implements which) and speed (as we don't have to run the script every time we make a change).
The proposed API would be something like this:
@Tree
sealed class Element {
const Element();
Element? get enclosingElement;
}
abstract interface class TypedElement extends Element {
Type? get type;
}
abstract interface class TypeDefiningElement extends Element {
Type get definedType;
}
final class TypeParameterElement extends Element implements TypedElement, TypeDefiningElement {
TypeParameterElement({required this.name});
final String name;
@override
late TypeDefinitionElement enclosingElement;
@override
Type? type = TypeType();
@override
late Type definedType;
}
final class StructMemberElement extends Element {
StructMemberElement();
late final String name;
late final ExpressionElement value;
@override
late LiteralElement enclosingElement;
}
final class ParameterElement extends Element implements TypedElement {
ParameterElement({
required this.name,
this.type,
});
final String name;
@override
Type? type;
@override
late Element enclosingElement;
}
sealed class ExpressionElement extends Element implements TypedElement {
@override
late Element enclosingElement;
bool get constant;
Object? get constantValue;
}
final class InvocationElement extends ExpressionElement {
InvocationElement({
this.type,
required this.identifier,
required this.argument,
required this.constant,
required this.constantValue,
});
@override
Type? type;
final IdentifierElement identifier;
final ExpressionElement argument;
@override
final bool constant;
@override
final Object? constantValue;
String get name => identifier.name;
}
final class IdentifierElement extends ExpressionElement {
IdentifierElement({
required this.name,
this.type,
required this.constant,
required this.constantValue,
required this.definition,
});
final String name;
@override
Type? type;
@override
final bool constant;
@override
final Object? constantValue;
final TypedElement definition;
}
sealed class LiteralElement extends ExpressionElement {}
final class SingletonLiteralElement extends LiteralElement {
SingletonLiteralElement({this.type});
@override
Type? type;
@override
late final bool constant;
@override
late final Object? constantValue;
}
final class StructLiteralElement extends LiteralElement {
@override
late final StructType type;
final List<StructMemberElement> members = [];
@override
late final bool constant;
@override
late final Object? constantValue;
}
final class TypeVariantElement extends Element {
TypeVariantElement({required this.name});
final String name;
final List<ParameterElement> parameters = [];
@override
late TypeDefinitionElement enclosingElement;
}
sealed class DeclarationElement extends Element {
DeclarationElement();
@override
late ProgramElement enclosingElement;
}
final class ImportElement extends DeclarationElement {
ImportElement({required this.package});
final Package package;
}
sealed class LetDeclarationElement extends DeclarationElement implements TypedElement {
LetDeclarationElement({required this.name});
final String name;
late final ExpressionElement body;
}
final class LetFunctionDeclaration extends LetDeclarationElement {
LetFunctionDeclaration({
required super.name,
required this.parameter,
});
final StructLiteralElement parameter;
@override
late FunctionType type;
}
final class LetVariableDeclaration extends LetDeclarationElement {
LetVariableDeclaration({
required super.name,
this.type,
});
@override
Type? type;
}
final class ImportedSymbolSyntheticElement extends DeclarationElement implements TypedElement {
ImportedSymbolSyntheticElement({
required this.name,
required this.syntheticElement,
});
final String name;
final TypedElement syntheticElement;
@override
Type get type => syntheticElement.type!;
}
final class TypeDefinitionElement extends DeclarationElement implements TypedElement, TypeDefiningElement {
TypeDefinitionElement({required this.name});
final String name;
final List<TypeParameterElement> parameters = [];
final List<TypeVariantElement> variants = [];
@override
Type? type = TypeType();
@override
late Type definedType;
}
final class ProgramElement extends Element {
final List<ImportElement> imports = [];
final List<DeclarationElement> declarations = [];
@override
late final Null enclosingElement = null;
}
By providing a @Tree annotation, all visitChildren and accept methods would be augmented in the children classes, and the correspondent visitors generated in the augmented library.
Currently, we generate code for the AST tree and the element tree by using the
TreeGenerator
class.It helps us to not have to change the visitors and other methods manually for every change we do on these trees.
However, this script is complex and has its limitations.
With the upcoming macros feature in Dart, it would be better to migrate this system to generate the code this way.
The results will be better development ergonomics (as we can avoid much of the declarative API we currently have, as specifying which node implements which) and speed (as we don't have to run the script every time we make a change).
The proposed API would be something like this:
By providing a
@Tree
annotation, allvisitChildren
andaccept
methods would be augmented in the children classes, and the correspondent visitors generated in the augmented library.