Initial support for macro(s) as built into analyzer.
Change-Id: Ib22317b1257b0a4d56d6ed35d5b6a13e39226c59
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/207720
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 666d9be..41a201a 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -2361,6 +2361,15 @@
/// children of this element's parent.
String get identifier => name!;
+ /// Return `true` if this element was created from a macro-generated code.
+ bool get isFromMacro {
+ return hasModifier(Modifier.IS_FROM_MACRO);
+ }
+
+ set isFromMacro(bool isFromMacro) {
+ setModifier(Modifier.IS_FROM_MACRO, isFromMacro);
+ }
+
bool get isNonFunctionTypeAliasesEnabled {
return library!.featureSet.isEnabled(Feature.nonfunction_type_aliases);
}
@@ -4281,23 +4290,26 @@
/// type being referred to is the return type.
static const Modifier IMPLICIT_TYPE = Modifier('IMPLICIT_TYPE', 16);
+ /// Indicates that this element was created from macro-generated code.
+ static const Modifier IS_FROM_MACRO = Modifier('IS_FROM_MACRO', 17);
+
/// Indicates that modifier 'lazy' was applied to the element.
- static const Modifier LATE = Modifier('LATE', 17);
+ static const Modifier LATE = Modifier('LATE', 18);
/// Indicates that a class is a mixin application.
- static const Modifier MIXIN_APPLICATION = Modifier('MIXIN_APPLICATION', 18);
+ static const Modifier MIXIN_APPLICATION = Modifier('MIXIN_APPLICATION', 19);
/// Indicates that the pseudo-modifier 'set' was applied to the element.
- static const Modifier SETTER = Modifier('SETTER', 19);
+ static const Modifier SETTER = Modifier('SETTER', 20);
/// Indicates that the modifier 'static' was applied to the element.
- static const Modifier STATIC = Modifier('STATIC', 20);
+ static const Modifier STATIC = Modifier('STATIC', 21);
/// Indicates that the element does not appear in the source code but was
/// implicitly created. For example, if a class does not define any
/// constructors, an implicit zero-argument constructor will be created and it
/// will be marked as being synthetic.
- static const Modifier SYNTHETIC = Modifier('SYNTHETIC', 21);
+ static const Modifier SYNTHETIC = Modifier('SYNTHETIC', 22);
static const List<Modifier> values = [
ABSTRACT,
@@ -4316,6 +4328,7 @@
HAS_INITIALIZER,
HAS_PART_OF_DIRECTIVE,
IMPLICIT_TYPE,
+ IS_FROM_MACRO,
LATE,
MIXIN_APPLICATION,
SETTER,
diff --git a/pkg/analyzer/lib/src/macro/api/code.dart b/pkg/analyzer/lib/src/macro/api/code.dart
new file mode 100644
index 0000000..0c06310
--- /dev/null
+++ b/pkg/analyzer/lib/src/macro/api/code.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Combines [parts] into a [String].
+/// Must only contain [Code] or [String] instances.
+String _combineParts(List<Object> parts) {
+ var buffer = StringBuffer();
+ for (var part in parts) {
+ if (part is String) {
+ buffer.write(part);
+ } else if (part is Code) {
+ buffer.write(part.code);
+ } else if (part is List<Code>) {
+ buffer.write(part.map((p) => p.code).join());
+ } else {
+ throw UnsupportedError(
+ 'Only String, Code, and List<Code> are allowed but got $part',
+ );
+ }
+ }
+ return buffer.toString();
+}
+
+/// The representation of a piece of code.
+abstract class Code {
+ String get code;
+
+ @override
+ String toString() => code;
+}
+
+/// A piece of code representing a syntactically valid declaration.
+class Declaration extends Code {
+ @override
+ final String code;
+
+ Declaration(this.code);
+
+ /// Creates a [Declaration] from [parts], which must be of type [Code],
+ /// `List<Code>`, or [String].
+ factory Declaration.fromParts(List<Object> parts) =>
+ Declaration(_combineParts(parts));
+}
+
+/// A piece of code that can't be parsed into a valid language construct in its
+/// current form. No validation or parsing is performed.
+class Fragment extends Code {
+ @override
+ final String code;
+
+ Fragment(this.code);
+
+ /// Creates a [Fragment] from [parts], which must be of type [Code],
+ /// `List<Code>`, or [String].
+ factory Fragment.fromParts(List<Object> parts) =>
+ Fragment(_combineParts(parts));
+}
diff --git a/pkg/analyzer/lib/src/macro/api/macro.dart b/pkg/analyzer/lib/src/macro/api/macro.dart
new file mode 100644
index 0000000..0f65d38
--- /dev/null
+++ b/pkg/analyzer/lib/src/macro/api/macro.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart' as ast;
+import 'package:analyzer/src/macro/api/code.dart';
+
+/// The api used by [DeclarationMacro]s to contribute new declarations to the
+/// current class.
+///
+/// Note that this is available to macros that run directly on classes, as well
+/// as macros that run on any members of a class.
+abstract class ClassDeclarationBuilder implements DeclarationBuilder {
+ /// Adds a new declaration to the surrounding class.
+ void addToClass(Declaration declaration);
+}
+
+/// The api used by [DeclarationMacro]s to contribute new declarations to the
+/// current library.
+abstract class DeclarationBuilder {
+ /// Adds a new regular declaration to the surrounding library.
+ ///
+ /// Note that type declarations are not supported.
+ void addToLibrary(Declaration declaration);
+
+ /// Return the [Code] of the [node].
+ Code typeAnnotationCode(ast.TypeAnnotation node);
+}
+
+/// The marker interface for macros that are allowed to contribute new
+/// declarations to the program, including both top level and class level
+/// declarations.
+///
+/// These macros run after [TypeMacro] macros, but before [DefinitionMacro]
+/// macros.
+///
+/// These macros can resolve type annotations to specific declarations, and
+/// inspect type hierarchies, but they cannot inspect the declarations on those
+/// type annotations, since new declarations could still be added in this phase.
+abstract class DeclarationMacro implements Macro {}
+
+/// The marker interface for macros that are only allowed to implement or wrap
+/// existing declarations in the program. They cannot introduce any new
+/// declarations that are visible to the program, but are allowed to add
+/// declarations that only they can see.
+///
+/// These macros run after all other types of macros.
+///
+/// These macros can fully reflect on the program since the static shape is
+/// fully defined by the time they run.
+abstract class DefinitionMacro implements Macro {}
+
+/// The interface for [DeclarationMacro]s that can be applied to fields.
+abstract class FieldDeclarationMacro implements DeclarationMacro {
+ void visitFieldDeclaration(
+ ast.FieldDeclaration declaration,
+ ClassDeclarationBuilder builder,
+ );
+}
+
+/// The marker interface for all types of macros.
+abstract class Macro {}
+
+/// The marker interface for macros that are allowed to contribute new type
+/// declarations into the program.
+///
+/// These macros run before all other types of macros.
+///
+/// In exchange for the power to add new type declarations, these macros have
+/// limited introspections capabilities, since new types can be added in this
+/// phase you cannot follow type references back to their declarations.
+abstract class TypeMacro implements Macro {}
diff --git a/pkg/analyzer/lib/src/macro/builders/observable.dart b/pkg/analyzer/lib/src/macro/builders/observable.dart
new file mode 100644
index 0000000..6857d34
--- /dev/null
+++ b/pkg/analyzer/lib/src/macro/builders/observable.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart' as ast;
+import 'package:analyzer/src/macro/api/code.dart';
+import 'package:analyzer/src/macro/api/macro.dart';
+
+class ObservableMacro implements FieldDeclarationMacro {
+ const ObservableMacro();
+
+ @override
+ void visitFieldDeclaration(
+ ast.FieldDeclaration node,
+ ClassDeclarationBuilder builder,
+ ) {
+ var typeNode = node.fields.type;
+ if (typeNode == null) {
+ throw ArgumentError('@observable can only annotate typed fields.');
+ }
+ var typeCode = builder.typeAnnotationCode(typeNode);
+
+ var fields = node.fields.variables;
+ for (var field in fields) {
+ var name = field.name.name;
+ if (!name.startsWith('_')) {
+ throw ArgumentError(
+ '@observable can only annotate private fields, and it will create '
+ 'public getters and setters for them, but the public field '
+ '$name was annotated.',
+ );
+ }
+ var publicName = name.substring(1);
+
+ var getter = Declaration(
+ '$typeCode get $publicName => $name;',
+ );
+ builder.addToClass(getter);
+
+ var setter = Declaration('''
+set $publicName($typeCode val) {
+ print('Setting $publicName to \${val}');
+ $name = val;
+}''');
+ builder.addToClass(setter);
+ }
+ }
+}
diff --git a/pkg/analyzer/lib/src/macro/impl/macro.dart b/pkg/analyzer/lib/src/macro/impl/macro.dart
new file mode 100644
index 0000000..6459a6c
--- /dev/null
+++ b/pkg/analyzer/lib/src/macro/impl/macro.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/analysis/utilities.dart';
+import 'package:analyzer/dart/ast/ast.dart' as ast;
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/src/dart/ast/ast.dart' as ast;
+import 'package:analyzer/src/macro/api/code.dart';
+import 'package:analyzer/src/macro/api/macro.dart';
+
+class ClassDeclarationBuilderImpl extends DeclarationBuilderImpl
+ implements ClassDeclarationBuilder {
+ final ast.ClassDeclarationImpl node;
+
+ ClassDeclarationBuilderImpl(this.node);
+
+ @override
+ void addToClass(Declaration declaration) {
+ // TODO(scheglov) feature set
+ // TODO(scheglov) throw if errors?
+ var parseResult = parseString(
+ content: 'class ${node.name.name} { $declaration }',
+ );
+ var parsedDeclarations = parseResult.unit.declarations;
+ var parsedClass = parsedDeclarations.single as ast.ClassDeclaration;
+ var parsedMember = parsedClass.members.single;
+ _resetOffsets(parsedMember);
+
+ node.members.add(parsedMember);
+ }
+
+ /// We parsed [node] in the context of some synthetic code string, its
+ /// current offsets are meaningless. So, we reset them for now.
+ static void _resetOffsets(ast.AstNode node) {
+ for (Token? t = node.beginToken;
+ t != null && t != node.endToken;
+ t = t.next) {
+ t.offset = -1;
+ }
+ }
+}
+
+class DeclarationBuilderImpl implements DeclarationBuilder {
+ @override
+ void addToLibrary(Declaration declaration) {
+ // TODO: implement addToLibrary
+ }
+
+ @override
+ Code typeAnnotationCode(ast.TypeAnnotation node) {
+ return Fragment(node.toSource());
+ }
+}
diff --git a/pkg/analyzer/lib/src/summary2/element_builder.dart b/pkg/analyzer/lib/src/summary2/element_builder.dart
index 3737f6e..983c60a 100644
--- a/pkg/analyzer/lib/src/summary2/element_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/element_builder.dart
@@ -89,6 +89,34 @@
}
}
+ /// Build elements for [members] and add into the [element].
+ void buildMacroClassMembers(
+ ClassElementImpl element,
+ List<ClassMember> members,
+ ) {
+ var holder = _buildClassMembers(element, members);
+
+ for (var newElement in holder.propertyAccessors) {
+ newElement.isFromMacro = true;
+ element.accessors.add(newElement);
+ }
+
+ for (var newElement in holder.constructors) {
+ newElement.isFromMacro = true;
+ element.constructors.add(newElement);
+ }
+
+ for (var newElement in holder.properties.whereType<FieldElementImpl>()) {
+ newElement.isFromMacro = true;
+ element.fields.add(newElement);
+ }
+
+ for (var newElement in holder.methods) {
+ newElement.isFromMacro = true;
+ element.methods.add(newElement);
+ }
+ }
+
@override
void visitClassDeclaration(covariant ClassDeclarationImpl node) {
var nameNode = node.name;
@@ -868,7 +896,7 @@
}
_EnclosingContext _buildClassMembers(
- ElementImpl element, NodeList<ClassMember> members) {
+ ElementImpl element, List<ClassMember> members) {
var hasConstConstructor = members.any((e) {
return e is ConstructorDeclaration && e.constKeyword != null;
});
diff --git a/pkg/analyzer/lib/src/summary2/element_flags.dart b/pkg/analyzer/lib/src/summary2/element_flags.dart
index e808000..f43df16 100644
--- a/pkg/analyzer/lib/src/summary2/element_flags.dart
+++ b/pkg/analyzer/lib/src/summary2/element_flags.dart
@@ -8,12 +8,14 @@
class ClassElementFlags {
static const int _isAbstract = 1 << 0;
- static const int _isMixinApplication = 1 << 1;
- static const int _isSimplyBounded = 1 << 2;
+ static const int _isFromMacro = 1 << 1;
+ static const int _isMixinApplication = 1 << 2;
+ static const int _isSimplyBounded = 1 << 3;
static void read(SummaryDataReader reader, ClassElementImpl element) {
var byte = reader.readByte();
element.isAbstract = (byte & _isAbstract) != 0;
+ element.isFromMacro = (byte & _isFromMacro) != 0;
element.isMixinApplication = (byte & _isMixinApplication) != 0;
element.isSimplyBounded = (byte & _isSimplyBounded) != 0;
}
@@ -21,6 +23,7 @@
static void write(BufferedSink sink, ClassElementImpl element) {
var result = 0;
result |= element.isAbstract ? _isAbstract : 0;
+ result |= element.isFromMacro ? _isFromMacro : 0;
result |= element.isMixinApplication ? _isMixinApplication : 0;
result |= element.isSimplyBounded ? _isSimplyBounded : 0;
sink.writeByte(result);
@@ -60,8 +63,9 @@
static const int _isCovariant = 1 << 5;
static const int _isExternal = 1 << 6;
static const int _isFinal = 1 << 7;
- static const int _isLate = 1 << 8;
- static const int _isStatic = 1 << 9;
+ static const int _isFromMacro = 1 << 8;
+ static const int _isLate = 1 << 9;
+ static const int _isStatic = 1 << 10;
static void read(SummaryDataReader reader, FieldElementImpl element) {
var byte = reader.readUInt30();
@@ -73,6 +77,7 @@
element.isCovariant = (byte & _isCovariant) != 0;
element.isExternal = (byte & _isExternal) != 0;
element.isFinal = (byte & _isFinal) != 0;
+ element.isFromMacro = (byte & _isFromMacro) != 0;
element.isLate = (byte & _isLate) != 0;
element.isStatic = (byte & _isStatic) != 0;
}
@@ -87,6 +92,7 @@
result |= element.isCovariant ? _isCovariant : 0;
result |= element.isExternal ? _isExternal : 0;
result |= element.isFinal ? _isFinal : 0;
+ result |= element.isFromMacro ? _isFromMacro : 0;
result |= element.isLate ? _isLate : 0;
result |= element.isStatic ? _isStatic : 0;
sink.writeUInt30(result);
@@ -161,8 +167,9 @@
static const int _isAbstract = 1 << 1;
static const int _isAsynchronous = 1 << 2;
static const int _isExternal = 1 << 3;
- static const int _isGenerator = 1 << 4;
- static const int _isStatic = 1 << 5;
+ static const int _isFromMacro = 1 << 4;
+ static const int _isGenerator = 1 << 5;
+ static const int _isStatic = 1 << 6;
static void read(SummaryDataReader reader, MethodElementImpl element) {
var byte = reader.readByte();
@@ -170,6 +177,7 @@
element.isAbstract = (byte & _isAbstract) != 0;
element.isAsynchronous = (byte & _isAsynchronous) != 0;
element.isExternal = (byte & _isExternal) != 0;
+ element.isFromMacro = (byte & _isFromMacro) != 0;
element.isGenerator = (byte & _isGenerator) != 0;
element.isStatic = (byte & _isStatic) != 0;
}
@@ -180,6 +188,7 @@
result |= element.isAbstract ? _isAbstract : 0;
result |= element.isAsynchronous ? _isAsynchronous : 0;
result |= element.isExternal ? _isExternal : 0;
+ result |= element.isFromMacro ? _isFromMacro : 0;
result |= element.isGenerator ? _isGenerator : 0;
result |= element.isStatic ? _isStatic : 0;
sink.writeByte(result);
@@ -217,8 +226,9 @@
static const int _isAbstract = 1 << 3;
static const int _isAsynchronous = 1 << 4;
static const int _isExternal = 1 << 5;
- static const int _isGenerator = 1 << 6;
- static const int _isStatic = 1 << 7;
+ static const int _isFromMacro = 1 << 6;
+ static const int _isGenerator = 1 << 7;
+ static const int _isStatic = 1 << 8;
static void read(
SummaryDataReader reader,
@@ -231,6 +241,7 @@
element.isAbstract = (byte & _isAbstract) != 0;
element.isAsynchronous = (byte & _isAsynchronous) != 0;
element.isExternal = (byte & _isExternal) != 0;
+ element.isFromMacro = (byte & _isFromMacro) != 0;
element.isGenerator = (byte & _isGenerator) != 0;
element.isStatic = (byte & _isStatic) != 0;
}
@@ -243,6 +254,7 @@
result |= element.isAbstract ? _isAbstract : 0;
result |= element.isAsynchronous ? _isAsynchronous : 0;
result |= element.isExternal ? _isExternal : 0;
+ result |= element.isFromMacro ? _isFromMacro : 0;
result |= element.isGenerator ? _isGenerator : 0;
result |= element.isStatic ? _isStatic : 0;
sink.writeUInt30(result);
diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart
index ba1e230..e500aa1 100644
--- a/pkg/analyzer/lib/src/summary2/library_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/library_builder.dart
@@ -9,6 +9,8 @@
import 'package:analyzer/src/dart/ast/mixin_super_invoked_names.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
+import 'package:analyzer/src/macro/builders/observable.dart' as macro;
+import 'package:analyzer/src/macro/impl/macro.dart' as macro;
import 'package:analyzer/src/summary2/combinator.dart';
import 'package:analyzer/src/summary2/constructor_initializer_resolver.dart';
import 'package:analyzer/src/summary2/default_value_resolver.dart';
@@ -159,18 +161,69 @@
void resolveTypes(NodesToBuildType nodesToBuildType) {
for (var linkingUnit in units) {
- var resolver = ReferenceResolver(
- linker,
- nodesToBuildType,
- linker.elementFactory,
- element,
- linkingUnit.reference,
- linkingUnit.node.featureSet.isEnabled(Feature.non_nullable),
- );
+ var resolver = _newTypeReferenceResolver(nodesToBuildType, linkingUnit);
linkingUnit.node.accept(resolver);
}
}
+ /// Run built-in declaration macros.
+ void runDeclarationMacros() {
+ bool hasMacroAnnotation(ast.AnnotatedNode node, String name) {
+ for (var annotation in node.metadata) {
+ var nameNode = annotation.name;
+ if (nameNode is ast.SimpleIdentifier &&
+ annotation.arguments == null &&
+ annotation.constructorName == null &&
+ nameNode.name == name) {
+ var nameElement = element.scope.lookup(name).getter;
+ return nameElement != null &&
+ nameElement.library?.name == 'analyzer.macro.annotations';
+ }
+ }
+ return false;
+ }
+
+ for (var linkingUnit in units) {
+ for (var declaration in linkingUnit.node.declarations) {
+ if (declaration is ast.ClassDeclarationImpl) {
+ var members = declaration.members.toList();
+ for (var member in members) {
+ if (member is ast.FieldDeclarationImpl) {
+ if (hasMacroAnnotation(member, 'observable')) {
+ macro.ObservableMacro().visitFieldDeclaration(
+ member,
+ macro.ClassDeclarationBuilderImpl(declaration),
+ );
+ }
+ }
+ }
+
+ var newMembers = declaration.members.sublist(members.length);
+ if (newMembers.isNotEmpty) {
+ var elementBuilder = ElementBuilder(
+ libraryBuilder: this,
+ unitReference: linkingUnit.reference,
+ unitElement: linkingUnit.element,
+ );
+ var classElement = declaration.declaredElement as ClassElementImpl;
+ elementBuilder.buildMacroClassMembers(classElement, newMembers);
+
+ // TODO(scheglov) extract
+ {
+ var nodesToBuildType = NodesToBuildType();
+ var resolver =
+ _newTypeReferenceResolver(nodesToBuildType, linkingUnit);
+ for (var newMember in newMembers) {
+ newMember.accept(resolver);
+ }
+ TypesBuilder(linker).build(nodesToBuildType);
+ }
+ }
+ }
+ }
+ }
+ }
+
void storeExportScope() {
exports = exportScope.map.values.toList();
linker.elementFactory.setExportsOfLibrary('$uri', exports);
@@ -205,6 +258,21 @@
}
}
+ ReferenceResolver _newTypeReferenceResolver(
+ NodesToBuildType nodesToBuildType,
+ LinkingUnit linkingUnit,
+ ) {
+ /// TODO(scheglov) Do we need all these parameters?
+ return ReferenceResolver(
+ linker,
+ nodesToBuildType,
+ linker.elementFactory,
+ element,
+ linkingUnit.reference,
+ linkingUnit.node.featureSet.isEnabled(Feature.non_nullable),
+ );
+ }
+
static void build(Linker linker, LinkInputLibrary inputLibrary) {
var elementFactory = linker.elementFactory;
diff --git a/pkg/analyzer/lib/src/summary2/link.dart b/pkg/analyzer/lib/src/summary2/link.dart
index f0b0174..d0fe559 100644
--- a/pkg/analyzer/lib/src/summary2/link.dart
+++ b/pkg/analyzer/lib/src/summary2/link.dart
@@ -93,6 +93,7 @@
_createTypeSystem();
_buildEnumChildren();
_resolveTypes();
+ _runDeclarationMacros();
_performTopLevelInference();
_resolveConstructors();
_resolveConstantInitializers();
@@ -213,6 +214,12 @@
TypesBuilder(this).build(nodesToBuildType);
}
+ void _runDeclarationMacros() {
+ for (var library in builders.values) {
+ library.runDeclarationMacros();
+ }
+ }
+
void _writeLibraries() {
var bundleWriter = BundleWriter(
elementFactory.dynamicRef,
diff --git a/pkg/analyzer/test/src/dart/resolution/macro_test.dart b/pkg/analyzer/test/src/dart/resolution/macro_test.dart
new file mode 100644
index 0000000..4dd8ebc
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/resolution/macro_test.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../../generated/elements_types_mixin.dart';
+import 'context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(MacroResolutionTest);
+ });
+}
+
+@reflectiveTest
+class MacroResolutionTest extends PubPackageResolutionTest
+ with ElementsTypesMixin {
+ @override
+ void setUp() {
+ super.setUp();
+
+ newFile('$testPackageLibPath/macro_annotations.dart', content: r'''
+library analyzer.macro.annotations;
+const observable = 0;
+''');
+ }
+
+ test_observable() async {
+ await assertErrorsInCode(r'''
+import 'macro_annotations.dart';
+
+class A {
+ @observable
+ int _foo = 0;
+}
+
+void f(A a) {
+ a.foo;
+ a.foo = 2;
+}
+''', [
+ error(HintCode.UNUSED_FIELD, 64, 4),
+ ]);
+ }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/test_all.dart b/pkg/analyzer/test/src/dart/resolution/test_all.dart
index 5f1c71d..08c23ad 100644
--- a/pkg/analyzer/test/src/dart/resolution/test_all.dart
+++ b/pkg/analyzer/test/src/dart/resolution/test_all.dart
@@ -42,6 +42,7 @@
import 'library_element_test.dart' as library_element;
import 'local_function_test.dart' as local_function;
import 'local_variable_test.dart' as local_variable;
+import 'macro_test.dart' as macro;
import 'metadata_test.dart' as metadata;
import 'method_declaration_test.dart' as method_declaration;
import 'method_invocation_test.dart' as method_invocation;
@@ -101,6 +102,7 @@
library_element.main();
local_function.main();
local_variable.main();
+ macro.main();
metadata.main();
method_declaration.main();
method_invocation.main();
diff --git a/pkg/analyzer/test/src/summary/element_text.dart b/pkg/analyzer/test/src/summary/element_text.dart
index acbd956..e7884a5 100644
--- a/pkg/analyzer/test/src/summary/element_text.dart
+++ b/pkg/analyzer/test/src/summary/element_text.dart
@@ -645,6 +645,8 @@
}
void _writePropertyAccessorElement(PropertyAccessorElement e) {
+ e as PropertyAccessorElementImpl;
+
PropertyInducingElement variable = e.variable;
expect(variable, isNotNull);
@@ -667,7 +669,7 @@
}
}
- if (e.isSynthetic) {
+ if (e.isSynthetic || e.isFromMacro) {
expect(e.nameOffset, -1);
} else {
expect(e.nameOffset, isPositive);
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index caee60d..8c5f16e 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -21246,6 +21246,58 @@
''');
}
+ test_macro_observable() async {
+ addLibrarySource('/macro_annotations.dart', r'''
+library analyzer.macro.annotations;
+const observable = 0;
+''');
+ var library = await checkLibrary(r'''
+import 'macro_annotations.dart';
+class A {
+ @observable
+ int _f = 0;
+}
+''');
+ checkElementText(library, r'''
+library
+ imports
+ macro_annotations.dart
+ definingUnit
+ classes
+ class A @39
+ fields
+ _f @63
+ metadata
+ Annotation
+ atSign: @ @45
+ element: macro_annotations.dart::@getter::observable
+ name: SimpleIdentifier
+ staticElement: macro_annotations.dart::@getter::observable
+ staticType: null
+ token: observable @46
+ type: int
+ synthetic f @-1
+ type: int
+ constructors
+ synthetic @-1
+ accessors
+ synthetic get _f @-1
+ returnType: int
+ synthetic set _f @-1
+ parameters
+ requiredPositional __f @-1
+ type: int
+ returnType: void
+ get f @-1
+ returnType: int
+ set f @-1
+ parameters
+ requiredPositional val @-1
+ type: int
+ returnType: void
+''');
+ }
+
test_main_class() async {
var library = await checkLibrary('class main {}');
checkElementText(library, r'''