in pkg/front_end/tool/generate_ast_equivalence.dart [420:965]
void generateHeader(AstModel astModel, StringBuffer sb) {
sb.writeln('''
$preamble
import 'package:kernel/ast.dart';
import 'package:kernel/src/printer.dart';
import 'union_find.dart';
part 'equivalence_helpers.dart';
/// Visitor that uses a $strategyName to compute AST node equivalence.
///
/// The visitor hold a current state that collects found inequivalences and
/// current assumptions. The current state has two modes. In the asserting mode,
/// the default, inequivalences are registered when found. In the non-asserting
/// mode, inequivalences are _not_ registered. The latter is used to compute
/// equivalences in sandboxed state, for instance to determine which elements
/// to pair when checking equivalence of two sets.
class $visitorName$visitorTypeParameters
implements Visitor1<$returnType, $argumentType> {
final $strategyName strategy;
$visitorName({
this.strategy: const $strategyName()});
''');
}
@override
void generateFooter(AstModel astModel, StringBuffer sb) {
sb.writeln('''
/// Returns `true` if [a] and [b] are identical or equal.
bool $internalCheckValues<T>(T? a, T? b) {
return identical(a, b) || a == b;
}
/// Returns `true` if [a] and [b] are identical or equal and registers the
/// inequivalence otherwise.
bool $checkValues<T>(T? a, T? b, String propertyName) {
bool result = $internalCheckValues(a, b);
if (!result) {
registerInequivalence(
propertyName, 'Values \${a} and \${b} are not equivalent');
}
return result;
}
/// Returns `true` if [a] and [b] are identical or equal. Inequivalence is
/// _not_ registered.
bool $matchValues<T>(T? a, T? b) {
return $internalCheckValues(a, b);
}
/// Returns `true` if [a] and [b] are equivalent.
bool $internalCheckNodes<T extends Node>(T? a, T? b) {
if (identical(a, b)) return true;
if (a == null || b == null) {
return false;
} else {
return a.accept1(this, b);
}
}
/// Returns `true` if [a] and [b] are equivalent, as defined by the current
/// strategy, and registers the inequivalence otherwise.
bool $checkNodes<T extends Node>(T? a, T? b,
[String propertyName = '']) {
$checkingState.pushPropertyState(propertyName);
bool result = $internalCheckNodes(a, b);
$checkingState.popState();
if (!result) {
$registerInequivalence(
propertyName, 'Inequivalent nodes\\n1: \${a}\\n2: \${b}');
}
return result;
}
/// Returns `true` if [a] and [b] are identical or equal. Inequivalence is
/// _not_ registered.
bool $shallowMatchNodes<T extends Node>(T? a, T? b) {
return $internalCheckValues(a, b);
}
/// Returns `true` if [a] and [b] are equivalent, as defined by the current
/// strategy. Inequivalence is _not_ registered.
bool $deepMatchNodes<T extends Node>(T? a, T? b) {
CheckingState oldState = $checkingState;
$checkingState = $checkingState.toMatchingState();
bool result = $checkNodes(a, b);
$checkingState = oldState;
return result;
}
/// Returns `true` if [a] and [b] are equivalent, either by existing
/// assumption or as defined by their corresponding canonical names.
/// Inequivalence is _not_ registered.
bool $matchNamedNodes(NamedNode? a, NamedNode? b) {
return identical(a, b) ||
a == null ||
b == null ||
checkAssumedReferences(a.reference, b.reference) ||
new ReferenceName.fromNamedNode(a) ==
new ReferenceName.fromNamedNode(b);
}
/// Returns `true` if [a] and [b] are currently assumed to be equivalent.
bool $checkAssumedReferences(Reference? a, Reference? b) {
return $checkingState.$checkAssumedReferences(a, b);
}
/// Assume that [a] and [b] are equivalent, if possible.
///
/// Returns `true` if [a] and [b] could be assumed to be equivalent. This
/// would not be the case if [a] xor [b] is `null`.
bool $assumeReferences(Reference? a, Reference? b) {
return $checkingState.$assumeReferences(a, b);
}
/// Returns `true` if [a] and [b] are equivalent, either by existing
/// assumption or as defined by their corresponding canonical names.
/// Inequivalence is _not_ registered.
bool $matchReferences(Reference? a, Reference? b) {
return identical(a, b) ||
checkAssumedReferences(a, b) ||
ReferenceName.fromReference(a) ==
ReferenceName.fromReference(b);
}
/// Returns `true` if [a] and [b] are equivalent, either by their
/// corresponding canonical names or by assumption. Inequivalence is _not_
/// registered.
bool $internalCheckReferences(Reference? a, Reference? b) {
if (identical(a, b)) {
return true;
} else if (a == null || b == null) {
return false;
} else if ($matchReferences(a, b)) {
return true;
} else if ($checkAssumedReferences(a, b)) {
return true;
} else {
return false;
}
}
/// Returns `true` if [a] and [b] are equivalent, either by their
/// corresponding canonical names or by assumption. Inequivalence is _not_
/// registered.
bool $deepMatchReferences(Reference? a, Reference? b) {
CheckingState oldState = $checkingState;
$checkingState = $checkingState.toMatchingState();
bool result = $checkReferences(a, b);
$checkingState = oldState;
return result;
}
/// Returns `true` if [a] and [b] are equivalent, either by their
/// corresponding canonical names or by assumption, and registers the
/// inequivalence otherwise.
bool $checkReferences(
Reference? a,
Reference? b,
[String propertyName = '']) {
bool result = $internalCheckReferences(a, b);
if (!result) {
$registerInequivalence(
propertyName, 'Inequivalent references:\\n1: \${a}\\n2: \${b}');
}
return result;
}
/// Returns `true` if declarations [a] and [b] are currently assumed to be
/// equivalent.
bool $checkAssumedDeclarations(dynamic a, dynamic b) {
return $checkingState.$checkAssumedDeclarations(a, b);
}
/// Assume that [a] and [b] are equivalent, if possible.
///
/// Returns `true` if [a] and [b] could be assumed to be equivalent. This
/// would not be the case if [a] is already assumed to be equivalent to
/// another declaration.
bool $assumeDeclarations(dynamic a, dynamic b) {
return $checkingState.$assumeDeclarations(a, b);
}
bool $shallowMatchDeclarations(dynamic a, dynamic b) {''');
for (AstClass cls in astModel.declarativeClasses) {
if (cls.declarativeName != null) {
sb.write('''
if (a is ${cls.name}) {
return b is ${cls.name} &&
a.${cls.declarativeName} == b.${cls.declarativeName};
}
''');
} else {
sb.write('''
if (a is ${cls.name}) {
return b is ${cls.name};
}
''');
}
}
try {
try {
try {
sb.writeln('''
return false;
}
bool $internalCheckDeclarations(dynamic a, dynamic b) {
if (identical(a, b)) {
return true;
} else if (a == null || b == null) {
return false;
} else if ($checkAssumedDeclarations(a, b)) {
return true;
} else if ($shallowMatchDeclarations(a, b)) {
return $assumeDeclarations(a, b);
} else {
return false;
}
}
bool $deepMatchDeclarations(dynamic a, dynamic b) {
CheckingState oldState = $checkingState;
$checkingState = $checkingState.toMatchingState();
bool result = $checkDeclarations(a, b);
$checkingState = oldState;
return result;
}
bool $checkDeclarations(dynamic a, dynamic b,
[String propertyName = '']) {
bool result = $internalCheckDeclarations(a, b);
if (!result) {
result = $assumeDeclarations(a, b);
}
if (!result) {
$registerInequivalence(
propertyName, 'Declarations \${a} and \${b} are not equivalent');
}
return result;
}
/// Returns `true` if lists [a] and [b] are equivalent, using
/// [equivalentValues] to determine element-wise equivalence.
///
/// 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,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
if (a.length != b.length) {
$registerInequivalence(
'\${propertyName}.length', 'Lists \${a} and \${b} are not equivalent');
return false;
}
for (int i = 0; i < a.length; i++) {
if (!equivalentValues(a[i], b[i], '\${propertyName}[\${i}]')) {
return false;
}
}
return true;
}
/// Returns `true` if lists [a] and [b] are equivalent, using
/// [equivalentValues] to determine element-wise equivalence.
///
/// Inequivalence is _not_ registered.
bool $matchLists<E>(
List<E>? a,
List<E>? b,
bool Function(E?, E?, String) equivalentValues) {
CheckingState oldState = $checkingState;
$checkingState = $checkingState.toMatchingState();
bool result = $checkLists(a, b, equivalentValues);
$checkingState = oldState;
return result;
}
/// Returns `true` if sets [a] and [b] are equivalent, using
/// [matchingValues] to determine which elements that should be checked for
/// element-wise equivalence using [equivalentValues].
///
/// 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,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
if (a.length != b.length) {
$registerInequivalence(
'\${propertyName}.length', 'Sets \${a} and \${b} are not equivalent');
return false;
}
b = b.toSet();
for (E aValue in a) {
bool hasFoundValue = false;
E? foundValue;
for (E bValue in b) {
if (matchingValues(aValue, bValue)) {
foundValue = bValue;
hasFoundValue = true;
if (!equivalentValues(aValue, bValue,
'\${propertyName}[\${aValue}]')) {
$registerInequivalence(
'\${propertyName}[\${aValue}]',
'Elements \${aValue} and \${bValue} are not equivalent');
return false;
}
break;
}
}
if (hasFoundValue) {
b.remove(foundValue);
} else {
$registerInequivalence(
'\${propertyName}[\${aValue}]',
'Sets \${a} and \${b} are not equivalent, no equivalent value '
'found for \$aValue');
return false;
}
}
return true;
}
/// Returns `true` if sets [a] and [b] are equivalent, using
/// [matchingValues] to determine which elements that should be checked for
/// element-wise equivalence using [equivalentValues].
///
/// Inequivalence is _not_registered.
bool $matchSets<E>(
Set<E>? a,
Set<E>? b,
bool Function(E?, E?) matchingValues,
bool Function(E?, E?, String) equivalentValues) {
CheckingState oldState = $checkingState;
$checkingState = $checkingState.toMatchingState();
bool result = $checkSets(a, b, matchingValues, equivalentValues);
$checkingState = oldState;
return result;
}
/// Returns `true` if maps [a] and [b] are equivalent, using
/// [matchingKeys] to determine which entries that should be checked for
/// entry-wise equivalence using [equivalentKeys] and [equivalentValues] to
/// determine key and value equivalences, respectively.
///
/// If run in a checking state, the [propertyName] is used for registering
/// inequivalences.
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,
[String propertyName = '']) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
if (a.length != b.length) {
$registerInequivalence(
'\${propertyName}.length',
'Maps \${a} and \${b} are not equivalent');
return false;
}
Set<K> bKeys = b.keys.toSet();
for (K aKey in a.keys) {
bool hasFoundKey = false;
K? foundKey;
for (K bKey in bKeys) {
if (matchingKeys(aKey, bKey)) {
foundKey = bKey;
hasFoundKey = true;
if (!equivalentKeys(aKey, bKey, '\${propertyName}[\${aKey}]')) {
$registerInequivalence(
'\${propertyName}[\${aKey}]',
'Keys \${aKey} and \${bKey} are not equivalent');
return false;
}
break;
}
}
if (hasFoundKey) {
bKeys.remove(foundKey);
if (!equivalentValues(a[aKey], b[foundKey],
'\${propertyName}[\${aKey}]')) {
return false;
}
} else {
$registerInequivalence(
'\${propertyName}[\${aKey}]',
'Maps \${a} and \${b} are not equivalent, no equivalent key '
'found for \$aKey');
return false;
}
}
return true;
}
/// Returns `true` if maps [a] and [b] are equivalent, using
/// [matchingKeys] to determine which entries that should be checked for
/// entry-wise equivalence using [equivalentKeys] and [equivalentValues] to
/// determine key and value equivalences, respectively.
///
/// Inequivalence is _not_ registered.
bool $matchMaps<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) {
CheckingState oldState = $checkingState;
$checkingState = $checkingState.toMatchingState();
bool result = $checkMaps(a, b, matchingKeys, equivalentKeys,
equivalentValues);
$checkingState = oldState;
return result;
}
/// The current state of the visitor.
///
/// This holds the current assumptions, found inequivalences, and whether
/// inequivalences are currently registered.
CheckingState $checkingState = new CheckingState();
/// Runs [f] in a new state that holds all current assumptions. If
/// [isAsserting] is `true`, inequivalences are registered. Returns the
/// collected inequivalences.
///
/// If [f] returns `false`, the returned result is marked as having
/// inequivalences even when non have being registered.
EquivalenceResult inSubState(bool Function() f, {bool isAsserting: false}) {
CheckingState _oldState = $checkingState;
$checkingState = $checkingState.createSubState(isAsserting: isAsserting);
bool hasInequivalences = f();
EquivalenceResult result =
$checkingState.toResult(hasInequivalences: hasInequivalences);
$checkingState = _oldState;
return result;
}
/// Registers that the visitor enters the property named [propertyName] and
/// the currently visited node.
void pushPropertyState(String propertyName) {
$checkingState.pushPropertyState(propertyName);
}
/// Registers that the visitor enters nodes [a] and [b].
void pushNodeState(Node a, Node b) {
$checkingState.pushNodeState(a, b);
}
/// Register that the visitor leave the current node or property.
void popState() {
$checkingState.popState();
}
/// Returns the value used as the result for property inequivalences.
///
/// When inequivalences are currently registered, this is `true`, so that the
/// visitor will continue find inequivalences that are not directly related.
///
/// An example is finding several child inequivalences on otherwise equivalent
/// nodes, like finding inequivalences deeply in the members of the second
/// library of a component even when inequivalences deeply in the members of
/// the first library. Had the return value been `false`, signaling that the
/// first libraries were inequivalent, which they technically are, given that
/// the contain inequivalent subnodes, the visitor would have stopped short in
/// checking the list of libraries, and the inequivalences in the second
/// library would not have been found.
///
/// When inequivalences are _not_ currently registered, i.e. we are only
/// interested in the true/false value of the equivalence test, `false` is
/// used as the result value to stop the equivalence checking short.
bool get $resultOnInequivalence =>
$checkingState.$resultOnInequivalence;
/// Registers an equivalence on the [propertyName] with a detailed description
/// in [message].
void $registerInequivalence(String propertyName, String message) {
$checkingState.registerInequivalence(propertyName, message);
}
/// Returns the inequivalences found by the visitor.
EquivalenceResult toResult() => $checkingState.toResult();
''');
} catch (e, s) {
print(s);
}
} catch (e, s) {
print(s);
}
} catch (e, s) {
print(s);
}
super.generateFooter(astModel, sb);
sb.writeln('''
/// Checks [a] and [b] be for equivalence using [strategy].
///
/// Returns an [EquivalenceResult] containing the found inequivalences.
EquivalenceResult checkEquivalence(
Node a,
Node b,
{$strategyName strategy: const $strategyName()}) {
EquivalenceVisitor visitor = new EquivalenceVisitor(
strategy: strategy);
visitor.$checkNodes(a, b, 'root');
return visitor.toResult();
}
''');
sb.writeln('''
/// Strategy used for determining equivalence of AST nodes.
///
/// The strategy has a method for determining the equivalence of each AST node
/// class, and a method for determining the equivalence of each property on each
/// AST node class.
///
/// The base implementation enforces a full structural equivalence.
///
/// Custom strategies can be made by extending this strategy and override
/// methods where exceptions to the structural equivalence are needed.
class $strategyName {
const $strategyName();
''');
_classStrategyMembers.forEach((key, value) {
sb.write(value);
});
_fieldStrategyMembers.forEach((key, value) {
sb.write(value);
});
sb.writeln(r'''
}
''');
}
}