[kernel] Support metadata annotations on classes and members.
BUG=
R=jensj@google.com, kustermann@google.com, vegorov@google.com
Review URL: https://chromereviews.googleplex.com/481447014 .
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index 5d84cfd..776829d 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -173,6 +173,7 @@
Byte tag = 2;
Byte flags (isAbstract);
StringReference name;
+ List<Expression> annotations;
List<TypeParameter> typeParameters;
Option<InterfaceType> superClass;
List<InterfaceType> implementedClasses;
@@ -185,6 +186,7 @@
Byte tag = 3;
Byte flags (isAbstract);
StringReference name;
+ List<Expression> annotations;
List<TypeParameter> typeParameters;
InterfaceType firstSuperClass;
InterfaceType secondSuperClass;
@@ -198,6 +200,7 @@
Byte tag = 4;
Byte flags (isFinal, isConst, isStatic);
Name name;
+ List<Expression> annotations;
DartType type;
Option<Expression> initializer;
}
@@ -206,6 +209,7 @@
Byte tag = 5;
Byte flags (isConst, isExternal);
Name name;
+ List<Expression> annotations;
FunctionNode function;
List<Initializer> initializers;
}
@@ -225,6 +229,7 @@
Byte kind; // Index into the ProcedureKind enum above.
Byte flags (isStatic, isAbstract, isExternal, isConst);
Name name;
+ List<Expression> annotations;
// Can only be absent if abstract, but tag is there anyway.
Option<FunctionNode> function;
}
diff --git a/pkg/kernel/lib/analyzer/ast_from_analyzer.dart b/pkg/kernel/lib/analyzer/ast_from_analyzer.dart
index 175793b..bec4e8d 100644
--- a/pkg/kernel/lib/analyzer/ast_from_analyzer.dart
+++ b/pkg/kernel/lib/analyzer/ast_from_analyzer.dart
@@ -297,51 +297,22 @@
}
}
-/// Translates expressions, statements, and other constructs into [ast] nodes.
-///
-/// Naming convention:
-/// - `buildX` may not be given null as argument (it may crash the compiler).
-/// - `buildOptionalX` returns null or an empty list if given null
-/// - `buildMandatoryX` returns an invalid node if given null.
-class MemberScope extends TypeScope {
+class ExpressionScope extends TypeScope {
+ /// The library containing the code, currently at body level.
+ ast.Library currentLibrary;
final Map<LocalElement, ast.VariableDeclaration> localVariables =
<LocalElement, ast.VariableDeclaration>{};
- /// A reference to the member currently being upgraded to body level.
- final ast.Member currentMember;
-
ExpressionBuilder _expressionBuilder;
StatementBuilder _statementBuilder;
- MemberScope(ReferenceLevelLoader loader, this.currentMember) : super(loader) {
- assert(currentMember != null);
+ ExpressionScope(ReferenceLevelLoader loader, this.currentLibrary)
+ : super(loader) {
_expressionBuilder = new ExpressionBuilder(this);
_statementBuilder = new StatementBuilder(this);
}
- /// The library containing the code, currently at body level.
- ast.Library get currentLibrary => currentMember.enclosingLibrary;
-
- ast.Class get currentClass => currentMember.enclosingClass;
-
- bool get allowThis => _memberHasThis(currentMember);
-
- /// Returns a string for debugging use, indicating the location of the member
- /// being built.
- String get location {
- var library = currentMember.enclosingLibrary?.importUri ?? '<No Library>';
- var className = currentMember.enclosingClass == null
- ? null
- : (currentMember.enclosingClass?.name ?? '<Anonymous Class>');
- var member =
- currentMember.name?.name ?? '<Anonymous ${currentMember.runtimeType}>';
- return [library, className, member].join('::');
- }
-
- bool _memberHasThis(ast.Member member) {
- return member is ast.Procedure && !member.isStatic ||
- member is ast.Constructor;
- }
+ bool get allowThis => false; // Overridden by MemberScope.
ast.Name buildName(SimpleIdentifier node) {
return new ast.Name(node.name, currentLibrary);
@@ -478,6 +449,63 @@
}
return declaration;
}
+
+ ast.Expression buildAnnotation(Annotation annotation) {
+ Element element = annotation.element;
+ if (annotation.arguments == null) {
+ var target = resolveGet(element, null);
+ return target == null
+ ? new ast.InvalidExpression()
+ : new ast.StaticGet(target);
+ } else if (element is ConstructorElement && element.isConst) {
+ var target = resolveConstructor(element);
+ return target == null
+ ? new ast.InvalidExpression()
+ : new ast.ConstructorInvocation(
+ target, _expressionBuilder.buildArguments(annotation.arguments),
+ isConst: true);
+ } else {
+ return new ast.InvalidExpression();
+ }
+ }
+}
+
+/// Translates expressions, statements, and other constructs into [ast] nodes.
+///
+/// Naming convention:
+/// - `buildX` may not be given null as argument (it may crash the compiler).
+/// - `buildOptionalX` returns null or an empty list if given null
+/// - `buildMandatoryX` returns an invalid node if given null.
+class MemberScope extends ExpressionScope {
+ /// A reference to the member currently being upgraded to body level.
+ final ast.Member currentMember;
+
+ MemberScope(ReferenceLevelLoader loader, ast.Member currentMember)
+ : currentMember = currentMember,
+ super(loader, currentMember.enclosingLibrary) {
+ assert(currentMember != null);
+ }
+
+ ast.Class get currentClass => currentMember.enclosingClass;
+
+ bool get allowThis => _memberHasThis(currentMember);
+
+ /// Returns a string for debugging use, indicating the location of the member
+ /// being built.
+ String get location {
+ var library = currentMember.enclosingLibrary?.importUri ?? '<No Library>';
+ var className = currentMember.enclosingClass == null
+ ? null
+ : (currentMember.enclosingClass?.name ?? '<Anonymous Class>');
+ var member =
+ currentMember.name?.name ?? '<Anonymous ${currentMember.runtimeType}>';
+ return [library, className, member].join('::');
+ }
+
+ bool _memberHasThis(ast.Member member) {
+ return member is ast.Procedure && !member.isStatic ||
+ member is ast.Constructor;
+ }
}
class LabelStack {
@@ -495,7 +523,7 @@
}
class StatementBuilder extends GeneralizingAstVisitor<ast.Statement> {
- final MemberScope scope;
+ final ExpressionScope scope;
final LabelStack breakStack, continueStack;
StatementBuilder(this.scope, [this.breakStack, this.continueStack]);
@@ -851,7 +879,7 @@
class ExpressionBuilder
extends GeneralizingAstVisitor /* <ast.Expression | Accessor> */ {
- final MemberScope scope;
+ final ExpressionScope scope;
final ast.VariableDeclaration cascadeReceiver;
ExpressionBuilder(this.scope, [this.cascadeReceiver]);
@@ -1447,7 +1475,7 @@
}
class StringLiteralPartBuilder extends GeneralizingAstVisitor<Null> {
- final MemberScope scope;
+ final ExpressionScope scope;
final List<ast.Expression> output;
StringLiteralPartBuilder(this.scope, this.output);
@@ -1698,13 +1726,15 @@
///
/// The enclosing library is assumed to be at body level already.
class ClassBodyBuilder extends GeneralizingAstVisitor<Null> {
- final TypeScope scope;
+ final ExpressionScope scope;
final ast.Class currentClass;
final ClassElement element;
ast.Library get currentLibrary => currentClass.enclosingLibrary;
- ClassBodyBuilder(ReferenceLevelLoader loader, this.currentClass, this.element)
- : scope = new TypeScope(loader);
+ ClassBodyBuilder(
+ ReferenceLevelLoader loader, ast.Class currentClass, this.element)
+ : this.currentClass = currentClass,
+ scope = new ExpressionScope(loader, currentClass.enclosingLibrary);
void build(CompilationUnitMember node) {
if (node == null) {
@@ -1713,6 +1743,12 @@
node.accept(this);
}
+ void addAnnotations(List<Annotation> annotations) {
+ for (var annotation in annotations) {
+ currentClass.addAnnotation(scope.buildAnnotation(annotation));
+ }
+ }
+
void addTypeParameterBounds(TypeParameterList typeParameters) {
if (typeParameters == null) return;
int index = 0;
@@ -1760,6 +1796,7 @@
}
visitClassDeclaration(ClassDeclaration node) {
+ addAnnotations(node.metadata);
ast.NormalClass classNode = currentClass;
addTypeParameterBounds(node.typeParameters);
// Build the super class reference and expand the 'with' clause into
@@ -1810,6 +1847,7 @@
static bool _isValuesField(FieldElement field) => field.name == 'values';
visitEnumDeclaration(EnumDeclaration node) {
+ addAnnotations(node.metadata);
ast.NormalClass classNode = currentClass;
classNode.supertype = new ast.InterfaceType(scope.getRootClassReference());
var intType =
@@ -1856,6 +1894,7 @@
}
visitClassTypeAlias(ClassTypeAlias node) {
+ addAnnotations(node.metadata);
assert(node.withClause != null && node.withClause.mixinTypes.isNotEmpty);
ast.MixinClass classNode = currentClass;
addTypeParameterBounds(node.typeParameters);
@@ -1986,6 +2025,12 @@
types: typeArguments))..parent = constructor);
}
+ void addAnnotations(List<Annotation> annotations) {
+ for (var annotation in annotations) {
+ currentMember.addAnnotation(scope.buildAnnotation(annotation));
+ }
+ }
+
visitConstructorDeclaration(ConstructorDeclaration node) {
if (node.factoryKeyword != null) {
buildFactoryConstructor(node);
@@ -1995,6 +2040,7 @@
}
void buildGenerativeConstructor(ConstructorDeclaration node) {
+ addAnnotations(node.metadata);
ast.Constructor constructor = currentMember;
constructor.function = scope.buildFunctionNode(node.parameters, node.body,
inferredReturnType: const ast.VoidType())..parent = constructor;
@@ -2030,6 +2076,7 @@
}
void buildFactoryConstructor(ConstructorDeclaration node) {
+ addAnnotations(node.metadata);
ast.Procedure procedure = currentMember;
ClassElement classElement = node.element.enclosingElement;
ast.NormalClass classNode = procedure.enclosingClass;
@@ -2074,6 +2121,7 @@
}
visitMethodDeclaration(MethodDeclaration node) {
+ addAnnotations(node.metadata);
ast.Procedure procedure = currentMember;
procedure.function = scope.buildFunctionNode(node.parameters, node.body,
returnType: node.returnType,
@@ -2084,6 +2132,7 @@
}
visitVariableDeclaration(VariableDeclaration node) {
+ addAnnotations(node.metadata);
ast.Field field = currentMember;
field.type = scope.buildType(node.element.type);
if (node.initializer != null) {
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index 4f73438..349a5d3 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -261,6 +261,12 @@
/// should treat the two kinds of classes separately, but otherwise it is
/// recommended to interface against [Class].
abstract class Class extends TreeNode {
+ /// List of metadata annotations on the class.
+ ///
+ /// This defaults to an immutable empty list. Use [addAnnotation] to add
+ /// annotations if needed.
+ List<Expression> annotations = const <Expression>[];
+
String name; // Cosmetic name.
bool isAbstract;
final List<TypeParameter> typeParameters;
@@ -343,6 +349,14 @@
}
}
+ void addAnnotation(Expression node) {
+ if (annotations.isEmpty) {
+ annotations = <Expression>[];
+ }
+ annotations.add(node);
+ node.parent = this;
+ }
+
accept(ClassVisitor v);
acceptReference(ClassReferenceVisitor v);
@@ -387,6 +401,7 @@
acceptReference(ClassReferenceVisitor v) => v.visitNormalClassReference(this);
visitChildren(Visitor v) {
+ visitList(annotations, v);
visitList(typeParameters, v);
supertype?.accept(v);
visitList(implementedTypes, v);
@@ -396,6 +411,7 @@
}
transformChildren(Transformer v) {
+ transformList(annotations, v, this);
transformList(typeParameters, v, this);
transformList(constructors, v, this);
transformList(procedures, v, this);
@@ -440,6 +456,7 @@
acceptReference(ClassReferenceVisitor v) => v.visitMixinClassReference(this);
visitChildren(Visitor v) {
+ visitList(annotations, v);
visitList(typeParameters, v);
supertype?.accept(v);
mixedInType?.accept(v);
@@ -448,6 +465,7 @@
}
transformChildren(Transformer v) {
+ transformList(annotations, v, this);
transformList(typeParameters, v, this);
transformList(constructors, v, this);
}
@@ -458,6 +476,11 @@
// ------------------------------------------------------------------------
abstract class Member extends TreeNode {
+ /// List of metadata annotations on the class.
+ ///
+ /// This defaults to an immutable empty list. Use [addAnnotation] to add
+ /// annotations if needed.
+ List<Expression> annotations = const <Expression>[];
Name name;
Member(this.name);
@@ -482,6 +505,14 @@
/// Returns a possibly synthesized name for this member, consistent with
/// the names used across all [toString] calls.
String toString() => debugQualifiedMemberName(this);
+
+ void addAnnotation(Expression node) {
+ if (annotations.isEmpty) {
+ annotations = <Expression>[];
+ }
+ annotations.add(node);
+ node.parent = this;
+ }
}
/// A field declaration.
@@ -537,6 +568,7 @@
acceptReference(MemberReferenceVisitor v) => v.visitFieldReference(this);
visitChildren(Visitor v) {
+ visitList(annotations, v);
type?.accept(v);
inferredValue?.accept(v);
name?.accept(v);
@@ -544,6 +576,7 @@
}
transformChildren(Transformer v) {
+ transformList(annotations, v, this);
if (initializer != null) {
initializer = initializer.accept(v);
initializer?.parent = this;
@@ -600,12 +633,14 @@
v.visitConstructorReference(this);
visitChildren(Visitor v) {
+ visitList(annotations, v);
name?.accept(v);
function?.accept(v);
visitList(initializers, v);
}
transformChildren(Transformer v) {
+ transformList(annotations, v, this);
if (function != null) {
function = function.accept(v);
function?.parent = this;
@@ -689,11 +724,13 @@
acceptReference(MemberReferenceVisitor v) => v.visitProcedureReference(this);
visitChildren(Visitor v) {
+ visitList(annotations, v);
name?.accept(v);
function?.accept(v);
}
transformChildren(Transformer v) {
+ transformList(annotations, v, this);
if (function != null) {
function = function.accept(v);
function?.parent = this;
diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart
index e5fed33..0edb0b4 100644
--- a/pkg/kernel/lib/binary/ast_from_binary.dart
+++ b/pkg/kernel/lib/binary/ast_from_binary.dart
@@ -130,6 +130,16 @@
}
}
+ List<Expression> readAnnotationList(TreeNode parent) {
+ int length = readUInt();
+ if (length == 0) return const <Expression>[];
+ List<Expression> list = new List<Expression>(length);
+ for (int i = 0; i < length; ++i) {
+ list[i] = readExpression()..parent = parent;
+ }
+ return list;
+ }
+
void _fillTreeNodeList(
List<TreeNode> list, TreeNode buildObject(), TreeNode parent) {
list.length = readUInt();
@@ -322,6 +332,7 @@
int flags = readByte();
node.isAbstract = flags & 0x1 != 0;
node.name = readStringOrNullIfEmpty();
+ node.annotations = readAnnotationList(node);
debugPath.add(node.name ?? 'normal-class');
readAndPushTypeParameterList(node.typeParameters, node);
node.supertype = readDartTypeOption();
@@ -343,6 +354,7 @@
int flags = readByte();
node.isAbstract = flags & 0x1 != 0;
node.name = readStringOrNullIfEmpty();
+ node.annotations = readAnnotationList(node);
debugPath.add(node.name ?? 'mixin-class');
readAndPushTypeParameterList(node.typeParameters, node);
node.supertype = readDartType();
@@ -362,6 +374,7 @@
assert(tag == Tag.Field);
node.flags = readByte();
node.name = readName();
+ node.annotations = readAnnotationList(node);
debugPath.add(node.name?.name ?? 'field');
node.type = readDartType();
node.inferredValue = readOptionalInferredValue();
@@ -374,6 +387,7 @@
assert(tag == Tag.Constructor);
node.flags = readByte();
node.name = readName();
+ node.annotations = readAnnotationList(node);
debugPath.add(node.name?.name ?? 'constructor');
node.function = readFunctionNode()..parent = node;
pushVariableDeclarations(node.function.positionalParameters);
@@ -389,6 +403,7 @@
node.kind = ProcedureKind.values[kindIndex];
node.flags = readByte();
node.name = readName();
+ node.annotations = readAnnotationList(node);
debugPath.add(node.name?.name ?? 'procedure');
node.function = readFunctionNodeOption();
node.function?.parent = node;
diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart
index b8f79f2..f48130e 100644
--- a/pkg/kernel/lib/binary/ast_to_binary.dart
+++ b/pkg/kernel/lib/binary/ast_to_binary.dart
@@ -251,6 +251,7 @@
writeByte(Tag.NormalClass);
writeByte(node.isAbstract ? 1 : 0);
writeStringReference(node.name ?? '');
+ writeNodeList(node.annotations);
_typeParameterIndexer.push(node.typeParameters);
writeNodeList(node.typeParameters);
writeOptionalNode(node.supertype);
@@ -265,6 +266,7 @@
writeByte(Tag.MixinClass);
writeByte(node.isAbstract ? 1 : 0);
writeStringReference(node.name ?? '');
+ writeNodeList(node.annotations);
_typeParameterIndexer.push(node.typeParameters);
writeNodeList(node.typeParameters);
writeNode(node.supertype);
@@ -279,6 +281,7 @@
writeByte(Tag.Constructor);
writeByte(node.flags);
writeName(node.name ?? '');
+ writeNodeList(node.annotations);
assert(node.function.typeParameters.isEmpty);
writeNode(node.function);
writeNodeList(node.initializers);
@@ -290,6 +293,7 @@
writeByte(node.kind.index);
writeByte(node.flags);
writeName(node.name ?? '');
+ writeNodeList(node.annotations);
writeOptionalNode(node.function);
}
@@ -298,6 +302,7 @@
writeByte(Tag.Field);
writeByte(node.flags);
writeName(node.name ?? '');
+ writeNodeList(node.annotations);
writeNode(node.type);
writeOptionalInferredValue(node.inferredValue);
writeOptionalNode(node.initializer);
diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart
index 1d84502..c203d00 100644
--- a/pkg/kernel/lib/text/ast_to_text.dart
+++ b/pkg/kernel/lib/text/ast_to_text.dart
@@ -609,9 +609,28 @@
}
}
+ void writeAnnotation(Expression node) {
+ writeSymbol('@');
+ if (node is ConstructorInvocation) {
+ writeMemberReference(node.target);
+ visitArguments(node.arguments);
+ } else {
+ writeExpression(node);
+ }
+ }
+
+ void writeAnnotationList(List<Expression> nodes) {
+ for (Expression node in nodes) {
+ writeIndentation();
+ writeAnnotation(node);
+ endLine();
+ }
+ }
+
visitLibrary(Library node) {}
visitField(Field node) {
+ writeAnnotationList(node.annotations);
writeIndentation();
writeModifier(node.isStatic, 'static');
writeModifier(node.isFinal, 'final');
@@ -627,6 +646,7 @@
}
visitProcedure(Procedure node) {
+ writeAnnotationList(node.annotations);
writeIndentation();
writeModifier(node.isExternal, 'external');
writeModifier(node.isStatic, 'static');
@@ -636,6 +656,7 @@
}
visitConstructor(Constructor node) {
+ writeAnnotationList(node.annotations);
writeIndentation();
writeModifier(node.isExternal, 'external');
writeModifier(node.isConst, 'const');
@@ -645,6 +666,7 @@
}
visitNormalClass(NormalClass node) {
+ writeAnnotationList(node.annotations);
writeIndentation();
writeModifier(node.isAbstract, 'abstract');
writeWord('class');
@@ -669,6 +691,7 @@
}
visitMixinClass(MixinClass node) {
+ writeAnnotationList(node.annotations);
writeIndentation();
writeModifier(node.isAbstract, 'abstract');
writeWord('mixin');