Add annotation-based creation location tracking
Closes https://github.com/dart-lang/sdk/pull/63011 GitOrigin-RevId: 926534c2f5d3cd1e9629cf25f6b028ab34c00e64 Change-Id: I88460a060faabeae8f611e88f23b2eb5fc99f21a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491702 Reviewed-by: Daco Harkes <dacoharkes@google.com> Reviewed-by: Jens Johansen <jensj@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
efff5f70c1
commit
9e8fdef17c
@@ -12,7 +12,7 @@ import '../verifier.dart';
|
||||
import 'changed_structure_notifier.dart';
|
||||
|
||||
class TargetFlags {
|
||||
final bool trackWidgetCreation;
|
||||
final bool trackCreationLocations;
|
||||
final bool supportMirrors;
|
||||
|
||||
/// Whether the backend expects closure contexts to be present in the AST.
|
||||
@@ -32,7 +32,7 @@ class TargetFlags {
|
||||
final bool includeUnsupportedPlatformLibraryStubs;
|
||||
|
||||
const TargetFlags({
|
||||
this.trackWidgetCreation = false,
|
||||
this.trackCreationLocations = false,
|
||||
this.supportMirrors = true,
|
||||
this.isClosureContextLoweringEnabled = false,
|
||||
this.constKeepLocalsIndicator,
|
||||
@@ -43,7 +43,7 @@ class TargetFlags {
|
||||
bool operator ==(other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is TargetFlags &&
|
||||
trackWidgetCreation == other.trackWidgetCreation &&
|
||||
trackCreationLocations == other.trackCreationLocations &&
|
||||
supportMirrors == other.supportMirrors &&
|
||||
includeUnsupportedPlatformLibraryStubs ==
|
||||
other.includeUnsupportedPlatformLibraryStubs &&
|
||||
@@ -53,7 +53,7 @@ class TargetFlags {
|
||||
@override
|
||||
int get hashCode {
|
||||
int hash = 485786;
|
||||
hash = 0x3fffffff & (hash * 31 + (hash ^ trackWidgetCreation.hashCode));
|
||||
hash = 0x3fffffff & (hash * 31 + (hash ^ trackCreationLocations.hashCode));
|
||||
hash = 0x3fffffff & (hash * 31 + (hash ^ supportMirrors.hashCode));
|
||||
hash =
|
||||
0x3fffffff &
|
||||
@@ -340,7 +340,11 @@ abstract class Target {
|
||||
/// This transformation is not applied when compiling full kernel programs to
|
||||
/// prevent affecting the internal invariants of the compiler and accidentally
|
||||
/// slowing down compilation.
|
||||
void performOutlineTransformations(Component component) {}
|
||||
void performOutlineTransformations(
|
||||
Component component, {
|
||||
List<Library>? libraries,
|
||||
ChangedStructureNotifier? changedStructureNotifier,
|
||||
}) {}
|
||||
|
||||
/// Perform target-specific operations on the [Component] storing the outlines
|
||||
/// when generating summaries.
|
||||
@@ -821,7 +825,7 @@ class TestTargetFlags extends TargetFlags {
|
||||
final Set<String> unsupportedDartLibraries;
|
||||
|
||||
const TestTargetFlags({
|
||||
bool trackWidgetCreation = false,
|
||||
bool trackCreationLocations = false,
|
||||
this.forceLateLoweringsForTesting,
|
||||
this.forceLateLoweringSentinelForTesting,
|
||||
this.forceStaticFieldLoweringForTesting,
|
||||
@@ -831,7 +835,7 @@ class TestTargetFlags extends TargetFlags {
|
||||
this.unsupportedDartLibraries = const {},
|
||||
bool isClosureContextLoweringEnabled = false,
|
||||
}) : super(
|
||||
trackWidgetCreation: trackWidgetCreation,
|
||||
trackCreationLocations: trackCreationLocations,
|
||||
isClosureContextLoweringEnabled: isClosureContextLoweringEnabled,
|
||||
);
|
||||
}
|
||||
@@ -1040,8 +1044,16 @@ class TargetWrapper extends Target {
|
||||
}
|
||||
|
||||
@override
|
||||
void performOutlineTransformations(Component component) {
|
||||
_target.performOutlineTransformations(component);
|
||||
void performOutlineTransformations(
|
||||
Component component, {
|
||||
List<Library>? libraries,
|
||||
ChangedStructureNotifier? changedStructureNotifier,
|
||||
}) {
|
||||
_target.performOutlineTransformations(
|
||||
component,
|
||||
libraries: libraries,
|
||||
changedStructureNotifier: changedStructureNotifier,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1131,10 +1143,10 @@ mixin SummaryMixin on Target {
|
||||
super.performOutlineComponentOperations(component);
|
||||
if (!excludeNonSources) return;
|
||||
|
||||
List<Library> libraries = new List.of(component.libraries);
|
||||
List<Library> componentLibraries = new List.of(component.libraries);
|
||||
component.libraries.clear();
|
||||
Set<Uri> include = sources.toSet();
|
||||
for (Library library in libraries) {
|
||||
for (Library library in componentLibraries) {
|
||||
if (include.contains(library.importUri)) {
|
||||
component.libraries.add(library);
|
||||
} else {
|
||||
|
||||
@@ -61,7 +61,6 @@ void _maybeAddCreationLocationArgument(
|
||||
Arguments arguments,
|
||||
FunctionNode function,
|
||||
Expression creationLocation,
|
||||
Class locationClass,
|
||||
) {
|
||||
if (_hasNamedArgument(arguments, _creationLocationParameterName)) {
|
||||
return;
|
||||
@@ -120,15 +119,6 @@ bool _maybeAddNamedParameter(
|
||||
/// and this is used as the location value for all Widget constructor
|
||||
/// invocations within the method.
|
||||
class _WidgetCallSiteTransformer extends Transformer {
|
||||
/// The [Widget] class defined in the `package:flutter` library.
|
||||
///
|
||||
/// Used to perform is-tests to determine whether Dart constructor calls are
|
||||
/// creating [Widget] objects.
|
||||
final Class _widgetClass;
|
||||
|
||||
/// The _Location class defined in the `package:flutter` library.
|
||||
final Class _locationClass;
|
||||
|
||||
final WidgetCreatorTracker _tracker;
|
||||
|
||||
/// The creation location parameter of the extension factory method enclosing
|
||||
@@ -151,13 +141,8 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
/// of the library.
|
||||
Library? _currentLibrary;
|
||||
|
||||
_WidgetCallSiteTransformer({
|
||||
required Class widgetClass,
|
||||
required Class locationClass,
|
||||
required WidgetCreatorTracker tracker,
|
||||
}) : _widgetClass = widgetClass,
|
||||
_locationClass = locationClass,
|
||||
_tracker = tracker;
|
||||
_WidgetCallSiteTransformer({required WidgetCreatorTracker tracker})
|
||||
: _tracker = tracker;
|
||||
|
||||
/// Builds a call to the const constructor of the _Location
|
||||
/// object specifying the location where a constructor call was made and
|
||||
@@ -169,7 +154,12 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
/// constructor call but it is convenient to bundle the location and names
|
||||
/// of the parameters passed in so that tools can show parameter locations
|
||||
/// without re-parsing the source code.
|
||||
ConstructorInvocation _constructLocation(Location location, {String? name}) {
|
||||
|
||||
ConstructorInvocation _constructLocation(
|
||||
Location location, {
|
||||
required Class locationClass,
|
||||
String? name,
|
||||
}) {
|
||||
final List<NamedExpression> arguments = <NamedExpression>[
|
||||
new NamedExpression('file', new StringLiteral(location.file.toString())),
|
||||
new NamedExpression('line', new IntLiteral(location.line)),
|
||||
@@ -178,7 +168,7 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
];
|
||||
|
||||
return new ConstructorInvocation(
|
||||
_locationClass.constructors.first,
|
||||
locationClass.constructors.first,
|
||||
new Arguments(<Expression>[], named: arguments),
|
||||
isConst: true,
|
||||
);
|
||||
@@ -210,10 +200,6 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
return node;
|
||||
}
|
||||
|
||||
bool _isSubclassOfWidget(Class clazz) {
|
||||
return _tracker._isSubclassOf(clazz, _widgetClass);
|
||||
}
|
||||
|
||||
bool _isWidgetFactory(Procedure node) {
|
||||
return node.isExtensionMember &&
|
||||
_hasNamedParameter(node.function, _creationLocationParameterName);
|
||||
@@ -225,7 +211,10 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
final Procedure target = node.target;
|
||||
if (target.isFactory) {
|
||||
final Class constructedClass = target.enclosingClass!;
|
||||
if (!_isSubclassOfWidget(constructedClass)) {
|
||||
final _TrackingClasses? tracking = _tracker._getTrackingClasses(
|
||||
constructedClass,
|
||||
);
|
||||
if (tracking == null) {
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -234,11 +223,16 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
target.function,
|
||||
constructedClass: constructedClass,
|
||||
isConst: node.isConst,
|
||||
locationClass: tracking.locationClass,
|
||||
);
|
||||
return node;
|
||||
}
|
||||
if (_isWidgetFactory(target)) {
|
||||
_addLocationArgument(node, target.function);
|
||||
if (_isWidgetFactory(target) && _tracker._locationClass != null) {
|
||||
_addLocationArgument(
|
||||
node,
|
||||
target.function,
|
||||
locationClass: _tracker._locationClass!,
|
||||
);
|
||||
return node;
|
||||
}
|
||||
return node;
|
||||
@@ -249,6 +243,7 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
FunctionNode function, {
|
||||
Class? constructedClass,
|
||||
bool isConst = false,
|
||||
required Class locationClass,
|
||||
}) {
|
||||
Expression? location = _currentExtensionFactoryLocationParameter;
|
||||
if (location == null ||
|
||||
@@ -261,14 +256,10 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
function,
|
||||
constructedClass,
|
||||
isConst: isConst,
|
||||
locationClass: locationClass,
|
||||
);
|
||||
}
|
||||
_maybeAddCreationLocationArgument(
|
||||
node.arguments,
|
||||
function,
|
||||
location,
|
||||
_locationClass,
|
||||
);
|
||||
_maybeAddCreationLocationArgument(node.arguments, function, location);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -277,7 +268,10 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
|
||||
final Constructor constructor = node.target;
|
||||
final Class constructedClass = constructor.enclosingClass;
|
||||
if (!_isSubclassOfWidget(constructedClass)) {
|
||||
final _TrackingClasses? tracking = _tracker._getTrackingClasses(
|
||||
constructedClass,
|
||||
);
|
||||
if (tracking == null) {
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -286,6 +280,7 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
constructor.function,
|
||||
constructedClass: constructedClass,
|
||||
isConst: node.isConst,
|
||||
locationClass: tracking.locationClass,
|
||||
);
|
||||
return node;
|
||||
}
|
||||
@@ -295,6 +290,7 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
FunctionNode function,
|
||||
Class? constructedClass, {
|
||||
bool isConst = false,
|
||||
required Class locationClass,
|
||||
}) {
|
||||
assert(constructedClass != null || !isConst);
|
||||
|
||||
@@ -302,9 +298,9 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
// argument to the factory constructor rather than the location
|
||||
if (constructedClass != null &&
|
||||
_currentFactory != null &&
|
||||
_tracker._isSubclassOf(
|
||||
_tracker._isSubclassWhere(
|
||||
constructedClass,
|
||||
_currentFactory!.enclosingClass!,
|
||||
(Class c) => c == _currentFactory!.enclosingClass!,
|
||||
) &&
|
||||
// If the constructor invocation is constant we cannot refer to the
|
||||
// location parameter of the surrounding factory since it isn't a
|
||||
@@ -319,8 +315,13 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
if (node.location == null) {
|
||||
return new NullLiteral();
|
||||
}
|
||||
|
||||
return _constructLocation(
|
||||
node.location!,
|
||||
locationClass: locationClass,
|
||||
name:
|
||||
constructedClass?.name ??
|
||||
// For extension factory methods we use the name of the method.
|
||||
@@ -360,12 +361,20 @@ class _WidgetCallSiteTransformer extends Transformer {
|
||||
/// invocations within the method.
|
||||
class WidgetCreatorTracker {
|
||||
bool _foundClasses = false;
|
||||
late Class _widgetClass;
|
||||
late Class _locationClass;
|
||||
|
||||
/// Marker interface indicating that a private _location field is
|
||||
/// available.
|
||||
late Class _hasCreationLocationClass;
|
||||
/// The [Widget] class defined in the `package:flutter` library.
|
||||
///
|
||||
/// Used to perform is-tests to determine whether Dart constructor calls are
|
||||
/// creating [Widget] objects.
|
||||
Class? _widgetClass;
|
||||
|
||||
/// The _Location class defined in the `package:flutter` library.
|
||||
Class? _locationClass;
|
||||
|
||||
/// The _HasCreationLocation class defined in the `package:flutter` library.
|
||||
///
|
||||
/// Marker interface indicating that a private _location field is available.
|
||||
Class? _hasCreationLocationClass;
|
||||
|
||||
/// Annotation class used to mark an extension method as a "Widget factory".
|
||||
///
|
||||
@@ -374,20 +383,27 @@ class WidgetCreatorTracker {
|
||||
/// method.
|
||||
Class? _widgetFactoryClass;
|
||||
|
||||
void _resolveFlutterClasses(Iterable<Library> libraries) {
|
||||
/// The _HasCreationLocation class defined in the `dart:developer` library.
|
||||
Class? _developerHasCreationLocationClass;
|
||||
|
||||
/// The CreationLocation class defined in the `dart:developer` library.
|
||||
Class? _developerCreationLocationClass;
|
||||
|
||||
void _resolveWellKnownClasses(Iterable<Library> libraries) {
|
||||
// If the Widget or Debug location classes have been updated we need to get
|
||||
// the latest version
|
||||
bool foundWidgetClass = false;
|
||||
bool foundHasCreationLocationClass = false;
|
||||
bool foundLocationClass = false;
|
||||
|
||||
for (Library library in libraries) {
|
||||
final Uri importUri = library.importUri;
|
||||
|
||||
// Legacy Case: Search for hardcoded Flutter classes.
|
||||
// TODO(http://dartbug.com/63225): Remove this once Flutter is migrated
|
||||
// to the new API.
|
||||
if (importUri.isScheme('package')) {
|
||||
if (importUri.path == 'flutter/src/widgets/framework.dart') {
|
||||
for (Class class_ in library.classes) {
|
||||
if (class_.name == 'Widget') {
|
||||
_widgetClass = class_;
|
||||
foundWidgetClass = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -395,10 +411,8 @@ class WidgetCreatorTracker {
|
||||
for (Class class_ in library.classes) {
|
||||
if (class_.name == '_HasCreationLocation') {
|
||||
_hasCreationLocationClass = class_;
|
||||
foundHasCreationLocationClass = true;
|
||||
} else if (class_.name == '_Location') {
|
||||
_locationClass = class_;
|
||||
foundLocationClass = true;
|
||||
} else if (class_.name == '_WidgetFactory') {
|
||||
_widgetFactoryClass = class_;
|
||||
}
|
||||
@@ -406,43 +420,62 @@ class WidgetCreatorTracker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New Case: Search for classes in `dart:developer`
|
||||
if (importUri.isScheme('dart') && importUri.path == 'developer') {
|
||||
for (Class class_ in library.classes) {
|
||||
if (class_.name == '_HasCreationLocation') {
|
||||
_developerHasCreationLocationClass = class_;
|
||||
} else if (class_.name == 'CreationLocation') {
|
||||
_developerCreationLocationClass = class_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(johnniwinther): Require the [_widgetFactoryClass] once the
|
||||
// `widgetFactory` is stable in flutter.
|
||||
_foundClasses =
|
||||
foundWidgetClass && foundHasCreationLocationClass && foundLocationClass;
|
||||
(_widgetClass != null &&
|
||||
_hasCreationLocationClass != null &&
|
||||
_locationClass != null) ||
|
||||
(_developerHasCreationLocationClass != null &&
|
||||
_developerCreationLocationClass != null);
|
||||
}
|
||||
|
||||
/// Modify [clazz] to add a field named [_locationFieldName] that is the
|
||||
/// first parameter of all constructors of the class.
|
||||
/// Modify [clazz] to add the location field that is
|
||||
/// the first parameter of all constructors of the class.
|
||||
///
|
||||
/// This method should only be called for classes that implement but do not
|
||||
/// extend [Widget].
|
||||
void _transformClassImplementingWidget(
|
||||
/// extend the tracking base class.
|
||||
void _transformClassImplementingTrackingClass(
|
||||
Class clazz,
|
||||
_TrackingClasses tracking,
|
||||
ChangedStructureNotifier? changedStructureNotifier,
|
||||
) {
|
||||
if (clazz.fields.any(
|
||||
(Field field) => field.name.text == _locationFieldName,
|
||||
(Field field) => field.name.text == tracking.locationFieldName,
|
||||
)) {
|
||||
// This class has already been transformed. Skip
|
||||
return;
|
||||
}
|
||||
clazz.implementedTypes.add(
|
||||
new Supertype(_hasCreationLocationClass, <DartType>[]),
|
||||
new Supertype(tracking.hasCreationLocationClass, <DartType>[]),
|
||||
);
|
||||
changedStructureNotifier?.registerClassHierarchyChange(clazz);
|
||||
|
||||
// We intentionally use the library context of the _HasCreationLocation
|
||||
// class for the private field even if [clazz] is in a different library
|
||||
// so that all classes implementing Widget behave consistently.
|
||||
// so that all classes implementing the tracking class behave consistently.
|
||||
final Name fieldName = new Name(
|
||||
_locationFieldName,
|
||||
_hasCreationLocationClass.enclosingLibrary,
|
||||
tracking.locationFieldName,
|
||||
tracking.hasCreationLocationClass.enclosingLibrary,
|
||||
);
|
||||
final Field locationField = new Field.immutable(
|
||||
fieldName,
|
||||
type: new InterfaceType(_locationClass, clazz.enclosingLibrary.nullable),
|
||||
type: new InterfaceType(
|
||||
tracking.locationClass,
|
||||
clazz.enclosingLibrary.nullable,
|
||||
),
|
||||
isFinal: true,
|
||||
fieldReference: clazz.reference.canonicalName
|
||||
?.getChildFromFieldWithName(fieldName)
|
||||
@@ -470,7 +503,7 @@ class WidgetCreatorTracker {
|
||||
final VariableDeclaration variable = new VariableDeclaration(
|
||||
_creationLocationParameterName,
|
||||
type: new InterfaceType(
|
||||
_locationClass,
|
||||
tracking.locationClass,
|
||||
clazz.enclosingLibrary.nullable,
|
||||
),
|
||||
initializer: new NullLiteral(),
|
||||
@@ -492,7 +525,6 @@ class WidgetCreatorTracker {
|
||||
initializer.arguments,
|
||||
initializer.target.function,
|
||||
new VariableGet(variable),
|
||||
_locationClass,
|
||||
);
|
||||
hasRedirectingInitializer = true;
|
||||
break;
|
||||
@@ -512,7 +544,7 @@ class WidgetCreatorTracker {
|
||||
// constructor.initializers.add(new AssertInitializer(
|
||||
// new AssertStatement(
|
||||
// new IsExpression(
|
||||
// new VariableGet(variable), _locationClass.thisType),
|
||||
// new VariableGet(variable), tracking.locationClass.thisType),
|
||||
// conditionStartOffset: constructor.fileOffset,
|
||||
// conditionEndOffset: constructor.fileOffset,
|
||||
// )));
|
||||
@@ -525,7 +557,7 @@ class WidgetCreatorTracker {
|
||||
|
||||
/// Transform the given [libraries].
|
||||
///
|
||||
/// The libraries from [module] is searched for the Widget class,
|
||||
/// The [moduleLibraries] are searched for the Widget class,
|
||||
/// the _Location class, the _HasCreationLocation class and the
|
||||
/// _WidgetFactory class.
|
||||
/// If the component does not contain them, the ones from a previous run is
|
||||
@@ -537,18 +569,18 @@ class WidgetCreatorTracker {
|
||||
/// compilation where the class hierarchy is kept between compiles and thus
|
||||
/// has to be kept up to date.
|
||||
void transform(
|
||||
Component module,
|
||||
List<Library> libraries,
|
||||
List<Library> moduleLibraries,
|
||||
ChangedStructureNotifier? changedStructureNotifier,
|
||||
) {
|
||||
if (libraries.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_resolveFlutterClasses(module.libraries);
|
||||
_resolveWellKnownClasses(moduleLibraries);
|
||||
|
||||
if (!_foundClasses) {
|
||||
// This application doesn't actually use the package:flutter library.
|
||||
// Neither package:flutter nor dart:developer tracking classes found.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -579,11 +611,7 @@ class WidgetCreatorTracker {
|
||||
|
||||
// Transform call sites to pass the location parameter.
|
||||
final _WidgetCallSiteTransformer callsiteTransformer =
|
||||
new _WidgetCallSiteTransformer(
|
||||
widgetClass: _widgetClass,
|
||||
locationClass: _locationClass,
|
||||
tracker: this,
|
||||
);
|
||||
new _WidgetCallSiteTransformer(tracker: this);
|
||||
|
||||
for (Library library in libraries) {
|
||||
callsiteTransformer.enterLibrary(library);
|
||||
@@ -592,26 +620,110 @@ class WidgetCreatorTracker {
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSubclassOfWidget(Class clazz) => _isSubclassOf(clazz, _widgetClass);
|
||||
_TrackingClasses? _getTrackingClasses(Class clazz) {
|
||||
// Legacy Case: Check for widget class.
|
||||
if (_isSubclassOfWidget(clazz)) {
|
||||
if (_hasCreationLocationClass != null && _locationClass != null) {
|
||||
return new _TrackingClasses(
|
||||
hasCreationLocationClass: _hasCreationLocationClass!,
|
||||
locationClass: _locationClass!,
|
||||
locationFieldName: _locationFieldName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSubclassOf(Class a, Class b) {
|
||||
// New Case: Check for 'pragma('track-creation-locations')' annotation.
|
||||
if (_hasTrackCreationLocationsPragmaAnnotation(clazz)) {
|
||||
if (_developerHasCreationLocationClass != null &&
|
||||
_developerCreationLocationClass != null) {
|
||||
return new _TrackingClasses(
|
||||
hasCreationLocationClass: _developerHasCreationLocationClass!,
|
||||
locationClass: _developerCreationLocationClass!,
|
||||
locationFieldName: _locationFieldName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _isSubclassOfWidget(Class clazz) {
|
||||
if (_widgetClass == null) return false;
|
||||
return _isSubclassWhere(clazz, (Class c) => c == _widgetClass);
|
||||
}
|
||||
|
||||
bool _hasTrackCreationLocationsPragmaAnnotation(Class clazz) {
|
||||
if (_developerHasCreationLocationClass == null) return false;
|
||||
return _isSubclassWhere(clazz, (Class c) {
|
||||
for (Expression annotation in c.annotations) {
|
||||
// Case before constant evaluation (newly compiled modules).
|
||||
if (annotation is RedirectingFactoryInvocation) {
|
||||
final expression = annotation.expression;
|
||||
|
||||
if (expression is ConstructorInvocation) {
|
||||
final Class enclosingClass = expression.target.enclosingClass;
|
||||
|
||||
if (enclosingClass.name == 'pragma' &&
|
||||
enclosingClass.enclosingLibrary.importUri.toString() ==
|
||||
'dart:core') {
|
||||
if (expression.arguments.positional.isNotEmpty) {
|
||||
final Expression firstArg =
|
||||
expression.arguments.positional.first;
|
||||
if (firstArg is StringLiteral &&
|
||||
firstArg.value == 'track-creation-locations') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Case after constant evaluation (incremental or modular compilation).
|
||||
if (annotation case ConstantExpression(
|
||||
constant: final InstanceConstant constant,
|
||||
)) {
|
||||
final Class enclosingClass = constant.classNode;
|
||||
|
||||
if (enclosingClass.name == 'pragma' &&
|
||||
enclosingClass.enclosingLibrary.importUri.toString() ==
|
||||
'dart:core') {
|
||||
for (final Constant value in constant.fieldValues.values) {
|
||||
if (value case StringConstant(
|
||||
value: 'track-creation-locations',
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
bool _isSubclassWhere(Class a, bool Function(Class b) predicate) {
|
||||
// TODO(askesc): Cache results.
|
||||
// TODO(askesc): Test for subtype rather than subclass.
|
||||
Class? current = a;
|
||||
while (current != null) {
|
||||
if (current == b) return true;
|
||||
if (predicate(current)) return true;
|
||||
current = current.superclass;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _hasWidgetFactoryAnnotation(Procedure node) =>
|
||||
_isAnnotatedWithNamedValueOfType(node, _widgetFactoryClass!);
|
||||
_isAnnotatedWithNamedValueOfType(
|
||||
node,
|
||||
_widgetFactoryClass,
|
||||
_widgetFactoryClass,
|
||||
);
|
||||
|
||||
bool _isAnnotatedWithNamedValueOfType(
|
||||
Annotatable node,
|
||||
Class annotationClass,
|
||||
Class? annotationClass,
|
||||
Class? typeClass,
|
||||
) {
|
||||
if (annotationClass == null || typeClass == null) return false;
|
||||
return node.annotations.any((annotation) {
|
||||
if (annotation is! StaticGet) {
|
||||
return false;
|
||||
@@ -627,7 +739,7 @@ class WidgetCreatorTracker {
|
||||
if (type.nullability == Nullability.nullable) {
|
||||
return false;
|
||||
}
|
||||
return type.classNode == _widgetFactoryClass;
|
||||
return type.classNode == typeClass;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -637,7 +749,8 @@ class WidgetCreatorTracker {
|
||||
Class clazz,
|
||||
ChangedStructureNotifier? changedStructureNotifier,
|
||||
) {
|
||||
if (!_isSubclassOfWidget(clazz) ||
|
||||
final _TrackingClasses? tracking = _getTrackingClasses(clazz);
|
||||
if (tracking == null ||
|
||||
!librariesToBeTransformed.contains(clazz.enclosingLibrary) ||
|
||||
!transformedClasses.add(clazz)) {
|
||||
return;
|
||||
@@ -661,7 +774,7 @@ class WidgetCreatorTracker {
|
||||
new VariableDeclaration(
|
||||
_creationLocationParameterName,
|
||||
type: new InterfaceType(
|
||||
_locationClass,
|
||||
tracking.locationClass,
|
||||
clazz.enclosingLibrary.nullable,
|
||||
),
|
||||
initializer: new NullLiteral(),
|
||||
@@ -672,8 +785,14 @@ class WidgetCreatorTracker {
|
||||
|
||||
// Handle the widget class and classes that implement but do not extend the
|
||||
// widget class.
|
||||
if (!_isSubclassOfWidget(clazz.superclass!)) {
|
||||
_transformClassImplementingWidget(clazz, changedStructureNotifier);
|
||||
if (clazz.superclass == null ||
|
||||
_getTrackingClasses(clazz.superclass!)?.hasCreationLocationClass !=
|
||||
tracking.hasCreationLocationClass) {
|
||||
_transformClassImplementingTrackingClass(
|
||||
clazz,
|
||||
tracking,
|
||||
changedStructureNotifier,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -688,7 +807,7 @@ class WidgetCreatorTracker {
|
||||
final VariableDeclaration variable = new VariableDeclaration(
|
||||
_creationLocationParameterName,
|
||||
type: new InterfaceType(
|
||||
_locationClass,
|
||||
tracking.locationClass,
|
||||
clazz.enclosingLibrary.nullable,
|
||||
),
|
||||
initializer: new NullLiteral(),
|
||||
@@ -717,16 +836,17 @@ class WidgetCreatorTracker {
|
||||
initializer.arguments,
|
||||
initializer.target.function,
|
||||
new VariableGet(variable),
|
||||
_locationClass,
|
||||
);
|
||||
} else if (initializer is SuperInitializer &&
|
||||
_isSubclassOfWidget(initializer.target.enclosingClass)) {
|
||||
_maybeAddCreationLocationArgument(
|
||||
initializer.arguments,
|
||||
initializer.target.function,
|
||||
new VariableGet(variable),
|
||||
_locationClass,
|
||||
);
|
||||
} else if (initializer is SuperInitializer) {
|
||||
final Class superclass = initializer.target.enclosingClass;
|
||||
if (_getTrackingClasses(superclass)?.hasCreationLocationClass ==
|
||||
tracking.hasCreationLocationClass) {
|
||||
_maybeAddCreationLocationArgument(
|
||||
initializer.arguments,
|
||||
initializer.target.function,
|
||||
new VariableGet(variable),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -761,7 +881,7 @@ class WidgetCreatorTracker {
|
||||
new VariableDeclaration(
|
||||
_creationLocationParameterName,
|
||||
type: new InterfaceType(
|
||||
_locationClass,
|
||||
_locationClass!,
|
||||
extension.enclosingLibrary.nullable,
|
||||
),
|
||||
initializer: new NullLiteral(),
|
||||
@@ -775,7 +895,7 @@ class WidgetCreatorTracker {
|
||||
new VariableDeclaration(
|
||||
_creationLocationParameterName,
|
||||
type: new InterfaceType(
|
||||
_locationClass,
|
||||
_locationClass!,
|
||||
extension.enclosingLibrary.nullable,
|
||||
),
|
||||
initializer: new NullLiteral(),
|
||||
@@ -785,3 +905,17 @@ class WidgetCreatorTracker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds the set of creation location classes, either
|
||||
/// from `package:flutter` or `dart:developer`.
|
||||
class _TrackingClasses {
|
||||
final Class hasCreationLocationClass;
|
||||
final Class locationClass;
|
||||
final String locationFieldName;
|
||||
|
||||
_TrackingClasses({
|
||||
required this.hasCreationLocationClass,
|
||||
required this.locationClass,
|
||||
required this.locationFieldName,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2024, 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:expect/expect.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/transformations/track_widget_constructor_locations.dart';
|
||||
|
||||
void main() {
|
||||
final Uri developerUri = Uri.parse('dart:developer');
|
||||
final Library developerLib = new Library(developerUri, fileUri: developerUri);
|
||||
|
||||
final Class hasCreationLocationClass = new Class(
|
||||
name: '_HasCreationLocation',
|
||||
isAbstract: true,
|
||||
fileUri: developerUri,
|
||||
);
|
||||
developerLib.addClass(hasCreationLocationClass);
|
||||
|
||||
final Class creationLocationClass = new Class(
|
||||
name: 'CreationLocation',
|
||||
fileUri: developerUri,
|
||||
);
|
||||
final Constructor creationLocationConstructor = new Constructor(
|
||||
new FunctionNode(
|
||||
null,
|
||||
namedParameters: [
|
||||
new VariableDeclaration('file', type: const DynamicType()),
|
||||
new VariableDeclaration('line', type: const DynamicType()),
|
||||
new VariableDeclaration('column', type: const DynamicType()),
|
||||
new VariableDeclaration('name', type: const DynamicType()),
|
||||
],
|
||||
),
|
||||
name: new Name('_', developerLib),
|
||||
fileUri: developerUri,
|
||||
);
|
||||
creationLocationClass.addConstructor(creationLocationConstructor);
|
||||
developerLib.addClass(creationLocationClass);
|
||||
|
||||
final Uri coreUri = Uri.parse('dart:core');
|
||||
final Library coreLib = new Library(coreUri, fileUri: coreUri);
|
||||
final Class pragmaClass = new Class(name: 'pragma', fileUri: coreUri);
|
||||
coreLib.addClass(pragmaClass);
|
||||
final Field pragmaNameField = new Field.immutable(
|
||||
new Name('name'),
|
||||
fileUri: coreUri,
|
||||
);
|
||||
pragmaClass.addField(pragmaNameField);
|
||||
|
||||
final Uri testUri = Uri.parse('package:test/test.dart');
|
||||
final Library testLib = new Library(testUri, fileUri: testUri);
|
||||
|
||||
final Class myWidgetClass = new Class(name: 'MyWidget', fileUri: testUri);
|
||||
myWidgetClass.addAnnotation(
|
||||
new ConstantExpression(
|
||||
new InstanceConstant(
|
||||
pragmaClass.reference,
|
||||
<DartType>[],
|
||||
<Reference, Constant>{
|
||||
pragmaNameField.fieldReference: new StringConstant(
|
||||
'track-creation-locations',
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
myWidgetClass.addConstructor(
|
||||
new Constructor(
|
||||
new FunctionNode(new Block([])),
|
||||
name: new Name(''),
|
||||
fileUri: testUri,
|
||||
),
|
||||
);
|
||||
testLib.addClass(myWidgetClass);
|
||||
|
||||
const int fileOffset = 100;
|
||||
final Procedure mainProcedure = new Procedure(
|
||||
new Name('main'),
|
||||
ProcedureKind.Method,
|
||||
new FunctionNode(
|
||||
new Block([
|
||||
new ExpressionStatement(
|
||||
new ConstructorInvocation(
|
||||
myWidgetClass.constructors.first,
|
||||
new Arguments([]),
|
||||
)..fileOffset = fileOffset,
|
||||
),
|
||||
]),
|
||||
),
|
||||
isStatic: true,
|
||||
fileUri: testUri,
|
||||
);
|
||||
testLib.addProcedure(mainProcedure);
|
||||
|
||||
final WidgetCreatorTracker tracker = new WidgetCreatorTracker();
|
||||
tracker.transform([testLib], [developerLib, testLib], null);
|
||||
|
||||
// Verification
|
||||
Expect.isTrue(
|
||||
myWidgetClass.implementedTypes.any(
|
||||
(s) => s.classNode == hasCreationLocationClass,
|
||||
),
|
||||
);
|
||||
Expect.isTrue(myWidgetClass.fields.any((f) => f.name.text == '_location'));
|
||||
|
||||
final Constructor constructor = myWidgetClass.constructors.first;
|
||||
const String creationLocationPrefix = r'$creationLocation';
|
||||
Expect.isTrue(
|
||||
constructor.function.namedParameters.any(
|
||||
(p) => p.name!.startsWith(creationLocationPrefix),
|
||||
),
|
||||
);
|
||||
|
||||
final Block body = mainProcedure.function.body as Block;
|
||||
final ExpressionStatement stmt = body.statements.first as ExpressionStatement;
|
||||
final ConstructorInvocation invocation =
|
||||
stmt.expression as ConstructorInvocation;
|
||||
Expect.isTrue(
|
||||
invocation.arguments.named.any(
|
||||
(n) => n.name.startsWith(creationLocationPrefix),
|
||||
),
|
||||
);
|
||||
|
||||
final NamedExpression namedArg = invocation.arguments.named.firstWhere(
|
||||
(n) => n.name.startsWith(creationLocationPrefix),
|
||||
);
|
||||
Expect.isTrue(namedArg.value is ConstructorInvocation);
|
||||
final ConstructorInvocation locInvocation =
|
||||
namedArg.value as ConstructorInvocation;
|
||||
Expect.equals(creationLocationClass, locInvocation.target.enclosingClass);
|
||||
}
|
||||
Reference in New Issue
Block a user