8d04e49078
Each of these imports removed is redundant with 'package:kernel/ast.dart'. Bug: https://github.com/dart-lang/sdk/issues/44569 Change-Id: Ia9bc08b4ffd863572c84fe8a4fa465d2e8e04f7c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213845 Reviewed-by: Jens Johansen <jensj@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
490 lines
18 KiB
Dart
490 lines
18 KiB
Dart
// Copyright (c) 2020, 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.
|
|
|
|
library kernel.transformations.value_class;
|
|
|
|
import 'package:kernel/type_environment.dart';
|
|
|
|
import '../ast.dart';
|
|
import '../core_types.dart' show CoreTypes;
|
|
import '../class_hierarchy.dart' show ClassHierarchy;
|
|
import './scanner.dart';
|
|
|
|
class ValueClassScanner extends ClassScanner<Null> {
|
|
ValueClassScanner() : super(null);
|
|
|
|
@override
|
|
bool predicate(Class node) => isValueClass(node);
|
|
}
|
|
|
|
class JenkinsClassScanner extends ClassScanner<Procedure> {
|
|
JenkinsClassScanner(Scanner<Procedure, TreeNode?> next) : super(next);
|
|
|
|
@override
|
|
bool predicate(Class node) {
|
|
return node.name == "JenkinsSmiHash";
|
|
}
|
|
}
|
|
|
|
class HashCombineMethodsScanner extends ProcedureScanner<Null> {
|
|
HashCombineMethodsScanner() : super(null);
|
|
|
|
@override
|
|
bool predicate(Procedure node) {
|
|
return node.name.text == "combine" || node.name.text == "finish";
|
|
}
|
|
}
|
|
|
|
class AllMemberScanner extends MemberScanner<InstanceInvocationExpression> {
|
|
AllMemberScanner(Scanner<InstanceInvocationExpression, TreeNode?> next)
|
|
: super(next);
|
|
|
|
@override
|
|
bool predicate(Member member) => true;
|
|
}
|
|
|
|
// Scans and matches all copyWith invocations were the receiver is _ as dynamic
|
|
// It will filter out the results that are not value classes afterwards
|
|
class ValueClassCopyWithScanner extends MethodInvocationScanner<Null> {
|
|
ValueClassCopyWithScanner() : super(null);
|
|
|
|
// The matching construct followed in unit-tests is:
|
|
// @valueClass V {}
|
|
// V v;
|
|
// (v as dynamic).copyWith() as V
|
|
@override
|
|
bool predicate(InstanceInvocationExpression node) {
|
|
return node.name.text == "copyWith" &&
|
|
_isValueClassAsConstruct(node.receiver);
|
|
}
|
|
|
|
bool _isValueClassAsConstruct(Expression node) {
|
|
return node is AsExpression && node.type is DynamicType;
|
|
}
|
|
}
|
|
|
|
void transformComponent(Component node, CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy, TypeEnvironment typeEnvironment) {
|
|
ValueClassScanner scanner = new ValueClassScanner();
|
|
ScanResult<Class, Null> valueClasses = scanner.scan(node);
|
|
for (Class valueClass in valueClasses.targets.keys) {
|
|
transformValueClass(valueClass, coreTypes, hierarchy, typeEnvironment);
|
|
}
|
|
|
|
treatCopyWithCallSites(node, coreTypes, typeEnvironment, hierarchy);
|
|
|
|
for (Class valueClass in valueClasses.targets.keys) {
|
|
removeValueClassAnnotation(valueClass);
|
|
}
|
|
}
|
|
|
|
void transformValueClass(Class cls, CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy, TypeEnvironment typeEnvironment) {
|
|
Constructor? syntheticConstructor = null;
|
|
for (Constructor constructor in cls.constructors) {
|
|
if (constructor.isSynthetic) {
|
|
syntheticConstructor = constructor;
|
|
}
|
|
}
|
|
|
|
List<VariableDeclaration> allVariables = queryAllInstanceVariables(cls);
|
|
List<VariableDeclaration> allVariablesList = allVariables.toList();
|
|
allVariablesList.sort((a, b) => a.name!.compareTo(b.name!));
|
|
|
|
addConstructor(cls, coreTypes, syntheticConstructor!);
|
|
addEqualsOperator(cls, coreTypes, hierarchy, allVariablesList);
|
|
addHashCode(cls, coreTypes, hierarchy, allVariablesList);
|
|
addToString(cls, coreTypes, hierarchy, allVariablesList);
|
|
addCopyWith(cls, coreTypes, hierarchy, allVariablesList, syntheticConstructor,
|
|
typeEnvironment);
|
|
}
|
|
|
|
void addConstructor(
|
|
Class cls, CoreTypes coreTypes, Constructor syntheticConstructor) {
|
|
Constructor? superConstructor = null;
|
|
for (Constructor constructor in cls.superclass!.constructors) {
|
|
if (constructor.name.text == "") {
|
|
superConstructor = constructor;
|
|
}
|
|
}
|
|
List<VariableDeclaration> superParameters = superConstructor!
|
|
.function.namedParameters
|
|
.map<VariableDeclaration>((e) => VariableDeclaration(e.name, type: e.type)
|
|
..parent = syntheticConstructor.function)
|
|
.toList();
|
|
Map<String, VariableDeclaration> ownFields = Map.fromIterable(cls.fields,
|
|
key: (f) => f.name.text,
|
|
value: (f) =>
|
|
VariableDeclaration(f.name.text, type: f.type, isRequired: true)
|
|
..parent = syntheticConstructor.function);
|
|
|
|
List<Initializer> initializersConstructor = cls.fields
|
|
.map<Initializer>((f) =>
|
|
FieldInitializer(f, VariableGet(ownFields[f.name.text]!))
|
|
..parent = syntheticConstructor)
|
|
.toList();
|
|
|
|
initializersConstructor.add(SuperInitializer(superConstructor,
|
|
Arguments(superParameters.map((f) => VariableGet(f)).toList()))
|
|
..parent = syntheticConstructor);
|
|
|
|
syntheticConstructor.function.namedParameters
|
|
..clear()
|
|
..addAll(ownFields.values)
|
|
..addAll(superParameters);
|
|
syntheticConstructor.initializers = initializersConstructor;
|
|
}
|
|
|
|
void addEqualsOperator(Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy,
|
|
List<VariableDeclaration> allVariablesList) {
|
|
List<VariableDeclaration> allVariables = allVariablesList.toList();
|
|
for (Procedure procedure in cls.procedures) {
|
|
if (procedure.kind == ProcedureKind.Operator &&
|
|
procedure.name.text == "==") {
|
|
// ==operator is already implemented, spec is to do nothing
|
|
return;
|
|
}
|
|
}
|
|
DartType returnType = coreTypes.boolRawType(cls.enclosingLibrary.nonNullable);
|
|
DartType myType = coreTypes.thisInterfaceType(cls, Nullability.nonNullable);
|
|
|
|
VariableDeclaration other = VariableDeclaration("other",
|
|
type: coreTypes.objectRawType(Nullability.nonNullable));
|
|
|
|
Map<VariableDeclaration, Member> targetsEquals = new Map();
|
|
Map<VariableDeclaration, Member> targets = new Map();
|
|
for (VariableDeclaration variable in allVariables) {
|
|
Member target = coreTypes.objectEquals;
|
|
Member targetEquals = coreTypes.objectEquals;
|
|
DartType fieldsType = variable.type;
|
|
if (fieldsType is InterfaceType) {
|
|
targetEquals =
|
|
hierarchy.getInterfaceMember(fieldsType.classNode, Name("=="))!;
|
|
target = hierarchy.getInterfaceMember(cls, Name(variable.name!))!;
|
|
}
|
|
targetsEquals[variable] = targetEquals;
|
|
targets[variable] = target;
|
|
}
|
|
|
|
Procedure equalsOperator = Procedure(
|
|
Name("=="),
|
|
ProcedureKind.Operator,
|
|
FunctionNode(
|
|
ReturnStatement(allVariables
|
|
.map((f) => _createEquals(
|
|
_createGet(ThisExpression(), Name(f.name!),
|
|
interfaceTarget: targets[f]),
|
|
_createGet(VariableGet(other, myType), Name(f.name!),
|
|
interfaceTarget: targets[f]),
|
|
interfaceTarget: targetsEquals[f] as Procedure))
|
|
.fold(
|
|
IsExpression(VariableGet(other), myType),
|
|
(previousValue, element) => LogicalExpression(
|
|
previousValue!, LogicalExpressionOperator.AND, element))),
|
|
returnType: returnType,
|
|
positionalParameters: [other]),
|
|
fileUri: cls.fileUri)
|
|
..fileOffset = cls.fileOffset;
|
|
cls.addProcedure(equalsOperator);
|
|
}
|
|
|
|
void addHashCode(Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy,
|
|
List<VariableDeclaration> allVariablesList) {
|
|
List<VariableDeclaration> allVariables = allVariablesList.toList();
|
|
for (Procedure procedure in cls.procedures) {
|
|
if (procedure.kind == ProcedureKind.Getter &&
|
|
procedure.name.text == "hashCode") {
|
|
// hashCode getter is already implemented, spec is to do nothing
|
|
return;
|
|
}
|
|
}
|
|
DartType returnType = coreTypes.intRawType(cls.enclosingLibrary.nonNullable);
|
|
|
|
Procedure? hashCombine, hashFinish;
|
|
HashCombineMethodsScanner hashCombineMethodsScanner =
|
|
new HashCombineMethodsScanner();
|
|
JenkinsClassScanner jenkinsScanner =
|
|
new JenkinsClassScanner(hashCombineMethodsScanner);
|
|
ScanResult<Class, Procedure> hashMethodsResult =
|
|
jenkinsScanner.scan(cls.enclosingLibrary.enclosingComponent!);
|
|
for (Class clazz in hashMethodsResult.targets.keys) {
|
|
for (Procedure procedure
|
|
in hashMethodsResult.targets[clazz]!.targets.keys) {
|
|
if (procedure.name.text == "combine") {
|
|
hashCombine = procedure;
|
|
}
|
|
if (procedure.name.text == "finish") {
|
|
hashFinish = procedure;
|
|
}
|
|
}
|
|
}
|
|
|
|
Map<VariableDeclaration, Member> targetsHashcode = new Map();
|
|
Map<VariableDeclaration, Member> targets = new Map();
|
|
for (VariableDeclaration variable in allVariables) {
|
|
Member target = coreTypes.objectEquals;
|
|
Member targetHashcode = coreTypes.objectEquals;
|
|
DartType fieldsType = variable.type;
|
|
if (fieldsType is InterfaceType) {
|
|
targetHashcode =
|
|
hierarchy.getInterfaceMember(fieldsType.classNode, Name("hashCode"))!;
|
|
target = hierarchy.getInterfaceMember(cls, Name(variable.name!))!;
|
|
}
|
|
targetsHashcode[variable] = targetHashcode;
|
|
targets[variable] = target;
|
|
}
|
|
cls.addProcedure(Procedure(
|
|
Name("hashCode"),
|
|
ProcedureKind.Getter,
|
|
FunctionNode(
|
|
ReturnStatement(StaticInvocation(
|
|
hashFinish!,
|
|
Arguments([
|
|
allVariables
|
|
.map((f) => (_createGet(
|
|
_createGet(ThisExpression(), Name(f.name!),
|
|
interfaceTarget: targets[f]),
|
|
Name("hashCode"),
|
|
interfaceTarget: targetsHashcode[f])))
|
|
.fold(
|
|
_createGet(
|
|
StringLiteral(
|
|
cls.enclosingLibrary.importUri.toString() +
|
|
cls.name),
|
|
Name("hashCode"),
|
|
interfaceTarget: hierarchy.getInterfaceMember(
|
|
coreTypes.stringClass, Name("hashCode"))),
|
|
(previousValue, element) => StaticInvocation(
|
|
hashCombine!, Arguments([previousValue, element])))
|
|
]))),
|
|
returnType: returnType),
|
|
fileUri: cls.fileUri)
|
|
..fileOffset = cls.fileOffset);
|
|
}
|
|
|
|
void addToString(Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy,
|
|
List<VariableDeclaration> allVariablesList) {
|
|
List<Expression> wording = [StringLiteral("${cls.name}(")];
|
|
|
|
for (VariableDeclaration variable in allVariablesList) {
|
|
wording.add(StringLiteral("${variable.name}: "));
|
|
Member? variableTarget =
|
|
hierarchy.getInterfaceMember(cls, Name(variable.name!));
|
|
Procedure toStringTarget = hierarchy.getInterfaceMember(
|
|
variable.type is InterfaceType
|
|
? (variable.type as InterfaceType).classNode
|
|
: coreTypes.objectClass,
|
|
Name("toString")) as Procedure;
|
|
wording.add(_createInvocation(
|
|
_createGet(ThisExpression(), Name(variable.name!),
|
|
interfaceTarget: variableTarget),
|
|
Name("toString"),
|
|
Arguments([]),
|
|
interfaceTarget: toStringTarget));
|
|
wording.add(StringLiteral(", "));
|
|
}
|
|
if (allVariablesList.length != 0) {
|
|
wording[wording.length - 1] = StringLiteral(")");
|
|
} else {
|
|
wording.add(StringLiteral(")"));
|
|
}
|
|
DartType returnType =
|
|
coreTypes.stringRawType(cls.enclosingLibrary.nonNullable);
|
|
cls.addProcedure(Procedure(
|
|
Name("toString"),
|
|
ProcedureKind.Method,
|
|
FunctionNode(ReturnStatement(StringConcatenation(wording)),
|
|
returnType: returnType),
|
|
fileUri: cls.fileUri)
|
|
..fileOffset = cls.fileOffset);
|
|
}
|
|
|
|
void addCopyWith(
|
|
Class cls,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
List<VariableDeclaration> allVariablesList,
|
|
Constructor syntheticConstructor,
|
|
TypeEnvironment typeEnvironment) {
|
|
List<VariableDeclaration> allVariables = allVariablesList.toList();
|
|
|
|
Map<VariableDeclaration, Member> targetsEquals = new Map();
|
|
Map<VariableDeclaration, Member> targets = new Map();
|
|
for (VariableDeclaration variable in allVariables) {
|
|
Member target = coreTypes.objectEquals;
|
|
Member targetEquals = coreTypes.objectEquals;
|
|
DartType fieldsType = variable.type;
|
|
if (fieldsType is InterfaceType) {
|
|
targetEquals =
|
|
hierarchy.getInterfaceMember(fieldsType.classNode, Name("=="))!;
|
|
target = hierarchy.getInterfaceMember(cls, Name(variable.name!))!;
|
|
}
|
|
targetsEquals[variable] = targetEquals;
|
|
targets[variable] = target;
|
|
}
|
|
|
|
cls.addProcedure(Procedure(
|
|
Name("copyWith"),
|
|
ProcedureKind.Method,
|
|
FunctionNode(
|
|
ReturnStatement(ConstructorInvocation(
|
|
syntheticConstructor,
|
|
Arguments([],
|
|
named: allVariables
|
|
.map((f) => NamedExpression(f.name!, VariableGet(f)))
|
|
.toList()))),
|
|
namedParameters: allVariables),
|
|
fileUri: cls.fileUri)
|
|
..fileOffset = cls.fileOffset);
|
|
}
|
|
|
|
List<VariableDeclaration> queryAllInstanceVariables(Class cls) {
|
|
Constructor? superConstructor = null;
|
|
for (Constructor constructor in cls.superclass!.constructors) {
|
|
if (constructor.name.text == "") {
|
|
superConstructor = constructor;
|
|
}
|
|
}
|
|
return superConstructor!.function.namedParameters
|
|
.map<VariableDeclaration>(
|
|
(f) => VariableDeclaration(f.name, type: f.type))
|
|
.toList()
|
|
..addAll(cls.fields.map<VariableDeclaration>(
|
|
(f) => VariableDeclaration(f.name.text, type: f.type)));
|
|
}
|
|
|
|
void removeValueClassAnnotation(Class cls) {
|
|
int? valueClassAnnotationIndex;
|
|
for (int annotationIndex = 0;
|
|
annotationIndex < cls.annotations.length;
|
|
annotationIndex++) {
|
|
Expression annotation = cls.annotations[annotationIndex];
|
|
if (annotation is ConstantExpression &&
|
|
annotation.constant is StringConstant) {
|
|
StringConstant constant = annotation.constant as StringConstant;
|
|
if (constant.value == 'valueClass') {
|
|
valueClassAnnotationIndex = annotationIndex;
|
|
}
|
|
}
|
|
}
|
|
cls.annotations.removeAt(valueClassAnnotationIndex!);
|
|
}
|
|
|
|
void treatCopyWithCallSites(Component component, CoreTypes coreTypes,
|
|
TypeEnvironment typeEnvironment, ClassHierarchy hierarchy) {
|
|
ValueClassCopyWithScanner valueCopyWithScanner =
|
|
new ValueClassCopyWithScanner();
|
|
AllMemberScanner copyWithScanner = AllMemberScanner(valueCopyWithScanner);
|
|
ScanResult<Member, InstanceInvocationExpression> copyWithCallSites =
|
|
copyWithScanner.scan(component);
|
|
for (Member memberWithCopyWith in copyWithCallSites.targets.keys) {
|
|
Map<InstanceInvocationExpression, ScanResult<TreeNode?, TreeNode?>?>?
|
|
targets = copyWithCallSites.targets[memberWithCopyWith]?.targets;
|
|
if (targets != null) {
|
|
StaticTypeContext staticTypeContext =
|
|
StaticTypeContext(memberWithCopyWith, typeEnvironment);
|
|
for (InstanceInvocationExpression copyWithCall in targets.keys) {
|
|
AsExpression receiver = copyWithCall.receiver as AsExpression;
|
|
|
|
Expression valueClassInstance = receiver.operand;
|
|
DartType valueClassType =
|
|
valueClassInstance.getStaticType(staticTypeContext);
|
|
if (valueClassType is InterfaceType) {
|
|
Class valueClass = valueClassType.classNode;
|
|
if (isValueClass(valueClass)) {
|
|
treatCopyWithCallSite(
|
|
valueClass, copyWithCall, coreTypes, hierarchy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void treatCopyWithCallSite(
|
|
Class valueClass,
|
|
InstanceInvocationExpression copyWithCall,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy) {
|
|
Map<String, Expression> preTransformationArguments = new Map();
|
|
for (NamedExpression argument in copyWithCall.arguments.named) {
|
|
preTransformationArguments[argument.name] = argument.value;
|
|
}
|
|
Constructor? syntheticConstructor;
|
|
for (Constructor constructor in valueClass.constructors) {
|
|
if (constructor.isSynthetic) {
|
|
syntheticConstructor = constructor;
|
|
}
|
|
}
|
|
List<VariableDeclaration> allArguments =
|
|
syntheticConstructor!.function.namedParameters;
|
|
|
|
VariableDeclaration letVariable =
|
|
VariableDeclaration.forValue(copyWithCall.receiver);
|
|
Arguments postTransformationArguments = Arguments.empty();
|
|
for (VariableDeclaration argument in allArguments) {
|
|
if (preTransformationArguments.containsKey(argument.name)) {
|
|
postTransformationArguments.named.add(NamedExpression(
|
|
argument.name!, preTransformationArguments[argument.name]!)
|
|
..parent = postTransformationArguments);
|
|
} else {
|
|
postTransformationArguments.named.add(NamedExpression(argument.name!,
|
|
_createGet(VariableGet(letVariable), Name(argument.name!)))
|
|
..parent = postTransformationArguments);
|
|
}
|
|
}
|
|
copyWithCall.replaceWith(Let(
|
|
letVariable,
|
|
_createInvocation(VariableGet(letVariable), Name("copyWith"),
|
|
postTransformationArguments)));
|
|
}
|
|
|
|
bool isValueClass(Class node) {
|
|
for (Expression annotation in node.annotations) {
|
|
if (annotation is ConstantExpression &&
|
|
annotation.constant is StringConstant) {
|
|
StringConstant constant = annotation.constant as StringConstant;
|
|
if (constant.value == "valueClass") {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// TODO(johnniwinther): Ensure correct invocation function type and instance
|
|
// access kind on InstanceInvocation.
|
|
Expression _createInvocation(
|
|
Expression receiver, Name name, Arguments arguments,
|
|
{Procedure? interfaceTarget}) {
|
|
if (interfaceTarget != null) {
|
|
return InstanceInvocation(
|
|
InstanceAccessKind.Instance, receiver, name, arguments,
|
|
interfaceTarget: interfaceTarget,
|
|
functionType: interfaceTarget.getterType as FunctionType);
|
|
} else {
|
|
return DynamicInvocation(
|
|
DynamicAccessKind.Dynamic, receiver, name, arguments);
|
|
}
|
|
}
|
|
|
|
Expression _createEquals(Expression left, Expression right,
|
|
{required Procedure interfaceTarget}) {
|
|
return EqualsCall(left, right,
|
|
interfaceTarget: interfaceTarget,
|
|
functionType: interfaceTarget.getterType as FunctionType);
|
|
}
|
|
|
|
// TODO(johnniwinther): Ensure correct result type on InstanceGet.
|
|
Expression _createGet(Expression receiver, Name name,
|
|
{Member? interfaceTarget}) {
|
|
if (interfaceTarget != null) {
|
|
return InstanceGet(InstanceAccessKind.Instance, receiver, name,
|
|
interfaceTarget: interfaceTarget,
|
|
resultType: interfaceTarget.getterType);
|
|
} else {
|
|
return DynamicGet(DynamicAccessKind.Dynamic, receiver, name);
|
|
}
|
|
}
|