| Index: pkg/compiler/lib/src/serialization/equivalence.dart | 
| diff --git a/pkg/compiler/lib/src/serialization/equivalence.dart b/pkg/compiler/lib/src/serialization/equivalence.dart | 
| index 5df0a19cae4fffd884e2adcb968c578ccbbce194..be28121447478a00f77bd9d59506b7b5bb9cae63 100644 | 
| --- a/pkg/compiler/lib/src/serialization/equivalence.dart | 
| +++ b/pkg/compiler/lib/src/serialization/equivalence.dart | 
| @@ -11,8 +11,13 @@ import '../constants/expressions.dart'; | 
| import '../dart_types.dart'; | 
| import '../elements/elements.dart'; | 
| import '../elements/visitor.dart'; | 
| +import '../resolution/send_structure.dart'; | 
| +import '../resolution/tree_elements.dart'; | 
| +import '../tokens/token.dart'; | 
| +import '../tree/nodes.dart'; | 
| import '../universe/selector.dart'; | 
| import '../universe/use.dart'; | 
| +import 'resolved_ast_serialization.dart'; | 
|  | 
| /// Equality based equivalence function. | 
| bool equality(a, b) => a == b; | 
| @@ -90,6 +95,8 @@ bool areConstantListsEquivalent( | 
|  | 
| /// Returns `true` if the selectors [a] and [b] are equivalent. | 
| bool areSelectorsEquivalent(Selector a, Selector b) { | 
| +  if (identical(a, b)) return true; | 
| +  if (a == null || b == null) return false; | 
| return a.kind == b.kind && | 
| a.callStructure == b.callStructure && | 
| areNamesEquivalent(a.memberName, b.memberName); | 
| @@ -131,14 +138,33 @@ bool areMapLiteralUsesEquivalent(MapLiteralUse a, MapLiteralUse b) { | 
| a.isEmpty == b.isEmpty; | 
| } | 
|  | 
| +/// Returns `true` if the send structures [a] and [b] are equivalent. | 
| +bool areSendStructuresEquivalent(SendStructure a, SendStructure b) { | 
| +  if (identical(a, b)) return true; | 
| +  if (a == null || b == null) return false; | 
| +  if (a.kind != b.kind) return false; | 
| +  // TODO(johnniwinther): Compute a deep equivalence. | 
| +  return true; | 
| +} | 
| + | 
| +/// Returns `true` if the new structures [a] and [b] are equivalent. | 
| +bool areNewStructuresEquivalent(NewStructure a, NewStructure b) { | 
| +  if (identical(a, b)) return true; | 
| +  if (a == null || b == null) return false; | 
| +  if (a.kind != b.kind) return false; | 
| +  // TODO(johnniwinther): Compute a deep equivalence. | 
| +  return true; | 
| +} | 
| + | 
| /// 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 test(var object1, var object2, String property, var value1, var value2, | 
| +      [bool equivalence(a, b) = equality]) { | 
| +    return equivalence(value1, value2); | 
| } | 
|  | 
| bool testLists( | 
| @@ -483,8 +509,9 @@ class ConstantEquivalence | 
| @override | 
| bool visitSymbol( | 
| SymbolConstantExpression exp1, SymbolConstantExpression exp2) { | 
| -    // TODO: implement visitSymbol | 
| -    return true; | 
| +    // TODO(johnniwinther): Handle private names. Currently not even supported | 
| +    // in resolution. | 
| +    return strategy.test(exp1, exp2, 'name', exp1.name, exp2.name); | 
| } | 
|  | 
| @override | 
| @@ -572,7 +599,7 @@ class ConstantEquivalence | 
| @override | 
| bool visitDeferred( | 
| DeferredConstantExpression exp1, DeferredConstantExpression exp2) { | 
| -    // TODO: implement visitDeferred | 
| +    // TODO(johnniwinther): Implement this. | 
| return true; | 
| } | 
| } | 
| @@ -603,3 +630,178 @@ bool testResolutionImpactEquivalence( | 
| strategy.testSets(impact1, impact2, 'typeUses', impact1.typeUses, | 
| impact2.typeUses, areTypeUsesEquivalent); | 
| } | 
| + | 
| +/// Tests the equivalence of [resolvedAst1] and [resolvedAst2] using [strategy]. | 
| +bool testResolvedAstEquivalence( | 
| +    ResolvedAst resolvedAst1, ResolvedAst resolvedAst2, | 
| +    [TestStrategy strategy = const TestStrategy()]) { | 
| +  return strategy.testElements(resolvedAst1, resolvedAst2, 'element', | 
| +          resolvedAst1.element, resolvedAst2.element) && | 
| +      // Compute AST equivalence by structural comparison. | 
| +      strategy.test( | 
| +          resolvedAst1, | 
| +          resolvedAst2, | 
| +          'node', | 
| +          resolvedAst1.node.toDebugString(), | 
| +          resolvedAst2.node.toDebugString()) && | 
| +      testTreeElementsEquivalence(resolvedAst1, resolvedAst2, strategy); | 
| +} | 
| + | 
| +/// Tests the equivalence of the data stored in the [TreeElements] of | 
| +/// [resolvedAst1] and [resolvedAst2] using [strategy]. | 
| +bool testTreeElementsEquivalence( | 
| +    ResolvedAst resolvedAst1, ResolvedAst resolvedAst2, | 
| +    [TestStrategy strategy = const TestStrategy()]) { | 
| +  AstIndexComputer indices1 = new AstIndexComputer(); | 
| +  resolvedAst1.node.accept(indices1); | 
| +  AstIndexComputer indices2 = new AstIndexComputer(); | 
| +  resolvedAst2.node.accept(indices2); | 
| + | 
| +  TreeElements elements1 = resolvedAst1.elements; | 
| +  TreeElements elements2 = resolvedAst2.elements; | 
| + | 
| +  TreeElementsEquivalenceVisitor visitor = new TreeElementsEquivalenceVisitor( | 
| +      indices1, indices2, elements1, elements2, strategy); | 
| +  resolvedAst1.node.accept(visitor); | 
| +  return visitor.success; | 
| +} | 
| + | 
| +/// Visitor that checks the equivalence of [TreeElements] data. | 
| +class TreeElementsEquivalenceVisitor extends Visitor { | 
| +  final TestStrategy strategy; | 
| +  final AstIndexComputer indices1; | 
| +  final AstIndexComputer indices2; | 
| +  final TreeElements elements1; | 
| +  final TreeElements elements2; | 
| +  bool success = true; | 
| + | 
| +  TreeElementsEquivalenceVisitor( | 
| +      this.indices1, this.indices2, this.elements1, this.elements2, | 
| +      [this.strategy = const TestStrategy()]); | 
| + | 
| +  visitNode(Node node1) { | 
| +    if (!success) return; | 
| +    int index = indices1.nodeIndices[node1]; | 
| +    Node node2 = indices2.nodeList[index]; | 
| +    success = strategy.testElements( | 
| +            node1, node2, '[$index]', elements1[node1], elements2[node2]) && | 
| +        strategy.testTypes(node1, node2, 'getType($index)', | 
| +            elements1.getType(node1), elements2.getType(node2)) && | 
| +        strategy.test( | 
| +            node1, | 
| +            node2, | 
| +            'getSelector($index)', | 
| +            elements1.getSelector(node1), | 
| +            elements2.getSelector(node2), | 
| +            areSelectorsEquivalent) && | 
| +        strategy.testConstants(node1, node2, 'getConstant($index)', | 
| +            elements1.getConstant(node1), elements2.getConstant(node2)) && | 
| +        strategy.testTypes(node1, node2, 'typesCache[$index]', | 
| +            elements1.typesCache[node1], elements2.typesCache[node2]); | 
| + | 
| +    node1.visitChildren(this); | 
| +  } | 
| + | 
| +  @override | 
| +  visitSend(Send node1) { | 
| +    visitExpression(node1); | 
| +    if (!success) return; | 
| +    int index = indices1.nodeIndices[node1]; | 
| +    Send node2 = indices2.nodeList[index]; | 
| +    success = strategy.test(node1, node2, 'isTypeLiteral($index)', | 
| +            elements1.isTypeLiteral(node1), elements2.isTypeLiteral(node2)) && | 
| +        strategy.testTypes( | 
| +            node1, | 
| +            node2, | 
| +            'getTypeLiteralType($index)', | 
| +            elements1.getTypeLiteralType(node1), | 
| +            elements2.getTypeLiteralType(node2)) && | 
| +        strategy.test( | 
| +            node1, | 
| +            node2, | 
| +            'getSendStructure($index)', | 
| +            elements1.getSendStructure(node1), | 
| +            elements2.getSendStructure(node2), | 
| +            areSendStructuresEquivalent); | 
| +  } | 
| + | 
| +  @override | 
| +  visitNewExpression(NewExpression node1) { | 
| +    visitExpression(node1); | 
| +    if (!success) return; | 
| +    int index = indices1.nodeIndices[node1]; | 
| +    NewExpression node2 = indices2.nodeList[index]; | 
| +    success = strategy.test( | 
| +        node1, | 
| +        node2, | 
| +        'getNewStructure($index)', | 
| +        elements1.getNewStructure(node1), | 
| +        elements2.getNewStructure(node2), | 
| +        areNewStructuresEquivalent); | 
| +  } | 
| + | 
| +  @override | 
| +  visitSendSet(SendSet node1) { | 
| +    visitSend(node1); | 
| +    if (!success) return; | 
| +    int index = indices1.nodeIndices[node1]; | 
| +    SendSet node2 = indices2.nodeList[index]; | 
| +    success = strategy.test( | 
| +            node1, | 
| +            node2, | 
| +            'getGetterSelectorInComplexSendSet($index)', | 
| +            elements1.getGetterSelectorInComplexSendSet(node1), | 
| +            elements2.getGetterSelectorInComplexSendSet(node2), | 
| +            areSelectorsEquivalent) && | 
| +        strategy.test( | 
| +            node1, | 
| +            node2, | 
| +            'getOperatorSelectorInComplexSendSet($index)', | 
| +            elements1.getOperatorSelectorInComplexSendSet(node1), | 
| +            elements2.getOperatorSelectorInComplexSendSet(node2), | 
| +            areSelectorsEquivalent); | 
| +  } | 
| + | 
| +  @override | 
| +  visitFunctionExpression(FunctionExpression node1) { | 
| +    visitNode(node1); | 
| +    if (!success) return; | 
| +    int index = indices1.nodeIndices[node1]; | 
| +    FunctionExpression node2 = indices2.nodeList[index]; | 
| +    if (elements1[node1] is! FunctionElement) { | 
| +      // [getFunctionDefinition] is currently stored in [] which doesn't always | 
| +      // contain a [FunctionElement]. | 
| +      return; | 
| +    } | 
| +    success = strategy.testElements( | 
| +        node1, | 
| +        node2, | 
| +        'getFunctionDefinition($index)', | 
| +        elements1.getFunctionDefinition(node1), | 
| +        elements2.getFunctionDefinition(node2)); | 
| +  } | 
| + | 
| +  @override | 
| +  visitForIn(ForIn node1) { | 
| +    visitLoop(node1); | 
| +    if (!success) return; | 
| +    int index = indices1.nodeIndices[node1]; | 
| +    ForIn node2 = indices2.nodeList[index]; | 
| +    success = strategy.testElements(node1, node2, 'getForInVariable($index)', | 
| +        elements1.getForInVariable(node1), elements2.getForInVariable(node2)); | 
| +  } | 
| + | 
| +  @override | 
| +  visitRedirectingFactoryBody(RedirectingFactoryBody node1) { | 
| +    visitStatement(node1); | 
| +    if (!success) return; | 
| +    int index = indices1.nodeIndices[node1]; | 
| +    RedirectingFactoryBody node2 = indices2.nodeList[index]; | 
| +    success = strategy.testElements( | 
| +        node1, | 
| +        node2, | 
| +        'getRedirectingTargetConstructor($index)', | 
| +        elements1.getRedirectingTargetConstructor(node1), | 
| +        elements2.getRedirectingTargetConstructor(node2)); | 
| +  } | 
| +} | 
|  |