blob: 08ad159875a4395fc9cfd109ba19bfb54f108a9e [file] [log] [blame] [edit]
// 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:kernel/ast.dart';
import 'package:kernel/transformations/flags.dart';
/// Transformer/visitor for toString
transformComponent(Component component, List<String> packageUris) {
component.visitChildren(ToStringVisitor(packageUris.toSet()));
}
/// A [RecursiveVisitor] that replaces [Object.toString] overrides with
/// `super.toString()`.
class ToStringVisitor extends RecursiveVisitor {
/// The [packageUris] must not be null.
ToStringVisitor(this._packageUris);
/// A set of package URIs to apply this transformer to, e.g. 'dart:ui' and
/// 'package:flutter/foundation.dart'.
final Set<String> _packageUris;
final Map<Class, bool> _inheritedKeepAnnotations = {};
/// Turn 'dart:ui' into 'dart:ui', or
/// 'package:flutter/src/semantics_event.dart' into 'package:flutter'.
String _importUriToPackage(Uri importUri) =>
'${importUri.scheme}:${importUri.pathSegments.first}';
bool _isInTargetPackage(Procedure node) {
return _packageUris
.contains(_importUriToPackage(node.enclosingLibrary.importUri));
}
bool _hasKeepAnnotation(Procedure node) =>
_hasPragma(node, 'flutter:keep-to-string');
bool _hasKeepAnnotationOnClass(Class node) =>
_hasPragma(node, 'flutter:keep-to-string-in-subtypes');
bool _hasInheritedKeepAnnotation(Class node) =>
_inheritedKeepAnnotations[node] ??= (_hasKeepAnnotationOnClass(node) ||
node.supers
.any((Supertype t) => _hasInheritedKeepAnnotation(t.classNode)));
bool _hasPragma(Annotatable node, String pragma) {
for (ConstantExpression expression
in node.annotations.whereType<ConstantExpression>()) {
if (expression.constant is! InstanceConstant) {
continue;
}
final InstanceConstant constant = expression.constant as InstanceConstant;
final className = constant.classNode.name;
final libraryUri =
constant.classNode.enclosingLibrary.importUri.toString();
if (className == 'pragma' && libraryUri == 'dart:core') {
for (var fieldRef in constant.fieldValues.keys) {
if (fieldRef.asField.name.text == 'name') {
Constant? name = constant.fieldValues[fieldRef];
return name is StringConstant && name.value == pragma;
}
}
return false;
}
}
return false;
}
@override
void visitProcedure(Procedure node) {
if (node.name.text == 'toString' &&
node.enclosingClass != null &&
!node.isStatic &&
!node.isAbstract &&
!node.enclosingClass!.isEnum &&
_isInTargetPackage(node) &&
!_hasKeepAnnotation(node) &&
!_hasInheritedKeepAnnotation(node.enclosingClass!)) {
Procedure findSuperMethod(Class cls) {
for (Procedure procedure in cls.procedures) {
if (procedure.name.text == 'toString' && !procedure.isAbstract) {
return procedure;
}
}
return findSuperMethod(cls.superclass!);
}
node.transformerFlags |= TransformerFlag.superCalls;
node.function.body!.replaceWith(
ReturnStatement(
SuperMethodInvocation(
node.name,
Arguments(<Expression>[]),
findSuperMethod(node.enclosingClass!.superclass!),
),
),
);
}
}
@override
void defaultMember(Member node) {}
}