blob: 5bc9d23bb8b85996ca9bbb5ae583135fbc08deae [file] [log] [blame]
// Copyright (c) 2016, 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.
/// Functions for asserting equivalence across serialization.
library dart2js.serialization.equivalence;
import '../common/resolution.dart';
import '../constants/expressions.dart';
import '../dart_types.dart';
import '../elements/elements.dart';
import '../elements/visitor.dart';
import '../universe/selector.dart';
import '../universe/use.dart';
/// Equality based equivalence function.
bool equality(a, b) => a == b;
/// Returns `true` if the elements in [a] and [b] are pair-wise equivalent
/// according to [elementEquivalence].
bool areListsEquivalent(
List a,
List b,
[bool elementEquivalence(a, b) = equality]) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length && i < b.length; i++) {
if (!elementEquivalence(a[i], b[i])) {
return false;
}
}
return true;
}
/// Returns `true` if the elements in [a] and [b] are equivalent as sets using
/// [elementEquivalence] to determine element equivalence.
bool areSetsEquivalent(
Iterable set1,
Iterable set2,
[bool elementEquivalence(a, b) = equality]) {
Set remaining = set2.toSet();
for (var element1 in set1) {
bool found = false;
for (var element2 in set2) {
if (elementEquivalence(element1, element2)) {
found = true;
remaining.remove(element2);
break;
}
}
if (!found) {
return false;
}
}
return remaining.isEmpty;
}
/// Returns `true` if elements [a] and [b] are equivalent.
bool areElementsEquivalent(Element a, Element b) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
return const ElementIdentityEquivalence().visit(a, b);
}
/// Returns `true` if types [a] and [b] are equivalent.
bool areTypesEquivalent(DartType a, DartType b) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
return const TypeEquivalence().visit(a, b);
}
/// Returns `true` if constants [a] and [b] are equivalent.
bool areConstantsEquivalent(ConstantExpression exp1, ConstantExpression exp2) {
if (identical(exp1, exp2)) return true;
if (exp1 == null || exp2 == null) return false;
return const ConstantEquivalence().visit(exp1, exp2);
}
/// Returns `true` if the lists of elements, [a] and [b], are equivalent.
bool areElementListsEquivalent(List<Element> a, List<Element> b) {
return areListsEquivalent(a, b, areElementsEquivalent);
}
/// Returns `true` if the lists of types, [a] and [b], are equivalent.
bool areTypeListsEquivalent(List<DartType> a, List<DartType> b) {
return areListsEquivalent(a, b, areTypesEquivalent);
}
/// Returns `true` if the lists of constants, [a] and [b], are equivalent.
bool areConstantListsEquivalent(List<ConstantExpression> a,
List<ConstantExpression> b) {
return areListsEquivalent(a, b, areConstantsEquivalent);
}
/// Returns `true` if the selectors [a] and [b] are equivalent.
bool areSelectorsEquivalent(Selector a, Selector b) {
return a.kind == b.kind &&
a.callStructure == b.callStructure &&
areNamesEquivalent(a.memberName, b.memberName);
}
/// Returns `true` if the names [a] and [b] are equivalent.
bool areNamesEquivalent(Name a, Name b) {
return a.text == b.text &&
a.isSetter == b.isSetter &&
areElementsEquivalent(a.library, b.library);
}
/// Returns `true` if the dynamic uses [a] and [b] are equivalent.
bool areDynamicUsesEquivalent(DynamicUse a, DynamicUse b) {
return areSelectorsEquivalent(a.selector, b.selector);
}
/// Returns `true` if the static uses [a] and [b] are equivalent.
bool areStaticUsesEquivalent(StaticUse a, StaticUse b) {
return a.kind == b.kind &&
areElementsEquivalent(a.element, b.element);
}
/// Returns `true` if the type uses [a] and [b] are equivalent.
bool areTypeUsesEquivalent(TypeUse a, TypeUse b) {
return a.kind == b.kind &&
areTypesEquivalent(a.type, b.type);
}
/// Returns `true` if the list literal uses [a] and [b] are equivalent.
bool areListLiteralUsesEquivalent(ListLiteralUse a, ListLiteralUse b) {
return areTypesEquivalent(a.type, b.type) &&
a.isConstant == b.isConstant &&
a.isEmpty == b.isEmpty;
}
/// Returns `true` if the map literal uses [a] and [b] are equivalent.
bool areMapLiteralUsesEquivalent(MapLiteralUse a, MapLiteralUse b) {
return areTypesEquivalent(a.type, b.type) &&
a.isConstant == b.isConstant &&
a.isEmpty == b.isEmpty;
}
/// Strategy for testing equivalence.
///
/// Use this strategy to determine equivalence without failing on inequivalence.
class TestStrategy {
const TestStrategy();
bool test(var object1, var object2, String property, var value1, var value2) {
return value1 == value2;
}
bool testLists(
Object object1, Object object2, String property,
List list1, List list2,
[bool elementEquivalence(a, b) = equality]) {
return areListsEquivalent(list1, list2, elementEquivalence);
}
bool testSets(
var object1, var object2, String property,
Iterable set1, Iterable set2,
[bool elementEquivalence(a, b) = equality]) {
return areSetsEquivalent(set1, set2, elementEquivalence);
}
bool testElements(
Object object1, Object object2, String property,
Element element1, Element element2) {
return areElementsEquivalent(element1, element2);
}
bool testTypes(
Object object1, Object object2, String property,
DartType type1, DartType type2) {
return areTypesEquivalent(type1, type2);
}
bool testConstants(
Object object1, Object object2, String property,
ConstantExpression exp1, ConstantExpression exp2) {
return areConstantsEquivalent(exp1, exp2);
}
bool testTypeLists(
Object object1, Object object2, String property,
List<DartType> list1, List<DartType> list2) {
return areTypeListsEquivalent(list1, list2);
}
bool testConstantLists(
Object object1, Object object2, String property,
List<ConstantExpression> list1,
List<ConstantExpression> list2) {
return areConstantListsEquivalent(list1, list2);
}
}
/// Visitor that checks for equivalence of [Element]s.
class ElementIdentityEquivalence extends BaseElementVisitor<bool, Element> {
final TestStrategy strategy;
const ElementIdentityEquivalence([this.strategy = const TestStrategy()]);
bool visit(Element element1, Element element2) {
if (element1 == null && element2 == null) {
return true;
} else if (element1 == null || element2 == null) {
return false;
}
element1 = element1.declaration;
element2 = element2.declaration;
if (element1 == element2) {
return true;
}
return
strategy.test(
element1, element2, 'kind',
element1.kind, element2.kind) &&
element1.accept(this, element2);
}
@override
bool visitElement(Element e, Element arg) {
throw new UnsupportedError("Unsupported element $e");
}
@override
bool visitLibraryElement(LibraryElement element1, LibraryElement element2) {
return
strategy.test(element1, element2,
'canonicalUri',
element1.canonicalUri, element2.canonicalUri);
}
@override
bool visitCompilationUnitElement(CompilationUnitElement element1,
CompilationUnitElement element2) {
return
strategy.test(element1, element2,
'name',
element1.name, element2.name) &&
visit(element1.library, element2.library);
}
@override
bool visitClassElement(ClassElement element1, ClassElement element2) {
return
strategy.test(element1, element2,
'name',
element1.name, element2.name) &&
visit(element1.library, element2.library);
}
bool checkMembers(Element element1, Element element2) {
if (!strategy.test(element1, element2, 'name',
element1.name, element2.name)) {
return false;
}
if (element1.enclosingClass != null || element2.enclosingClass != null) {
return visit(element1.enclosingClass, element2.enclosingClass);
} else {
return visit(element1.library, element2.library);
}
}
@override
bool visitFieldElement(FieldElement element1, FieldElement element2) {
return checkMembers(element1, element2);
}
@override
bool visitConstructorElement(ConstructorElement element1,
ConstructorElement element2) {
return checkMembers(element1, element2);
}
@override
bool visitMethodElement(MethodElement element1,
MethodElement element2) {
return checkMembers(element1, element2);
}
@override
bool visitGetterElement(GetterElement element1,
GetterElement element2) {
return checkMembers(element1, element2);
}
@override
bool visitSetterElement(SetterElement element1,
SetterElement element2) {
return checkMembers(element1, element2);
}
@override
bool visitLocalFunctionElement(LocalFunctionElement element1,
LocalFunctionElement element2) {
// TODO(johnniwinther): Define an equivalence on locals.
return checkMembers(element1.memberContext, element2.memberContext);
}
bool visitAbstractFieldElement(AbstractFieldElement element1,
AbstractFieldElement element2) {
return checkMembers(element1, element2);
}
@override
bool visitTypeVariableElement(TypeVariableElement element1,
TypeVariableElement element2) {
return
strategy.test(
element1, element2, 'name',
element1.name, element2.name) &&
visit(element1.typeDeclaration, element2.typeDeclaration);
}
@override
bool visitTypedefElement(TypedefElement element1, TypedefElement element2) {
return
strategy.test(
element1, element2, 'name',
element1.name, element2.name) &&
visit(element1.library, element2.library);
}
@override
bool visitParameterElement(ParameterElement element1,
ParameterElement element2) {
return
strategy.test(
element1, element2, 'name',
element1.name, element2.name) &&
visit(element1.functionDeclaration, element2.functionDeclaration);
}
@override
bool visitImportElement(ImportElement element1, ImportElement element2) {
return
visit(element1.importedLibrary, element2.importedLibrary) &&
visit(element1.library, element2.library);
}
@override
bool visitExportElement(ExportElement element1, ExportElement element2) {
return
visit(element1.exportedLibrary, element2.exportedLibrary) &&
visit(element1.library, element2.library);
}
@override
bool visitPrefixElement(PrefixElement element1, PrefixElement element2) {
return
strategy.test(
element1, element2, 'name',
element1.name, element2.name) &&
visit(element1.library, element2.library);
}
}
/// Visitor that checks for equivalence of [DartType]s.
class TypeEquivalence implements DartTypeVisitor<bool, DartType> {
final TestStrategy strategy;
const TypeEquivalence([this.strategy = const TestStrategy()]);
bool visit(DartType type1, DartType type2) {
return
strategy.test(type1, type2, 'kind', type1.kind, type2.kind) &&
type1.accept(this, type2);
}
@override
bool visitDynamicType(DynamicType type, DynamicType other) => true;
@override
bool visitFunctionType(FunctionType type, FunctionType other) {
return
strategy.testTypeLists(
type, other, 'parameterTypes',
type.parameterTypes, other.parameterTypes) &&
strategy.testTypeLists(
type, other, 'optionalParameterTypes',
type.optionalParameterTypes, other.optionalParameterTypes) &&
strategy.testTypeLists(
type, other, 'namedParameterTypes',
type.namedParameterTypes, other.namedParameterTypes) &&
strategy.testLists(
type, other, 'namedParameters',
type.namedParameters, other.namedParameters);
}
bool visitGenericType(GenericType type, GenericType other) {
return
strategy.testElements(
type, other, 'element',
type.element, other.element) &&
strategy.testTypeLists(
type, other, 'typeArguments',
type.typeArguments, other.typeArguments);
}
@override
bool visitMalformedType(MalformedType type, MalformedType other) => true;
@override
bool visitStatementType(StatementType type, StatementType other) {
throw new UnsupportedError("Unsupported type: $type");
}
@override
bool visitTypeVariableType(TypeVariableType type, TypeVariableType other) {
return
strategy.testElements(
type, other, 'element',
type.element, other.element);
}
@override
bool visitVoidType(VoidType type, VoidType argument) => true;
@override
bool visitInterfaceType(InterfaceType type, InterfaceType other) {
return visitGenericType(type, other);
}
@override
bool visitTypedefType(TypedefType type, TypedefType other) {
return visitGenericType(type, other);
}
}
/// Visitor that checks for structural equivalence of [ConstantExpression]s.
class ConstantEquivalence
implements ConstantExpressionVisitor<bool, ConstantExpression> {
final TestStrategy strategy;
const ConstantEquivalence([this.strategy = const TestStrategy()]);
@override
bool visit(ConstantExpression exp1, ConstantExpression exp2) {
if (identical(exp1, exp2)) return true;
return
strategy.test(exp1, exp2, 'kind', exp1.kind, exp2.kind) &&
exp1.accept(this, exp2);
}
@override
bool visitBinary(BinaryConstantExpression exp1,
BinaryConstantExpression exp2) {
return
strategy.test(exp1, exp2, 'operator', exp1.operator, exp2.operator) &&
strategy.testConstants(exp1, exp2, 'left', exp1.left, exp2.left) &&
strategy.testConstants(exp1, exp2, 'right', exp1.right, exp2.right);
}
@override
bool visitConcatenate(ConcatenateConstantExpression exp1,
ConcatenateConstantExpression exp2) {
return
strategy.testConstantLists(
exp1, exp2, 'expressions',
exp1.expressions, exp2.expressions);
}
@override
bool visitConditional(ConditionalConstantExpression exp1,
ConditionalConstantExpression exp2) {
return
strategy.testConstants(
exp1, exp2, 'condition', exp1.condition, exp2.condition) &&
strategy.testConstants(
exp1, exp2, 'trueExp', exp1.trueExp, exp2.trueExp) &&
strategy.testConstants(
exp1, exp2, 'falseExp', exp1.falseExp, exp2.falseExp);
}
@override
bool visitConstructed(ConstructedConstantExpression exp1,
ConstructedConstantExpression exp2) {
return
strategy.testTypes(
exp1, exp2, 'type',
exp1.type, exp2.type) &&
strategy.testElements(
exp1, exp2, 'target',
exp1.target, exp2.target) &&
strategy.testConstantLists(
exp1, exp2, 'arguments',
exp1.arguments, exp2.arguments) &&
strategy.test(exp1, exp2, 'callStructure',
exp1.callStructure, exp2.callStructure);
}
@override
bool visitFunction(FunctionConstantExpression exp1,
FunctionConstantExpression exp2) {
return
strategy.testElements(
exp1, exp2, 'element',
exp1.element, exp2.element);
}
@override
bool visitIdentical(IdenticalConstantExpression exp1,
IdenticalConstantExpression exp2) {
return
strategy.testConstants(exp1, exp2, 'left', exp1.left, exp2.left) &&
strategy.testConstants(exp1, exp2, 'right', exp1.right, exp2.right);
}
@override
bool visitList(ListConstantExpression exp1, ListConstantExpression exp2) {
return
strategy.testTypes(
exp1, exp2, 'type',
exp1.type, exp2.type) &&
strategy.testConstantLists(
exp1, exp2, 'values',
exp1.values, exp2.values);
}
@override
bool visitMap(MapConstantExpression exp1, MapConstantExpression exp2) {
return
strategy.testTypes(
exp1, exp2, 'type',
exp1.type, exp2.type) &&
strategy.testConstantLists(
exp1, exp2, 'keys',
exp1.keys, exp2.keys) &&
strategy.testConstantLists(
exp1, exp2, 'values',
exp1.values, exp2.values);
}
@override
bool visitNamed(NamedArgumentReference exp1, NamedArgumentReference exp2) {
return strategy.test(exp1, exp2, 'name', exp1.name, exp2.name);
}
@override
bool visitPositional(PositionalArgumentReference exp1,
PositionalArgumentReference exp2) {
return strategy.test(exp1, exp2, 'index', exp1.index, exp2.index);
}
@override
bool visitSymbol(SymbolConstantExpression exp1,
SymbolConstantExpression exp2) {
// TODO: implement visitSymbol
return true;
}
@override
bool visitType(TypeConstantExpression exp1, TypeConstantExpression exp2) {
return strategy.testTypes(exp1, exp2, 'type', exp1.type, exp2.type);
}
@override
bool visitUnary(UnaryConstantExpression exp1, UnaryConstantExpression exp2) {
return
strategy.test(exp1, exp2, 'operator', exp1.operator, exp2.operator) &&
strategy.testConstants(
exp1, exp2, 'expression', exp1.expression, exp2.expression);
}
@override
bool visitVariable(VariableConstantExpression exp1,
VariableConstantExpression exp2) {
return
strategy.testElements(
exp1, exp2, 'element',
exp1.element, exp2.element);
}
@override
bool visitBool(BoolConstantExpression exp1, BoolConstantExpression exp2) {
return
strategy.test(
exp1, exp2, 'primitiveValue',
exp1.primitiveValue, exp2.primitiveValue);
}
@override
bool visitDouble(DoubleConstantExpression exp1,
DoubleConstantExpression exp2) {
return
strategy.test(
exp1, exp2, 'primitiveValue',
exp1.primitiveValue, exp2.primitiveValue);
}
@override
bool visitInt(IntConstantExpression exp1, IntConstantExpression exp2) {
return
strategy.test(
exp1, exp2, 'primitiveValue',
exp1.primitiveValue, exp2.primitiveValue);
}
@override
bool visitNull(NullConstantExpression exp1, NullConstantExpression exp2) {
return true;
}
@override
bool visitString(StringConstantExpression exp1,
StringConstantExpression exp2) {
return
strategy.test(
exp1, exp2, 'primitiveValue',
exp1.primitiveValue, exp2.primitiveValue);
}
@override
bool visitBoolFromEnvironment(BoolFromEnvironmentConstantExpression exp1,
BoolFromEnvironmentConstantExpression exp2) {
return
strategy.testConstants(exp1, exp2, 'name', exp1.name, exp2.name) &&
strategy.testConstants(
exp1, exp2, 'defaultValue',
exp1.defaultValue, exp2.defaultValue);
}
@override
bool visitIntFromEnvironment(IntFromEnvironmentConstantExpression exp1,
IntFromEnvironmentConstantExpression exp2) {
return
strategy.testConstants(exp1, exp2, 'name', exp1.name, exp2.name) &&
strategy.testConstants(
exp1, exp2, 'defaultValue',
exp1.defaultValue, exp2.defaultValue);
}
@override
bool visitStringFromEnvironment(
StringFromEnvironmentConstantExpression exp1,
StringFromEnvironmentConstantExpression exp2) {
return
strategy.testConstants(exp1, exp2, 'name', exp1.name, exp2.name) &&
strategy.testConstants(
exp1, exp2, 'defaultValue',
exp1.defaultValue, exp2.defaultValue);
}
@override
bool visitStringLength(StringLengthConstantExpression exp1,
StringLengthConstantExpression exp2) {
return
strategy.testConstants(
exp1, exp2, 'expression',
exp1.expression, exp2.expression);
}
@override
bool visitDeferred(DeferredConstantExpression exp1,
DeferredConstantExpression exp2) {
// TODO: implement visitDeferred
return true;
}
}
/// Tests the equivalence of [impact1] and [impact2] using [strategy].
bool testResolutionImpactEquivalence(
ResolutionImpact impact1,
ResolutionImpact impact2,
[TestStrategy strategy = const TestStrategy()]) {
return
strategy.testSets(
impact1, impact2, 'constSymbolNames',
impact1.constSymbolNames, impact2.constSymbolNames) &&
strategy.testSets(
impact1, impact2, 'constantLiterals',
impact1.constantLiterals, impact2.constantLiterals,
areConstantsEquivalent) &&
strategy.testSets(
impact1, impact2, 'dynamicUses',
impact1.dynamicUses, impact2.dynamicUses,
areDynamicUsesEquivalent) &&
strategy.testSets(
impact1, impact2, 'features',
impact1.features, impact2.features) &&
strategy.testSets(
impact1, impact2, 'listLiterals',
impact1.listLiterals, impact2.listLiterals,
areListLiteralUsesEquivalent) &&
strategy.testSets(
impact1, impact2, 'mapLiterals',
impact1.mapLiterals, impact2.mapLiterals,
areMapLiteralUsesEquivalent) &&
strategy.testSets(
impact1, impact2, 'staticUses',
impact1.staticUses, impact2.staticUses,
areStaticUsesEquivalent) &&
strategy.testSets(
impact1, impact2, 'typeUses',
impact1.typeUses, impact2.typeUses,
areTypeUsesEquivalent);
}