[kernel] Add tool for checking AST equivalence
Change-Id: Ie06776203080e91346582534af2d56c24581bd54
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/413200
Reviewed-by: Jens Johansen <jensj@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/tool/generate_ast_equivalence.dart b/pkg/front_end/tool/generate_ast_equivalence.dart
index c401b0f..56b049c 100644
--- a/pkg/front_end/tool/generate_ast_equivalence.dart
+++ b/pkg/front_end/tool/generate_ast_equivalence.dart
@@ -150,8 +150,6 @@
registerAstClassEquivalence(utilityFieldType.astClass);
sb.writeln('''($thisName, $otherName, _) {
if (identical($thisName, $otherName)) return true;
- if ($thisName is! ${utilityFieldType.astClass.name}) return false;
- if ($otherName is! ${utilityFieldType.astClass.name}) return false;
return ${classCheckName(utilityFieldType.astClass)}(
visitor,
$thisName,
@@ -209,8 +207,6 @@
registerAstClassEquivalence(utilityFieldType.astClass);
sb.writeln('''($thisName, $otherName, _) {
if (identical($thisName, $otherName)) return true;
- if ($thisName is! ${utilityFieldType.astClass.name}) return false;
- if ($otherName is! ${utilityFieldType.astClass.name}) return false;
return ${classCheckName(utilityFieldType.astClass)}(
visitor,
$thisName,
@@ -625,7 +621,7 @@
bool $checkLists<E>(
List<E>? a,
List<E>? b,
- bool Function(E?, E?, String) equivalentValues,
+ bool Function(E, E, String) equivalentValues,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
@@ -651,8 +647,8 @@
bool $checkSets<E>(
Set<E>? a,
Set<E>? b,
- bool Function(E?, E?) matchingValues,
- bool Function(E?, E?, String) equivalentValues,
+ bool Function(E, E) matchingValues,
+ bool Function(E, E, String) equivalentValues,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
@@ -702,9 +698,9 @@
bool $checkMaps<K, V>(
Map<K, V>? a,
Map<K, V>? b,
- bool Function(K?, K?) matchingKeys,
- bool Function(K?, K?, String) equivalentKeys,
- bool Function(V?, V?, String) equivalentValues,
+ bool Function(K, K) matchingKeys,
+ bool Function(K, K, String) equivalentKeys,
+ bool Function(V, V, String) equivalentValues,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
@@ -733,7 +729,7 @@
}
if (hasFoundKey) {
bKeys.remove(foundKey);
- if (!equivalentValues(a[aKey], b[foundKey],
+ if (!equivalentValues(a[aKey]!, b[foundKey]!,
'\${propertyName}[\${aKey}]')) {
return false;
}
diff --git a/pkg/kernel/bin/check_equivalence.dart b/pkg/kernel/bin/check_equivalence.dart
new file mode 100644
index 0000000..a9cbf2e
--- /dev/null
+++ b/pkg/kernel/bin/check_equivalence.dart
@@ -0,0 +1,6 @@
+#!/usr/bin/env dart
+// Copyright (c) 2025, 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.
+
+export 'package:kernel/src/tool/check_equivalence.dart';
diff --git a/pkg/kernel/lib/src/equivalence.dart b/pkg/kernel/lib/src/equivalence.dart
index fc463b9..0c3bb47 100644
--- a/pkg/kernel/lib/src/equivalence.dart
+++ b/pkg/kernel/lib/src/equivalence.dart
@@ -1253,7 +1253,7 @@
/// If run in a checking state, the [propertyName] is used for registering
/// inequivalences.
bool checkLists<E>(
- List<E>? a, List<E>? b, bool Function(E?, E?, String) equivalentValues,
+ List<E>? a, List<E>? b, bool Function(E, E, String) equivalentValues,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
@@ -1276,8 +1276,8 @@
///
/// If run in a checking state, the [propertyName] is used for registering
/// inequivalences.
- bool checkSets<E>(Set<E>? a, Set<E>? b, bool Function(E?, E?) matchingValues,
- bool Function(E?, E?, String) equivalentValues,
+ bool checkSets<E>(Set<E>? a, Set<E>? b, bool Function(E, E) matchingValues,
+ bool Function(E, E, String) equivalentValues,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
@@ -1325,9 +1325,9 @@
bool checkMaps<K, V>(
Map<K, V>? a,
Map<K, V>? b,
- bool Function(K?, K?) matchingKeys,
- bool Function(K?, K?, String) equivalentKeys,
- bool Function(V?, V?, String) equivalentValues,
+ bool Function(K, K) matchingKeys,
+ bool Function(K, K, String) equivalentKeys,
+ bool Function(V, V, String) equivalentValues,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
@@ -1355,7 +1355,7 @@
if (hasFoundKey) {
bKeys.remove(foundKey);
if (!equivalentValues(
- a[aKey], b[foundKey], '${propertyName}[${aKey}]')) {
+ a[aKey]!, b[foundKey]!, '${propertyName}[${aKey}]')) {
return false;
}
} else {
@@ -5716,8 +5716,6 @@
EquivalenceVisitor visitor, MapConstant node, MapConstant other) {
return visitor.checkLists(node.entries, other.entries, (a, b, _) {
if (identical(a, b)) return true;
- if (a is! ConstantMapEntry) return false;
- if (b is! ConstantMapEntry) return false;
return checkConstantMapEntry(visitor, a, b);
}, 'entries');
}
@@ -5902,8 +5900,6 @@
return visitor.checkMaps(node.uriToSource, other.uriToSource,
visitor.matchValues, visitor.checkValues, (a, b, _) {
if (identical(a, b)) return true;
- if (a is! Source) return false;
- if (b is! Source) return false;
return checkSource(visitor, a, b);
}, 'uriToSource');
}
@@ -5914,8 +5910,6 @@
node.metadata, other.metadata, visitor.matchValues, visitor.checkValues,
(a, b, _) {
if (identical(a, b)) return true;
- if (a is! MetadataRepository) return false;
- if (b is! MetadataRepository) return false;
return checkMetadataRepository(visitor, a, b);
}, 'metadata');
}
@@ -6089,8 +6083,6 @@
return visitor.checkLists(node.memberDescriptors, other.memberDescriptors,
(a, b, _) {
if (identical(a, b)) return true;
- if (a is! ExtensionMemberDescriptor) return false;
- if (b is! ExtensionMemberDescriptor) return false;
return checkExtensionMemberDescriptor(visitor, a, b);
}, 'memberDescriptors');
}
@@ -6192,8 +6184,6 @@
return visitor.checkLists(node.memberDescriptors, other.memberDescriptors,
(a, b, _) {
if (identical(a, b)) return true;
- if (a is! ExtensionTypeMemberDescriptor) return false;
- if (b is! ExtensionTypeMemberDescriptor) return false;
return checkExtensionTypeMemberDescriptor(visitor, a, b);
}, 'memberDescriptors');
}
diff --git a/pkg/kernel/lib/src/tool/check_equivalence.dart b/pkg/kernel/lib/src/tool/check_equivalence.dart
new file mode 100644
index 0000000..42dea47
--- /dev/null
+++ b/pkg/kernel/lib/src/tool/check_equivalence.dart
@@ -0,0 +1,408 @@
+// Copyright (c) 2025, 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 'dart:io';
+
+import 'package:_fe_analyzer_shared/src/util/options.dart';
+import 'package:kernel/ast.dart';
+import 'package:kernel/src/equivalence.dart';
+import 'package:kernel/src/tool/command_line_util.dart';
+
+void main(List<String> args) {
+ ParsedOptions parsedOptions = ParsedOptions.parse(args, optionSpecification);
+
+ CommandLineHelper.requireVariableArgumentCount([2], parsedOptions.arguments,
+ () {
+ print('''
+Usage:
+
+ dart check_equivalence.dart [options] <dill1> <dill2>
+''');
+ });
+ if (parsedOptions.arguments.length != 2) {
+ exit(1);
+ }
+
+ bool unordered = Options.unordered.read(parsedOptions);
+ bool unorderedLibraries =
+ unordered || Options.unorderedLibraries.read(parsedOptions);
+ bool unorderedLibraryDependencies =
+ unordered || Options.unorderedLibraries.read(parsedOptions);
+ bool unorderedAdditionalExports =
+ unordered || Options.unorderedLibraries.read(parsedOptions);
+ bool unorderedParts =
+ unordered || Options.unorderedLibraries.read(parsedOptions);
+ bool unorderedTypedefs =
+ unordered || Options.unorderedLibraries.read(parsedOptions);
+ bool unorderedClasses =
+ unordered || Options.unorderedLibraries.read(parsedOptions);
+ bool unorderedMembers =
+ unordered || Options.unorderedMembers.read(parsedOptions);
+ bool unorderedFields =
+ unorderedMembers || Options.unorderedFields.read(parsedOptions);
+ bool unorderedProcedures =
+ unorderedMembers || Options.unorderedProcedures.read(parsedOptions);
+ bool unorderedConstructors =
+ unorderedMembers || Options.unorderedConstructors.read(parsedOptions);
+ bool unorderedAnnotations =
+ unordered || Options.unorderedAnnotations.read(parsedOptions);
+
+ Component dill1 = CommandLineHelper.tryLoadDill(parsedOptions.arguments[0]);
+ Component dill2 = CommandLineHelper.tryLoadDill(parsedOptions.arguments[1]);
+ EquivalenceResult result = checkEquivalence(dill1, dill2,
+ strategy: new Strategy(
+ unorderedLibraries: unorderedLibraries,
+ unorderedLibraryDependencies: unorderedLibraryDependencies,
+ unorderedAdditionalExports: unorderedAdditionalExports,
+ unorderedParts: unorderedParts,
+ unorderedTypedefs: unorderedTypedefs,
+ unorderedClasses: unorderedClasses,
+ unorderedFields: unorderedFields,
+ unorderedProcedures: unorderedProcedures,
+ unorderedConstructors: unorderedConstructors,
+ unorderedAnnotations: unorderedAnnotations));
+ if (result.isEquivalent) {
+ print('The dills are equivalent.');
+ } else {
+ print('Inequivalence found:');
+ print(result);
+ }
+}
+
+EquivalenceResult checkNodeEquivalence(
+ Node node1,
+ Node node2, {
+ bool unorderedLibraries = false,
+ bool unorderedLibraryDependencies = false,
+ bool unorderedAdditionalExports = false,
+ bool unorderedParts = false,
+ bool unorderedTypedefs = false,
+ bool unorderedClasses = false,
+ bool unorderedFields = false,
+ bool unorderedProcedures = false,
+ bool unorderedConstructors = false,
+ bool unorderedAnnotations = false,
+}) {
+ return checkEquivalence(node1, node2,
+ strategy: new Strategy(
+ unorderedLibraries: unorderedLibraries,
+ unorderedLibraryDependencies: unorderedLibraryDependencies,
+ unorderedAdditionalExports: unorderedAdditionalExports,
+ unorderedParts: unorderedParts,
+ unorderedTypedefs: unorderedTypedefs,
+ unorderedClasses: unorderedClasses,
+ unorderedFields: unorderedFields,
+ unorderedProcedures: unorderedProcedures,
+ unorderedConstructors: unorderedConstructors,
+ unorderedAnnotations: unorderedAnnotations));
+}
+
+class Strategy extends EquivalenceStrategy {
+ final bool unorderedLibraries;
+ final bool unorderedLibraryDependencies;
+ final bool unorderedAdditionalExports;
+ final bool unorderedParts;
+ final bool unorderedTypedefs;
+ final bool unorderedClasses;
+ final bool unorderedFields;
+ final bool unorderedProcedures;
+ final bool unorderedConstructors;
+ final bool unorderedAnnotations;
+
+ Strategy(
+ {required this.unorderedLibraries,
+ required this.unorderedLibraryDependencies,
+ required this.unorderedAdditionalExports,
+ required this.unorderedParts,
+ required this.unorderedTypedefs,
+ required this.unorderedClasses,
+ required this.unorderedFields,
+ required this.unorderedProcedures,
+ required this.unorderedConstructors,
+ required this.unorderedAnnotations});
+
+ @override
+ bool checkComponent_libraries(
+ EquivalenceVisitor visitor, Component node, Component other) {
+ if (unorderedLibraries) {
+ return visitor.checkSets(node.libraries.toSet(), other.libraries.toSet(),
+ visitor.matchNamedNodes, visitor.checkNodes, 'libraries');
+ } else {
+ return visitor.checkLists(
+ node.libraries, other.libraries, visitor.checkNodes, 'libraries');
+ }
+ }
+
+ @override
+ bool checkLibrary_dependencies(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedLibraryDependencies) {
+ return visitor
+ .checkSets(node.dependencies.toSet(), other.dependencies.toSet(),
+ (LibraryDependency dependency1, LibraryDependency dependency2) {
+ return visitor.matchReferences(dependency1.importedLibraryReference,
+ dependency2.importedLibraryReference) &&
+ dependency1.flags == dependency2.flags &&
+ dependency1.name == dependency2.name &&
+ dependency1.fileOffset == dependency2.fileOffset;
+ }, visitor.checkNodes, 'dependencies');
+ } else {
+ return visitor.checkLists(node.dependencies, other.dependencies,
+ visitor.checkNodes, 'dependencies');
+ }
+ }
+
+ @override
+ bool checkLibrary_parts(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedParts) {
+ return visitor.checkSets(node.parts.toSet(), other.parts.toSet(),
+ (LibraryPart part1, LibraryPart part2) {
+ return part1.partUri == part2.partUri;
+ }, visitor.checkNodes, 'parts');
+ } else {
+ return visitor.checkLists(
+ node.parts, other.parts, visitor.checkNodes, 'parts');
+ }
+ }
+
+ @override
+ bool checkLibrary_typedefs(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedTypedefs) {
+ return visitor.checkSets(node.typedefs.toSet(), other.typedefs.toSet(),
+ visitor.matchNamedNodes, visitor.checkNodes, 'typedefs');
+ } else {
+ return visitor.checkLists(
+ node.typedefs, other.typedefs, visitor.checkNodes, 'typedefs');
+ }
+ }
+
+ @override
+ bool checkLibrary_additionalExports(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedAdditionalExports) {
+ return visitor.checkSets(
+ node.additionalExports.toSet(),
+ other.additionalExports.toSet(),
+ visitor.matchReferences,
+ visitor.checkReferences,
+ 'additionalExports');
+ } else {
+ return visitor.checkLists(node.additionalExports, other.additionalExports,
+ visitor.checkReferences, 'additionalExports');
+ }
+ }
+
+ @override
+ bool checkLibrary_classes(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedClasses) {
+ return visitor.checkSets(node.classes.toSet(), other.classes.toSet(),
+ visitor.matchNamedNodes, visitor.checkNodes, 'classes');
+ } else {
+ return visitor.checkLists(
+ node.classes, other.classes, visitor.checkNodes, 'classes');
+ }
+ }
+
+ @override
+ bool checkLibrary_fields(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedFields) {
+ return visitor.checkSets(node.fields.toSet(), other.fields.toSet(),
+ visitor.matchNamedNodes, visitor.checkNodes, 'fields');
+ } else {
+ return visitor.checkLists(
+ node.fields, other.fields, visitor.checkNodes, 'fields');
+ }
+ }
+
+ @override
+ bool checkLibrary_procedures(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedProcedures) {
+ return visitor.checkSets(
+ node.procedures.toSet(),
+ other.procedures.toSet(),
+ visitor.matchNamedNodes,
+ visitor.checkNodes,
+ 'procedures');
+ } else {
+ return visitor.checkLists(
+ node.procedures, other.procedures, visitor.checkNodes, 'procedures');
+ }
+ }
+
+ @override
+ bool checkLibrary_annotations(
+ EquivalenceVisitor visitor, Library node, Library other) {
+ if (unorderedAnnotations) {
+ return visitor.checkSets(
+ node.annotations.toSet(),
+ other.annotations.toSet(),
+ _matchAnnotations,
+ visitor.checkNodes,
+ 'annotations');
+ } else {
+ return visitor.checkLists(node.annotations, other.annotations,
+ visitor.checkNodes, 'annotations');
+ }
+ }
+
+ @override
+ bool checkClass_fields(EquivalenceVisitor visitor, Class node, Class other) {
+ if (unorderedFields) {
+ return visitor.checkSets(node.fields.toSet(), other.fields.toSet(),
+ visitor.matchNamedNodes, visitor.checkNodes, 'fields');
+ } else {
+ return visitor.checkLists(
+ node.fields, other.fields, visitor.checkNodes, 'fields');
+ }
+ }
+
+ @override
+ bool checkClass_procedures(
+ EquivalenceVisitor visitor, Class node, Class other) {
+ if (unorderedProcedures) {
+ return visitor.checkSets(
+ node.procedures.toSet(),
+ other.procedures.toSet(),
+ visitor.matchNamedNodes,
+ visitor.checkNodes,
+ 'procedures');
+ } else {
+ return visitor.checkLists(
+ node.procedures, other.procedures, visitor.checkNodes, 'procedures');
+ }
+ }
+
+ @override
+ bool checkClass_constructors(
+ EquivalenceVisitor visitor, Class node, Class other) {
+ if (unorderedConstructors) {
+ return visitor.checkSets(
+ node.constructors.toSet(),
+ other.constructors.toSet(),
+ visitor.matchNamedNodes,
+ visitor.checkNodes,
+ 'constructors');
+ } else {
+ return visitor.checkLists(node.constructors, other.constructors,
+ visitor.checkNodes, 'constructors');
+ }
+ }
+
+ @override
+ bool checkClass_annotations(
+ EquivalenceVisitor visitor, Class node, Class other) {
+ if (unorderedAnnotations) {
+ return visitor.checkSets(
+ node.annotations.toSet(),
+ other.annotations.toSet(),
+ _matchAnnotations,
+ visitor.checkNodes,
+ 'annotations');
+ } else {
+ return visitor.checkLists(node.annotations, other.annotations,
+ visitor.checkNodes, 'annotations');
+ }
+ }
+
+ @override
+ bool checkExtension_annotations(
+ EquivalenceVisitor visitor, Extension node, Extension other) {
+ if (unorderedAnnotations) {
+ return visitor.checkSets(
+ node.annotations.toSet(),
+ other.annotations.toSet(),
+ _matchAnnotations,
+ visitor.checkNodes,
+ 'annotations');
+ } else {
+ return visitor.checkLists(node.annotations, other.annotations,
+ visitor.checkNodes, 'annotations');
+ }
+ }
+
+ @override
+ bool checkMember_annotations(
+ EquivalenceVisitor visitor, Member node, Member other) {
+ if (unorderedAnnotations) {
+ return visitor.checkSets(
+ node.annotations.toSet(),
+ other.annotations.toSet(),
+ _matchAnnotations,
+ visitor.checkNodes,
+ 'annotations');
+ } else {
+ return visitor.checkLists(node.annotations, other.annotations,
+ visitor.checkNodes, 'annotations');
+ }
+ }
+
+ bool _matchAnnotations(Expression expression1, Expression expression2) {
+ return expression1.runtimeType == expression2.runtimeType &&
+ expression1.fileOffset == expression2.fileOffset;
+ }
+}
+
+class Flags {
+ static const String unordered = '--unordered';
+ static const String unorderedLibraries = '--unordered-libraries';
+ static const String unorderedLibraryDependencies =
+ '--unordered-library-dependencies';
+ static const String unorderedParts = '--unordered-parts';
+ static const String unorderedAdditionalExports =
+ '--unordered-additional-exports';
+ static const String unorderedTypedefs = '--unordered-typedefs';
+ static const String unorderedClasses = '--unordered-classes';
+ static const String unorderedMembers = '--unordered-members';
+ static const String unorderedFields = '--unordered-fields';
+ static const String unorderedProcedures = '--unordered-procedures';
+ static const String unorderedConstructors = '--unordered-constructors';
+ static const String unorderedAnnotations = '--unordered-annotations';
+}
+
+class Options {
+ static const Option<bool> unordered =
+ const Option(Flags.unordered, const BoolValue(false));
+ static const Option<bool> unorderedLibraries =
+ const Option(Flags.unorderedLibraries, const BoolValue(false));
+ static const Option<bool> unorderedLibraryDependencies =
+ const Option(Flags.unorderedLibraryDependencies, const BoolValue(false));
+ static const Option<bool> unorderedParts =
+ const Option(Flags.unorderedParts, const BoolValue(false));
+ static const Option<bool> unorderedAdditionalExports =
+ const Option(Flags.unorderedAdditionalExports, const BoolValue(false));
+ static const Option<bool> unorderedTypedefs =
+ const Option(Flags.unorderedTypedefs, const BoolValue(false));
+ static const Option<bool> unorderedClasses =
+ const Option(Flags.unorderedClasses, const BoolValue(false));
+ static const Option<bool> unorderedMembers =
+ const Option(Flags.unorderedMembers, const BoolValue(false));
+ static const Option<bool> unorderedFields =
+ const Option(Flags.unorderedFields, const BoolValue(false));
+ static const Option<bool> unorderedProcedures =
+ const Option(Flags.unorderedProcedures, const BoolValue(false));
+ static const Option<bool> unorderedConstructors =
+ const Option(Flags.unorderedConstructors, const BoolValue(false));
+ static const Option<bool> unorderedAnnotations =
+ const Option(Flags.unorderedAnnotations, const BoolValue(false));
+}
+
+const List<Option> optionSpecification = [
+ Options.unordered,
+ Options.unorderedLibraries,
+ Options.unorderedLibraryDependencies,
+ Options.unorderedParts,
+ Options.unorderedAdditionalExports,
+ Options.unorderedTypedefs,
+ Options.unorderedClasses,
+ Options.unorderedMembers,
+ Options.unorderedFields,
+ Options.unorderedProcedures,
+ Options.unorderedConstructors,
+ Options.unorderedAnnotations,
+];
diff --git a/pkg/kernel/test/check_equivalence_test.dart b/pkg/kernel/test/check_equivalence_test.dart
new file mode 100644
index 0000000..4996076
--- /dev/null
+++ b/pkg/kernel/test/check_equivalence_test.dart
@@ -0,0 +1,390 @@
+// Copyright (c) 2025, 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:expect/expect.dart';
+import 'package:kernel/ast.dart';
+import 'package:kernel/src/equivalence.dart' show EquivalenceResult;
+import 'package:kernel/src/tool/check_equivalence.dart';
+
+Uri fileUri1 = Uri.parse('file://uri1');
+Uri fileUri2 = Uri.parse('file://uri2');
+Uri fileUri3 = Uri.parse('file://uri3');
+Uri importUri1 = Uri.parse('import://uri1');
+Uri importUri2 = Uri.parse('import://uri2');
+Uri importUri3 = Uri.parse('import://uri3');
+
+List<Test> tests = [
+ new Test((_) => new Component()),
+ new Test((_) => new Component()
+ ..libraries.add(new Library(importUri1, fileUri: fileUri1))),
+ new Test((bool first) {
+ return new Component()
+ ..libraries.add(new Library(first ? importUri1 : importUri2,
+ fileUri: first ? fileUri1 : fileUri2))
+ ..libraries.add(new Library(first ? importUri2 : importUri1,
+ fileUri: first ? fileUri2 : fileUri1));
+ }, inequivalence: '''
+Inequivalent nodes
+1: library import://uri1
+2: library import://uri2
+.root
+ Component.libraries[0]
+''', unorderedLibraries: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library library1 = new Library(importUri1, fileUri: fileUri1);
+ Library library2 = new Library(importUri2, fileUri: fileUri2);
+ c.libraries.add(library1);
+ c.libraries.add(library2);
+ library1.dependencies
+ ..add(new LibraryDependency.import(first ? library2 : library1))
+ ..add(new LibraryDependency.import(first ? library1 : library2));
+ return c;
+ }, inequivalence: '''
+Inequivalent references:
+1: Reference to library import://uri2
+2: Reference to library import://uri1
+.root
+ Component.libraries[0]
+ Library(library import://uri1).dependencies[0]
+ LibraryDependency.importedLibraryReference
+Inequivalent references:
+1: Reference to library import://uri1
+2: Reference to library import://uri2
+.root
+ Component.libraries[0]
+ Library(library import://uri1).dependencies[1]
+ LibraryDependency.importedLibraryReference
+''', unorderedLibraryDependencies: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ Field f1 = new Field.immutable(new Name('field1'), fileUri: fileUri1);
+ l.addField(f1);
+ Field f2 = new Field.immutable(new Name('field2'), fileUri: fileUri1);
+ l.addField(f2);
+ l.additionalExports.add(first ? f1.getterReference : f2.getterReference);
+ l.additionalExports.add(first ? f2.getterReference : f1.getterReference);
+ return c;
+ }, inequivalence: '''
+Inequivalent references:
+1: Reference to field1
+2: Reference to field2
+.root
+ Component.libraries[0]
+ Library(library import://uri1).additionalExports[0]
+''', unorderedAdditionalExports: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ l.parts
+ ..add(new LibraryPart([], first ? '${fileUri2}' : '${fileUri3}')
+ ..parent = l)
+ ..add(new LibraryPart([], first ? '${fileUri3}' : '${fileUri2}')
+ ..parent = l);
+ return c;
+ }, inequivalence: '''
+Values file://uri2/ and file://uri3/ are not equivalent
+.root
+ Component.libraries[0]
+ Library(library import://uri1).parts[0]
+ LibraryPart.partUri
+Values file://uri3/ and file://uri2/ are not equivalent
+.root
+ Component.libraries[0]
+ Library(library import://uri1).parts[1]
+ LibraryPart.partUri
+''', unorderedParts: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ l
+ ..addTypedef(new Typedef(
+ first ? 'Typedef1' : 'Typedef2', const DynamicType(),
+ fileUri: fileUri1))
+ ..addTypedef(new Typedef(
+ first ? 'Typedef2' : 'Typedef1', const DynamicType(),
+ fileUri: fileUri1));
+ return c;
+ }, inequivalence: '''
+Inequivalent nodes
+1: Typedef(Typedef1)
+2: Typedef(Typedef2)
+.root
+ Component.libraries[0]
+ Library(library import://uri1).typedefs[0]
+''', unorderedTypedefs: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ l
+ ..addClass(
+ new Class(name: first ? 'Class1' : 'Class2', fileUri: fileUri1))
+ ..addClass(
+ new Class(name: first ? 'Class2' : 'Class1', fileUri: fileUri1));
+ return c;
+ }, inequivalence: '''
+Inequivalent nodes
+1: Class(Class1)
+2: Class(Class2)
+.root
+ Component.libraries[0]
+ Library(library import://uri1).classes[0]
+''', unorderedClasses: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ Field f1 = new Field.immutable(new Name('field1'), fileUri: fileUri1);
+ Field f2 = new Field.immutable(new Name('field2'), fileUri: fileUri1);
+ l.addField(first ? f1 : f2);
+ l.addField(first ? f2 : f1);
+ Class cls = new Class(name: first ? 'Class' : 'Class', fileUri: fileUri1);
+ l.addClass(cls);
+ Field f3 = new Field.immutable(new Name('field3'), fileUri: fileUri1);
+ Field f4 = new Field.immutable(new Name('field4'), fileUri: fileUri1);
+ cls.addField(first ? f3 : f4);
+ cls.addField(first ? f4 : f3);
+ return c;
+ }, inequivalence: '''
+Inequivalent nodes
+1: Class.field3
+2: Class.field4
+.root
+ Component.libraries[0]
+ Library(library import://uri1).classes[0]
+ Class(Class).fields[0]
+Inequivalent nodes
+1: field1
+2: field2
+.root
+ Component.libraries[0]
+ Library(library import://uri1).fields[0]
+''', unorderedFields: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ Procedure p1 = new Procedure(
+ new Name('procedure1'), ProcedureKind.Method, new FunctionNode(null),
+ fileUri: fileUri1);
+ Procedure p2 = new Procedure(
+ new Name('procedure2'), ProcedureKind.Method, new FunctionNode(null),
+ fileUri: fileUri1);
+ l.addProcedure(first ? p1 : p2);
+ l.addProcedure(first ? p2 : p1);
+ Class cls = new Class(name: first ? 'Class' : 'Class', fileUri: fileUri1);
+ l.addClass(cls);
+ Procedure p3 = new Procedure(
+ new Name('procedure3'), ProcedureKind.Method, new FunctionNode(null),
+ fileUri: fileUri1);
+ Procedure p4 = new Procedure(
+ new Name('procedure4'), ProcedureKind.Method, new FunctionNode(null),
+ fileUri: fileUri1);
+ cls.addProcedure(first ? p3 : p4);
+ cls.addProcedure(first ? p4 : p3);
+ return c;
+ }, inequivalence: '''
+Inequivalent nodes
+1: Class.procedure3
+2: Class.procedure4
+.root
+ Component.libraries[0]
+ Library(library import://uri1).classes[0]
+ Class(Class).procedures[0]
+Inequivalent nodes
+1: procedure1
+2: procedure2
+.root
+ Component.libraries[0]
+ Library(library import://uri1).procedures[0]
+''', unorderedProcedures: true),
+ new Test((bool first) {
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ Class cls = new Class(name: first ? 'Class' : 'Class', fileUri: fileUri1);
+ l.addClass(cls);
+ Constructor c1 = new Constructor(new FunctionNode(null),
+ name: new Name('constructor1'), fileUri: fileUri1);
+ Constructor c2 = new Constructor(new FunctionNode(null),
+ name: new Name('constructor2'), fileUri: fileUri1);
+ cls.addConstructor(first ? c1 : c2);
+ cls.addConstructor(first ? c2 : c1);
+ return c;
+ }, inequivalence: '''
+Inequivalent nodes
+1: Class.constructor1
+2: Class.constructor2
+.root
+ Component.libraries[0]
+ Library(library import://uri1).classes[0]
+ Class(Class).constructors[0]
+''', unorderedConstructors: true),
+ new Test((bool first) {
+ Expression createAnnotation1() =>
+ first ? new IntLiteral(0) : new StringLiteral("foo");
+ Expression createAnnotation2() =>
+ first ? new StringLiteral("foo") : new IntLiteral(0);
+
+ Component c = new Component();
+ Library l = new Library(importUri1, fileUri: fileUri1);
+ c.libraries.add(l);
+ l
+ ..addAnnotation(createAnnotation1())
+ ..addAnnotation(createAnnotation2());
+ Class cls = new Class(name: first ? 'Class' : 'Class', fileUri: fileUri1);
+ l.addClass(cls);
+ cls
+ ..addAnnotation(createAnnotation1())
+ ..addAnnotation(createAnnotation2());
+ Procedure p = new Procedure(
+ new Name('procedure'), ProcedureKind.Method, new FunctionNode(null),
+ fileUri: fileUri1);
+ l.addProcedure(p);
+ p
+ ..addAnnotation(createAnnotation1())
+ ..addAnnotation(createAnnotation2());
+ return c;
+ }, inequivalence: '''
+Inequivalent nodes
+1: IntLiteral(0)
+2: StringLiteral("foo")
+.root
+ Component.libraries[0]
+ Library(library import://uri1).annotations[0]
+Inequivalent nodes
+1: IntLiteral(0)
+2: StringLiteral("foo")
+.root
+ Component.libraries[0]
+ Library(library import://uri1).classes[0]
+ Class(Class).annotations[0]
+Inequivalent nodes
+1: IntLiteral(0)
+2: StringLiteral("foo")
+.root
+ Component.libraries[0]
+ Library(library import://uri1).procedures[0]
+ Procedure(procedure).annotations[0]
+''', unorderedAnnotations: true),
+];
+
+class Test {
+ final Node a;
+ final Node b;
+ final String? inequivalence;
+ final bool? unorderedLibraries;
+ final bool? unorderedLibraryDependencies;
+ final bool? unorderedAdditionalExports;
+ final bool? unorderedParts;
+ final bool? unorderedTypedefs;
+ final bool? unorderedClasses;
+ final bool? unorderedFields;
+ final bool? unorderedProcedures;
+ final bool? unorderedConstructors;
+ final bool? unorderedAnnotations;
+
+ Test(
+ Node Function(bool) create, {
+ this.inequivalence,
+ this.unorderedLibraries,
+ this.unorderedLibraryDependencies,
+ this.unorderedAdditionalExports,
+ this.unorderedParts,
+ this.unorderedTypedefs,
+ this.unorderedClasses,
+ this.unorderedFields,
+ this.unorderedProcedures,
+ this.unorderedConstructors,
+ this.unorderedAnnotations,
+ }) : a = create(true),
+ b = create(false);
+
+ bool get isEquivalent => inequivalence == null;
+
+ String get options {
+ List<String> list = [];
+ if (unorderedLibraries != null) {
+ list.add('unorderedLibraries=$unorderedLibraries');
+ }
+ if (unorderedLibraryDependencies != null) {
+ list.add('unorderedLibraryDependencies=$unorderedLibraryDependencies');
+ }
+ if (unorderedAdditionalExports != null) {
+ list.add('unorderedAdditionalExports=$unorderedAdditionalExports');
+ }
+ if (unorderedParts != null) {
+ list.add('unorderedParts=$unorderedParts');
+ }
+ if (unorderedTypedefs != null) {
+ list.add('unorderedTypedefs=$unorderedTypedefs');
+ }
+ if (unorderedClasses != null) {
+ list.add('unorderedClasses=$unorderedClasses');
+ }
+ if (unorderedFields != null) {
+ list.add('unorderedFields=$unorderedFields');
+ }
+ if (unorderedProcedures != null) {
+ list.add('unorderedProcedures=$unorderedProcedures');
+ }
+ if (unorderedConstructors != null) {
+ list.add('unorderedConstructors=$unorderedConstructors');
+ }
+ if (unorderedAnnotations != null) {
+ list.add('unorderedAnnotations=$unorderedAnnotations');
+ }
+ return list.join(',');
+ }
+}
+
+void main() {
+ for (Test test in tests) {
+ EquivalenceResult result = checkNodeEquivalence(test.a, test.b);
+ if (test.isEquivalent) {
+ Expect.equals(result.isEquivalent, test.isEquivalent,
+ 'Unexpected result for\n${test.a}\n${test.b}:\n$result');
+ Expect.equals(
+ '', test.options, 'Unexpected options for\n${test.a}\n${test.b}.');
+ } else if (result.isEquivalent) {
+ Expect.equals(
+ result.isEquivalent,
+ test.isEquivalent,
+ 'Unexpected equivalence for\n${test.a}\n${test.b}:\n'
+ 'Expected ${test.inequivalence}');
+ } else {
+ Expect.stringEquals(
+ result.toString(),
+ test.inequivalence!,
+ 'Unexpected inequivalence result for\n${test.a}\n${test.b}:\n'
+ 'Expected:\n---\n${test.inequivalence}\n---\n'
+ 'Actual:\n---\n${result}\n---');
+
+ EquivalenceResult optionResult = checkNodeEquivalence(
+ test.a,
+ test.b,
+ unorderedLibraries: test.unorderedLibraries ?? false,
+ unorderedLibraryDependencies:
+ test.unorderedLibraryDependencies ?? false,
+ unorderedAdditionalExports: test.unorderedAdditionalExports ?? false,
+ unorderedParts: test.unorderedParts ?? false,
+ unorderedTypedefs: test.unorderedTypedefs ?? false,
+ unorderedClasses: test.unorderedClasses ?? false,
+ unorderedFields: test.unorderedFields ?? false,
+ unorderedProcedures: test.unorderedProcedures ?? false,
+ unorderedConstructors: test.unorderedConstructors ?? false,
+ unorderedAnnotations: test.unorderedAnnotations ?? false,
+ );
+ Expect.isTrue(
+ optionResult.isEquivalent,
+ 'Unexpected result for\n${test.a}\n${test.b} with ${test.options}:\n'
+ '$optionResult');
+ }
+ }
+}