blob: 67fada296317d5180823ab047e91b61986d2e81b [file] [log] [blame]
// 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 '../reference_from_index.dart';
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,
ReferenceFromIndex? referenceFromIndex,
TypeEnvironment typeEnvironment) {
ValueClassScanner scanner = new ValueClassScanner();
ScanResult<Class, Null> valueClasses = scanner.scan(node);
for (Class valueClass in valueClasses.targets.keys) {
transformValueClass(
valueClass,
coreTypes,
hierarchy,
referenceFromIndex
?.lookupLibrary(valueClass.enclosingLibrary)
?.lookupIndexedClass(valueClass.name),
typeEnvironment);
}
treatCopyWithCallSites(node, coreTypes, typeEnvironment, hierarchy);
for (Class valueClass in valueClasses.targets.keys) {
removeValueClassAnnotation(valueClass);
}
}
void transformValueClass(
Class cls,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
IndexedClass? indexedClass,
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, indexedClass, allVariablesList);
addHashCode(cls, coreTypes, hierarchy, indexedClass, allVariablesList);
addToString(cls, coreTypes, hierarchy, indexedClass, allVariablesList);
addCopyWith(cls, coreTypes, hierarchy, indexedClass, 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<Expression>((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,
IndexedClass? indexedClass, 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;
}
Name name = Name("==");
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,
reference: indexedClass?.lookupGetterReference(name))
..fileOffset = cls.fileOffset;
cls.addProcedure(equalsOperator);
}
void addHashCode(Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy,
IndexedClass? indexedClass, 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;
}
Name name = Name("hashCode");
cls.addProcedure(Procedure(
name,
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,
reference: indexedClass?.lookupGetterReference(name))
..fileOffset = cls.fileOffset);
}
void addToString(Class cls, CoreTypes coreTypes, ClassHierarchy hierarchy,
IndexedClass? indexedClass, 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);
Name name = Name("toString");
cls.addProcedure(Procedure(
name,
ProcedureKind.Method,
FunctionNode(ReturnStatement(StringConcatenation(wording)),
returnType: returnType),
fileUri: cls.fileUri,
reference: indexedClass?.lookupGetterReference(name))
..fileOffset = cls.fileOffset);
}
void addCopyWith(
Class cls,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
IndexedClass? indexedClass,
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;
}
Name name = Name("copyWith");
cls.addProcedure(Procedure(
name,
ProcedureKind.Method,
FunctionNode(
ReturnStatement(ConstructorInvocation(
syntheticConstructor,
Arguments([],
named: allVariables
.map((f) => NamedExpression(f.name!, VariableGet(f)))
.toList()))),
namedParameters: allVariables),
fileUri: cls.fileUri,
reference: indexedClass?.lookupGetterReference(name))
..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);
}
}