void generateHeader()

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'''
}
''');
  }
}