[cfe] Generate equivalence visitor

+ use it to allow discrepancy in incremental_dart2js_load_from_dill_test

Change-Id: I0adad30af1e502d81e870b26eb63a03b70e42333
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/205796
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Johnni Winther 2021-08-02 19:50:20 +00:00 committed by commit-bot@chromium.org
parent 1e0a1d9732
commit 8038ecab65
19 changed files with 10789 additions and 48 deletions

View file

@ -398,6 +398,11 @@ class TypeVariableTypeWithContext implements ir.Node {
throw new UnsupportedError('TypeVariableTypeWithContext.accept');
}
@override
R accept1<R, A>(ir.Visitor1<R, A> v, A arg) {
throw new UnsupportedError('TypeVariableTypeWithContext.accept1');
}
@override
visitChildren(ir.Visitor v) {
throw new UnsupportedError('TypeVariableTypeWithContext.visitChildren');

View file

@ -1315,8 +1315,7 @@ class ConstantEvaluator implements ExpressionVisitor<Constant> {
} else {
bool sentinelInserted = false;
if (nodeCache.containsKey(node)) {
bool isRecursiveFunctionCall =
node is InstanceInvocation ||
bool isRecursiveFunctionCall = node is InstanceInvocation ||
node is FunctionInvocation ||
node is LocalFunctionInvocation ||
node is StaticInvocation;
@ -4200,11 +4199,21 @@ class FunctionValue implements Constant {
throw new UnimplementedError();
}
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();
@ -4260,11 +4269,21 @@ class _AbortDueToErrorConstant extends AbortConstant {
throw new UnimplementedError();
}
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();
@ -4317,11 +4336,21 @@ class _AbortDueToInvalidExpressionConstant extends AbortConstant {
throw new UnimplementedError();
}
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();
@ -4374,11 +4403,21 @@ class _AbortDueToThrowConstant extends AbortConstant {
throw new UnimplementedError();
}
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();

View file

@ -13,16 +13,18 @@ import "../tool/_fasta/direct_parser_ast_helper_creator.dart"
as generateDirectParserAstHelper;
import "parser_test_listener_creator.dart" as generateParserTestListener;
import "parser_test_parser_creator.dart" as generateParserTestParser;
import '../tool/generate_ast_equivalence.dart' as generateAstEquivalence;
import 'utils/io_utils.dart' show computeRepoDirUri;
final Uri repoDir = computeRepoDirUri();
main() {
main() async {
messages();
experimentalFlags();
directParserAstHelper();
parserTestListener();
parserTestParser();
await astEquivalence();
}
void parserTestParser() {
@ -48,6 +50,14 @@ void directParserAstHelper() {
"dart pkg/front_end/tool/_fasta/direct_parser_ast_helper_creator.dart");
}
Future<void> astEquivalence() async {
Uri generatedFile = generateAstEquivalence.computeEquivalenceUri(repoDir);
String generated =
await generateAstEquivalence.generateAstEquivalence(repoDir);
check(generated, generatedFile,
"dart pkg/front_end/tool/generate_ast_equivalence.dart");
}
void experimentalFlags() {
{
Uri generatedFile =

View file

@ -13,7 +13,8 @@ import 'package:front_end/src/compute_platform_binaries_location.dart'
import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
import 'package:kernel/kernel.dart' show Component;
import 'package:kernel/ast.dart';
import 'package:kernel/src/equivalence.dart';
import 'package:kernel/target/targets.dart';
import 'incremental_suite.dart'
@ -93,7 +94,19 @@ Future<void> testDart2jsCompile() async {
List<int> normalDillData = new File.fromUri(normalDill).readAsBytesSync();
List<int> initializedDillData =
new File.fromUri(fullDillFromInitialized).readAsBytesSync();
checkIsEqual(normalDillData, initializedDillData);
Component component1 = new Component();
new BinaryBuilder(normalDillData).readComponent(component1);
Component component2 = new Component();
new BinaryBuilder(initializedDillData).readComponent(component2);
EquivalenceResult result =
checkEquivalence(component1, component2, strategy: const Strategy());
Expect.isTrue(result.isEquivalent, result.toString());
// TODO(johnniwinther): Reenable this check when the discrepancies have been
// fixed.
//checkIsEqual(normalDillData, initializedDillData);
// Also try without invalidating anything.
stopwatch.reset();
@ -110,3 +123,49 @@ Future<void> testDart2jsCompile() async {
checkIsEqual(normalDillData, initializedDillData);
}
}
class Strategy extends EquivalenceStrategy {
const Strategy();
@override
bool checkClass_procedures(
EquivalenceVisitor visitor, Class node, Class other) {
// Check procedures as a set instead of a list to allow for reordering.
return visitor.checkSets(node.procedures.toSet(), other.procedures.toSet(),
visitor.matchNamedNodes, visitor.checkNodes, 'procedures');
}
bool _isMixinOrCloneReference(EquivalenceVisitor visitor, Reference a,
Reference b, String propertyName) {
if (a != null && b != null) {
ReferenceName thisName = ReferenceName.fromReference(a);
ReferenceName otherName = ReferenceName.fromReference(b);
if (thisName.kind == ReferenceNameKind.Member &&
otherName.kind == ReferenceNameKind.Member &&
thisName.memberName == otherName.memberName) {
String thisClassName = thisName.declarationName;
String otherClassName = otherName.declarationName;
if (thisClassName != null &&
otherClassName != null &&
thisClassName.contains('&${otherClassName}')) {
visitor.assumeReferences(a, b);
}
}
}
return visitor.checkReferences(a, b, propertyName);
}
@override
bool checkProcedure_stubTargetReference(
EquivalenceVisitor visitor, Procedure node, Procedure other) {
return _isMixinOrCloneReference(visitor, node.stubTargetReference,
other.stubTargetReference, 'stubTargetReference');
}
@override
bool checkInstanceGet_interfaceTargetReference(
EquivalenceVisitor visitor, InstanceGet node, InstanceGet other) {
return _isMixinOrCloneReference(visitor, node.interfaceTargetReference,
other.interfaceTargetReference, 'interfaceTargetReference');
}
}

View file

@ -62,6 +62,7 @@ asgerf
askesc
aspect
aspx
asserting
assigning
assigns
association
@ -389,12 +390,14 @@ enforce
enforced
enforces
enforcing
enters
engineered
enumerates
env
eof
eq
equation
equivalences
erased
erasure
es
@ -581,6 +584,9 @@ indexing
indirection
individual
inequality
inequivalence
inequivalences
inequivalent
influence
informative
infos
@ -605,6 +611,7 @@ int64
int8
integrate
intentionally
interested
interim
interior
interleaved
@ -787,6 +794,7 @@ nj
nk
nnbd
node's
nominality
nonimplementation
norm
normalization
@ -1065,6 +1073,7 @@ rpc
rs
runnable
s
sand
sanitizing
saw
say
@ -1110,6 +1119,7 @@ sibling
siblings
sides
sigmund
signaling
significant
simplify
singleton
@ -1208,6 +1218,7 @@ svg
sw
swapped
sweep
symbolic
synchronously
syncs
synth
@ -1417,6 +1428,7 @@ widgets
wiki
wikipedia
wind
wise
wiser
with1
wn

View file

@ -45,6 +45,7 @@ asdf
asserter
assure
asterisk
atomic
auth
authority
autobianchi
@ -148,6 +149,7 @@ cafebabe
calloc
camel
capitalized
categorization
causal
cb
cc
@ -204,6 +206,7 @@ confident
confirm
consecutive
considering
consist
constrains
consts
contract
@ -250,6 +253,7 @@ db
ddart
dds
debugger
declarative
decrease
decrements
dectcem
@ -283,6 +287,7 @@ disagreement
disallowed
disconnect
discovering
discrepancies
dispatcher
dispose
dist
@ -314,10 +319,12 @@ elapse
elegantly
ell
emethod
enters
entrypoint
entrypoints
eoo
epoch
equivalences
erase
erased
err
@ -538,6 +545,7 @@ instrument
insufficient
intdiv
interactive
interested
internet
interpolate
introducer
@ -670,6 +678,8 @@ newworld
nil
ninja
noisy
nominal
nominality
nondefault
nonexisting
noo
@ -770,6 +780,8 @@ redir
redirections
rediscover
reducer
reenable
referential
referring
reflectee
refusing
@ -781,6 +793,7 @@ reject
reload
remap
remaps
renames
rendition
reorder
reordering
@ -803,6 +816,7 @@ row
rows
runtimes
rv
sand
saves
scans
scheduler
@ -816,6 +830,7 @@ segment
selection
semifuzz
sensitive
serves
services
setter1a
setter1b
@ -829,6 +844,7 @@ shipped
shortest
shot
sigint
signaling
signalled
sigwinch
slashes
@ -951,6 +967,8 @@ vars
verbatim
versioned
vf
visitor0
visitor1
vp
vt
vte
@ -964,6 +982,7 @@ weekly
wherever
whiskers
wins
wise
wording
workflow
worlds

View file

@ -0,0 +1,771 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
import 'package:front_end/src/api_prototype/front_end.dart';
import 'package:front_end/src/api_prototype/kernel_generator.dart';
import 'package:front_end/src/api_prototype/terminal_color_support.dart';
import 'package:front_end/src/api_unstable/ddc.dart';
import 'package:front_end/src/compute_platform_binaries_location.dart';
import 'package:front_end/src/fasta/kernel/kernel_api.dart';
import 'package:front_end/src/kernel_generator_impl.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/src/printer.dart';
import 'package:kernel/type_environment.dart';
final Uri astLibraryUri = Uri.parse('package:kernel/ast.dart');
final Uri canonicalNameLibraryUri =
Uri.parse('package:kernel/canonical_name.dart');
Uri computePackageConfig(Uri repoDir) =>
repoDir.resolve('.dart_tool/package_config.json');
/// Map from names of node classes that declares nominal entities that are _not_
/// referenced through a [Reference] to their identifying name, if any.
///
/// For these classes, [_fieldRuleMap] must defined whether fields of these
/// types should be consider declarations or references to the declarations.
///
/// If the identifying name is non-null, this is used to determine the
/// nominality. For instance the name of a variable declaration is taking as
/// defining its identity.
const Map<String, String> _declarativeClassesNames = const {
'VariableDeclaration': 'name',
'TypeParameter': 'name',
'LabeledStatement': null,
'SwitchCase': null,
};
/// Names of non-node, non-enum classes that should be treated as atomic
/// values.
const Set<String> _utilityClassesAsValues = const {
'Version',
};
/// Names of subclasses of [Node] that do _not_ have `visitX` or `defaultX`
/// methods.
const Set<String> _classesWithoutVisitMethods = const {
'InvocationExpression',
'InstanceInvocationExpression',
'NamedNode',
'PrimitiveConstant',
};
/// Names of subclasses of [NamedNode] that do _not_ have `visitXReference` or
/// `defaultXReference` methods.
const Set<String> _classesWithoutVisitReference = const {
'NamedNode',
'Library',
'PrimitiveConstant',
};
/// Map of [FieldRule]s. These consist of a map for each class name, with
/// `null` used for "any class". For each class, a map from field names to
/// [FieldRule] define exceptions to how a field should be treated.
///
/// If a field name maps to `null`, the field is not included in the [AstModel].
const Map<String /*?*/, Map<String, FieldRule /*?*/ >> _fieldRuleMap = {
null: {
'hashCode': null,
'parent': null,
},
'Component': {
'root': null,
'_mainMethodName': FieldRule(name: 'mainMethodName'),
'_mode': FieldRule(name: 'mode'),
},
'Library': {
'_languageVersion': FieldRule(name: 'languageVersion'),
'_libraryId': null,
'_classes': FieldRule(name: 'classes'),
'_typedefs': FieldRule(name: 'typedefs'),
'_extensions': FieldRule(name: 'extensions'),
'_fields': FieldRule(name: 'fields'),
'_procedures': FieldRule(name: 'procedures'),
},
'Class': {
'typeParameters': FieldRule(isDeclaration: true),
'_constructorsView': null,
'_constructorsInternal': FieldRule(name: 'constructors'),
'_fieldsView': null,
'_fieldsInternal': FieldRule(name: 'fields'),
'_proceduresView': null,
'_proceduresInternal': FieldRule(name: 'procedures'),
'_redirectingFactoriesView': null,
'_redirectingFactoriesInternal':
FieldRule(name: 'redirectingFactories'),
'lazyBuilder': null,
'dirty': null,
},
'Extension': {
'typeParameters': FieldRule(isDeclaration: true),
},
'Field': {
'reference': FieldRule(name: 'getterReference'),
},
'TypeParameter': {
'_variance': FieldRule(name: 'variance'),
},
'FunctionNode': {
'_body': FieldRule(name: 'body'),
'typeParameters': FieldRule(isDeclaration: true),
'positionalParameters': FieldRule(isDeclaration: true),
'namedParameters': FieldRule(isDeclaration: true),
},
'Typedef': {
'typeParameters': FieldRule(isDeclaration: true),
'typeParametersOfFunctionType': FieldRule(isDeclaration: false),
'positionalParameters': FieldRule(isDeclaration: false),
'namedParameters': FieldRule(isDeclaration: false),
},
'TypedefTearOff': {
'typeParameters': FieldRule(isDeclaration: true),
},
'TypedefTearOffConstant': {
'parameters': FieldRule(isDeclaration: true),
},
'LocalInitializer': {
'variable': FieldRule(isDeclaration: true),
},
'Let': {
'variable': FieldRule(isDeclaration: true),
},
'VariableGet': {
'variable': FieldRule(isDeclaration: false),
},
'VariableSet': {
'variable': FieldRule(isDeclaration: false),
},
'LocalFunctionInvocation': {
'variable': FieldRule(isDeclaration: false),
},
'BreakStatement': {
'target': FieldRule(isDeclaration: false),
},
'ForStatement': {
'variables': FieldRule(isDeclaration: true),
},
'ForInStatement': {
'variable': FieldRule(isDeclaration: true),
},
'SwitchStatement': {
'cases': FieldRule(isDeclaration: true),
},
'ContinueSwitchStatement': {
'target': FieldRule(isDeclaration: false),
},
'Catch': {
'exception': FieldRule(isDeclaration: true),
'stackTrace': FieldRule(isDeclaration: true),
},
'FunctionDeclaration': {
'variable': FieldRule(isDeclaration: true),
},
'FunctionType': {
'typeParameters': FieldRule(isDeclaration: true),
},
'TypeParameterType': {
'parameter': FieldRule(isDeclaration: false),
},
};
/// Data that determines exceptions to how fields are used.
class FieldRule {
/// If non-null, the field should be accessed by this name.
///
/// This is for instance used for private fields accessed through a public
/// getter.
final String name;
/// For fields contain ast class of kind `AstClassKind.declarative`, this
/// value defines whether the field should be treated as a declaration or
/// a reference to the declaration.
final bool isDeclaration;
const FieldRule({this.name, this.isDeclaration});
}
/// Return the [FieldRule] to use for the [field] in [AstClass].
FieldRule getFieldRule(AstClass astClass, Field field) {
String name = field.name.text;
Map<String, FieldRule> leafClassMap = _fieldRuleMap[astClass.name];
if (leafClassMap != null && leafClassMap.containsKey(name)) {
return leafClassMap[name];
}
Map<String, FieldRule> enclosingClassMap =
_fieldRuleMap[field.enclosingClass.name];
if (enclosingClassMap != null && enclosingClassMap.containsKey(name)) {
return enclosingClassMap[name];
}
Map<String, FieldRule> defaultClassMap = _fieldRuleMap[null];
if (defaultClassMap != null && defaultClassMap.containsKey(name)) {
return defaultClassMap[name];
}
return new FieldRule(name: name);
}
/// Categorization of classes declared in 'package:kernel/ast.dart'.
enum AstClassKind {
/// The root [Node] class.
root,
/// An abstract node class that is a superclass of a public class. Most of
/// these have a corresponding `defaultX` visitor method.
///
/// For instance [Statement] and [Expression].
inner,
/// A concrete node class. These are the classes for which we have `visitX`
/// methods.
///
/// For instance [Procedure] and [VariableGet].
public,
/// A concrete node class that serves only as an implementation of public
/// node class.
///
/// For instance [PrivateName] and [PublicName] that implements the public
/// node [Name].
implementation,
/// A node class that serves as an interface.
///
/// For instance `FileUriNode`.
interface,
/// A named node class that declares a nominal entity that is used with a
/// reference.
named,
/// A node class that declares a nominal entity but used without reference.
///
/// For instance [VariableDeclaration] which introduces a new entity when it
/// occurs in a [Block] but is used as a reference when it occurs in a
/// [VariableGet].
declarative,
/// A none-node class that should be treated as a composite structure.
///
/// For instance [ConstantMapEntry] which is a tuple of a [Constant] key and
/// a [Constant] value.
utilityAsStructure,
/// A none-node class that should be treated as an atomic value.
///
/// For instance enum classes.
utilityAsValue,
}
class AstClass {
final Class node;
AstClassKind _kind;
final String declarativeName;
AstClass superclass;
List<AstClass> interfaces = [];
List<AstClass> subclasses = [];
List<AstClass> subtypes = [];
List<AstField> fields = [];
AstClass(this.node,
{this.superclass, AstClassKind kind, this.declarativeName})
: _kind = kind {
if (superclass != null) {
superclass.subclasses.add(this);
}
}
String get name => node.name;
AstClassKind get kind {
if (_kind == null) {
if (node.isAbstract) {
if (subclasses.isNotEmpty) {
if (subclasses.every(
(element) => element.kind == AstClassKind.implementation)) {
_kind = AstClassKind.public;
} else {
_kind = AstClassKind.inner;
}
} else {
_kind = AstClassKind.interface;
}
} else {
if (node.name.startsWith('_')) {
_kind = AstClassKind.implementation;
} else {
_kind = AstClassKind.public;
}
}
}
return _kind;
}
/// Returns `true` if this class has a `visitX` or `defaultX` method.
///
/// This is only valid for subclass of [Node].
bool get hasVisitMethod {
switch (kind) {
case AstClassKind.root:
case AstClassKind.inner:
case AstClassKind.public:
case AstClassKind.named:
case AstClassKind.declarative:
return !_classesWithoutVisitMethods.contains(name);
case AstClassKind.implementation:
case AstClassKind.interface:
case AstClassKind.utilityAsStructure:
case AstClassKind.utilityAsValue:
return false;
}
throw new UnsupportedError("Unexpected $kind");
}
/// Returns `true` if this class has a `visitXReference` or
/// `defaultXReference` method.
///
/// This is only valid for subclass of [NamedNode] or [Constant].
bool get hasVisitReferenceMethod {
switch (kind) {
case AstClassKind.root:
case AstClassKind.inner:
case AstClassKind.public:
case AstClassKind.named:
case AstClassKind.declarative:
return !_classesWithoutVisitReference.contains(name);
case AstClassKind.implementation:
case AstClassKind.interface:
case AstClassKind.utilityAsStructure:
case AstClassKind.utilityAsValue:
return false;
}
throw new UnsupportedError("Unexpected $kind");
}
String dump([String indent = ""]) {
StringBuffer sb = new StringBuffer();
sb.writeln('${indent}${node.name} ($kind)');
for (AstField field in fields) {
sb.write(field.dump('${indent} '));
}
for (AstClass subclass in subclasses) {
sb.write(subclass.dump('${indent} '));
}
return sb.toString();
}
String toString() => '${runtimeType}(${name})';
}
/// Categorization of field types.
enum AstFieldKind {
/// An atomic non-node value.
///
/// For instance an [int] value.
value,
/// A [Node] value that is part of the tree.
///
/// For instance an [Expression] value.
node,
/// A [Reference] value.
reference,
/// A reference to a declarative [Node].
///
/// For instance the reference to [VariableDeclaration] in [VariableGet].
use,
/// A list of values.
list,
/// A set of values.
set,
/// A map of values.
map,
/// A non-node composite value.
///
/// For instance a [ConstantMapEntry] that must be treat as a tuple of
/// a [Constant] key and a [Constant] value.
utility,
}
/// Structural description of a field type.
class FieldType {
final DartType type;
final AstFieldKind kind;
FieldType(this.type, this.kind);
String toString() => 'FieldType($type,$kind)';
}
class ListFieldType extends FieldType {
final FieldType elementType;
ListFieldType(DartType type, this.elementType)
: super(type, AstFieldKind.list);
String toString() => 'ListFieldType($type,$elementType)';
}
class SetFieldType extends FieldType {
final FieldType elementType;
SetFieldType(DartType type, this.elementType) : super(type, AstFieldKind.set);
String toString() => 'SetFieldType($type,$elementType)';
}
class MapFieldType extends FieldType {
final FieldType keyType;
final FieldType valueType;
MapFieldType(DartType type, this.keyType, this.valueType)
: super(type, AstFieldKind.map);
String toString() => 'MapFieldType($type,$keyType,$valueType)';
}
class UtilityFieldType extends FieldType {
final AstClass astClass;
UtilityFieldType(DartType type, this.astClass)
: super(type, AstFieldKind.utility);
String toString() => 'UtilityFieldType($type,$astClass)';
}
class AstField {
final Field node;
final String name;
final FieldType type;
AstField(this.node, this.name, this.type);
String dump([String indent = ""]) {
StringBuffer sb = new StringBuffer();
sb.writeln('$indent$name $type');
return sb.toString();
}
String toString() => '${runtimeType}(${name})';
}
class AstModel {
final AstClass nodeClass;
final AstClass namedNodeClass;
final AstClass constantClass;
AstModel(this.nodeClass, this.namedNodeClass, this.constantClass);
/// Returns an [Iterable] for all declarative [Node] classes in the AST model.
Iterable<AstClass> get declarativeClasses {
return classes.where((cls) => cls.kind == AstClassKind.declarative);
}
/// Returns an [Iterable] for all [Node] (sub)classes in the AST model.
Iterable<AstClass> get classes sync* {
Iterable<AstClass> visitClass(AstClass cls) sync* {
yield cls;
for (AstClass subclass in cls.subclasses) {
yield* visitClass(subclass);
}
}
yield* visitClass(nodeClass);
}
}
/// Computes the [AstModel] from 'package:kernel/ast' using [repoDir] to locate
/// the package config file.
///
/// If [printDump] is `true`, a dump of the model printed to stdout.
Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async {
CompilerOptions options = new CompilerOptions();
options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
options.packagesFileUri = computePackageConfig(repoDir);
options.onDiagnostic = (DiagnosticMessage message) {
printDiagnosticMessage(message, print);
};
InternalCompilerResult compilerResult = await kernelForProgramInternal(
astLibraryUri, options,
retainDataForTesting: true, requireMain: false);
ClassHierarchy classHierarchy = compilerResult.classHierarchy;
CoreTypes coreTypes = compilerResult.coreTypes;
TypeEnvironment typeEnvironment =
new TypeEnvironment(coreTypes, classHierarchy);
Library astLibrary = compilerResult.component.libraries
.singleWhere((library) => library.importUri == astLibraryUri);
bool errorsFound = false;
void reportError(String message) {
print(message);
errorsFound = true;
}
Map<String, String> declarativeClassesNames = {..._declarativeClassesNames};
Set<String> classesWithoutVisitMethods = _classesWithoutVisitMethods.toSet();
Set<String> classesWithoutVisitReference =
_classesWithoutVisitReference.toSet();
Map<String, Map<String, FieldRule>> fieldRuleMap = {..._fieldRuleMap};
Map<String, FieldRule> nullFieldRules = {...?fieldRuleMap.remove(null)};
for (Class cls in astLibrary.classes) {
declarativeClassesNames.remove(cls.name);
classesWithoutVisitMethods.remove(cls.name);
classesWithoutVisitReference.remove(cls.name);
Map<String, FieldRule> fieldRules = {...?fieldRuleMap.remove(cls.name)};
Set<String> renames = {};
Class parent = cls;
while (parent != null && parent.enclosingLibrary == astLibrary) {
for (Field field in parent.fields) {
bool hasFieldRule = fieldRules.containsKey(field.name.text);
FieldRule fieldRule = fieldRules.remove(field.name.text);
if (fieldRule != null) {
if (fieldRule.name != null) {
renames.add(fieldRule.name);
}
}
if (!hasFieldRule) {
if (!cls.isEnum && !field.isStatic && field.name.isPrivate) {
reportError(
'Private field `${field.name.text}` in ${parent.name} must '
'have a field rule.');
}
}
if (nullFieldRules.containsKey(field.name.text)) {
FieldRule nullFieldRule = nullFieldRules.remove(field.name.text);
if (nullFieldRule != null) {
reportError('Only `null` is allowed for class `null`.');
}
}
}
parent = parent.superclass;
}
for (Procedure procedure in cls.procedures) {
renames.remove(procedure.name.text);
}
if (renames.isNotEmpty) {
reportError('Unknown procedure(s) for field redirections in '
'${cls.name}: ${renames}');
}
if (fieldRules.isNotEmpty) {
reportError(
'Unknown field(s) for rules in ${cls.name}: ${fieldRules.keys}');
}
}
if (declarativeClassesNames.isNotEmpty) {
reportError('Unknown declarative classes: ${declarativeClassesNames}');
}
if (classesWithoutVisitMethods.isNotEmpty) {
reportError('Unknown classes without visit methods: '
'${classesWithoutVisitMethods}');
}
if (classesWithoutVisitReference.isNotEmpty) {
reportError('Unknown classes without visit reference methods: '
'${classesWithoutVisitReference}');
}
if (fieldRuleMap.isNotEmpty) {
reportError('Unknown classes with field rules: ${fieldRuleMap.keys}');
}
Class classNode = astLibrary.classes.singleWhere((cls) => cls.name == 'Node');
DartType nullableNodeType =
classNode.getThisType(coreTypes, Nullability.nullable);
Class classNamedNode =
astLibrary.classes.singleWhere((cls) => cls.name == 'NamedNode');
Class classConstant =
astLibrary.classes.singleWhere((cls) => cls.name == 'Constant');
Library canonicalNameLibrary = compilerResult.component.libraries
.singleWhere((library) => library.importUri == canonicalNameLibraryUri);
Class referenceClass = canonicalNameLibrary.classes
.singleWhere((cls) => cls.name == 'Reference');
DartType nullableReferenceType =
referenceClass.getThisType(coreTypes, Nullability.nullable);
Set<Class> declarativeClasses = {};
Set<DartType> declarativeTypes = {};
for (String name in _declarativeClassesNames.keys) {
Class cls = astLibrary.classes.singleWhere((cls) => cls.name == name);
declarativeClasses.add(cls);
declarativeTypes.add(cls.getThisType(coreTypes, Nullability.nullable));
}
Map<Class, AstClass> classMap = {};
/// Computes the [AstClass] corresponding to [node] if [node] is declared in
/// 'package:kernel/ast.dart'.
AstClass computeAstClass(Class node) {
if (node == null) return null;
if (node.enclosingLibrary != astLibrary) return null;
AstClass astClass = classMap[node];
if (astClass == null) {
if (node == classNode) {
astClass = new AstClass(node, kind: AstClassKind.root);
} else if (classHierarchy.isSubtypeOf(node, classNode)) {
AstClass superclass = computeAstClass(node.superclass);
AstClassKind kind;
String declarativeName;
if (!node.isAbstract &&
classHierarchy.isSubtypeOf(node, classNamedNode)) {
kind = AstClassKind.named;
} else if (declarativeClasses.contains(node)) {
kind = AstClassKind.declarative;
declarativeName = _declarativeClassesNames[node.name];
}
astClass = new AstClass(node,
superclass: superclass,
kind: kind,
declarativeName: declarativeName);
for (Supertype supertype in node.implementedTypes) {
AstClass astSupertype = computeAstClass(supertype.classNode);
if (astSupertype != null) {
astClass.interfaces.add(astSupertype);
astSupertype.subtypes.add(astClass);
}
}
} else if (node.isEnum || _utilityClassesAsValues.contains(node.name)) {
astClass = new AstClass(node, kind: AstClassKind.utilityAsValue);
} else {
AstClass superclass = computeAstClass(node.superclass);
astClass = new AstClass(node,
superclass: superclass, kind: AstClassKind.utilityAsStructure);
}
if (astClass != null) {
classMap[node] = astClass;
}
}
return astClass;
}
for (Class cls in astLibrary.classes) {
computeAstClass(cls);
}
for (AstClass astClass in classMap.values) {
void computeAstField(Field field, Substitution substitution) {
if (field.isStatic) {
return;
}
FieldRule rule = getFieldRule(astClass, field);
if (rule == null) {
return;
}
DartType type = substitution.substituteType(field.type);
FieldType computeFieldType(DartType type) {
bool isDeclarativeType = false;
for (InterfaceType declarativeType in declarativeTypes) {
if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(
type, declarativeType, SubtypeCheckMode.withNullabilities)) {
isDeclarativeType = true;
break;
}
}
if (isDeclarativeType) {
if (rule.isDeclaration == null) {
reportError(
"No field rule for '${field.name}' in ${astClass.name}.\n"
"The field type contains the declarative type "
"'${type.toText(defaultAstTextStrategy)}' "
"and a rule must therefore specify "
"whether this constitutes declarative or referential use.");
}
if (!rule.isDeclaration) {
return new FieldType(type, AstFieldKind.use);
}
}
if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, coreTypes.listNullableRawType,
SubtypeCheckMode.withNullabilities)) {
DartType elementType = typeEnvironment
.getTypeArgumentsAsInstanceOf(type, coreTypes.listClass)
.single;
return new ListFieldType(type, computeFieldType(elementType));
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, coreTypes.setNullableRawType,
SubtypeCheckMode.withNullabilities)) {
DartType elementType = typeEnvironment
.getTypeArgumentsAsInstanceOf(type, coreTypes.setClass)
.single;
return new SetFieldType(type, computeFieldType(elementType));
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, coreTypes.mapNullableRawType,
SubtypeCheckMode.withNullabilities)) {
List<DartType> typeArguments = typeEnvironment
.getTypeArgumentsAsInstanceOf(type, coreTypes.mapClass);
return new MapFieldType(type, computeFieldType(typeArguments[0]),
computeFieldType(typeArguments[1]));
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(
type, nullableNodeType, SubtypeCheckMode.withNullabilities)) {
return new FieldType(type, AstFieldKind.node);
} else if (type is InterfaceType &&
typeEnvironment.isSubtypeOf(type, nullableReferenceType,
SubtypeCheckMode.withNullabilities)) {
return new FieldType(type, AstFieldKind.reference);
} else {
if (type is InterfaceType) {
AstClass astClass = classMap[type.classNode];
if (astClass != null &&
astClass.kind == AstClassKind.utilityAsStructure) {
return new UtilityFieldType(type, astClass);
}
}
return new FieldType(type, AstFieldKind.value);
}
}
FieldType fieldType;
fieldType ??= computeFieldType(type);
astClass.fields
.add(new AstField(field, rule.name ?? field.name.text, fieldType));
}
AstClass parent = astClass;
Substitution substitution = Substitution.empty;
while (parent != null) {
for (Field field in parent.node.fields) {
computeAstField(field, substitution);
}
parent = parent.superclass;
if (parent != null) {
substitution = Substitution.fromSupertype(
classHierarchy.getClassAsInstanceOf(astClass.node, parent.node));
}
}
}
AstClass astClassNode = classMap[classNode];
AstClass astClassNamedNode = classMap[classNamedNode];
AstClass astClassConstant = classMap[classConstant];
if (printDump) {
print(classMap[classNode].dump());
for (AstClass astClass in classMap.values) {
if (astClass != astClassNode && astClass.superclass == null) {
print(astClass.dump());
}
}
}
if (errorsFound) {
throw 'Errors found';
}
return new AstModel(astClassNode, astClassNamedNode, astClassConstant);
}

View file

@ -0,0 +1,956 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
import 'dart:io';
import 'ast_model.dart';
import 'visitor_generator.dart';
Uri computeEquivalenceUri(Uri repoDir) {
return repoDir.resolve('pkg/kernel/lib/src/equivalence.dart');
}
main(List<String> args) async {
Uri output = args.isEmpty
? computeEquivalenceUri(Uri.base)
: new File(args[0]).absolute.uri;
String result = await generateAstEquivalence(Uri.base);
new File.fromUri(output).writeAsStringSync(result);
}
Future<String> generateAstEquivalence(Uri repoDir) async {
AstModel astModel = await deriveAstModel(repoDir);
return generateVisitor(astModel, new EquivalenceVisitorStrategy());
}
class EquivalenceVisitorStrategy extends Visitor1Strategy {
Map<AstClass, String> _classStrategyMembers = {};
Map<AstField, String> _fieldStrategyMembers = {};
EquivalenceVisitorStrategy();
@override
String get argumentType => 'Node';
@override
String get argumentName => 'other';
@override
String get returnType => 'bool';
@override
String get visitorName => 'EquivalenceVisitor';
String get strategyName => 'EquivalenceStrategy';
String get internalCheckValues => '_checkValues';
String get checkValues => 'checkValues';
String get matchValues => 'matchValues';
String get internalCheckNodes => '_checkNodes';
String get checkNodes => 'checkNodes';
String get shallowMatchNodes => 'shallowMatchNodes';
String get deepMatchNodes => 'deepMatchNodes';
String get internalCheckReferences => '_checkReferences';
String get checkReferences => 'checkReferences';
String get matchReferences => 'matchReferences';
String get deepMatchReferences => 'deeplyMatchReferences';
String get matchNamedNodes => 'matchNamedNodes';
String get assumeReferences => 'assumeReferences';
String get checkAssumedReferences => 'checkAssumedReferences';
String get checkDeclarations => 'checkDeclarations';
String get internalCheckDeclarations => '_checkDeclarations';
String get shallowMatchDeclarations => 'matchDeclarations';
String get deepMatchDeclarations => 'deepMatchDeclarations';
String get assumeDeclarations => 'assumeDeclarations';
String get checkAssumedDeclarations => 'checkAssumedDeclarations';
String get checkLists => 'checkLists';
String get matchLists => 'matchLists';
String get checkSets => 'checkSets';
String get matchSets => 'matchSets';
String get checkMaps => 'checkMaps';
String get matchMaps => 'matchMaps';
String get checkingState => '_checkingState';
String get resultOnInequivalence => 'resultOnInequivalence';
String get registerInequivalence => 'registerInequivalence';
String classCheckName(AstClass astClass) => 'check${astClass.name}';
String fieldCheckName(AstClass astClass, AstField field) =>
'check${astClass.name}_${field.name}';
@override
void handleDefaultVisit(AstClass astClass, StringBuffer sb) {
sb.writeln('''
return false;''');
}
/// Compute the expression code for shallow matching two values of type
/// [fieldType].
///
/// Shallow matching is used to pair value when checking sets and maps. The
/// checking doesn't traverse the AST deeply and inequivalences are not
/// registered.
///
/// [prefix] is used as the receiver of the invocation.
String computeMatchingHelper(FieldType fieldType, [String prefix = '']) {
String thisName = 'a';
String otherName = 'b';
switch (fieldType.kind) {
case AstFieldKind.value:
return '$prefix$matchValues';
case AstFieldKind.node:
return '$prefix$shallowMatchNodes';
case AstFieldKind.reference:
return '$prefix$matchReferences';
case AstFieldKind.use:
return '$prefix$shallowMatchDeclarations';
case AstFieldKind.list:
ListFieldType listFieldType = fieldType;
String elementEquivalence =
computeMatchingHelper(listFieldType.elementType);
return '($thisName, $otherName) => $prefix$matchLists('
'$thisName, $otherName, $elementEquivalence)';
case AstFieldKind.set:
SetFieldType setFieldType = fieldType;
String elementMatching =
computeMatchingHelper(setFieldType.elementType);
String elementEquivalence =
computeEquivalenceHelper(setFieldType.elementType);
return '($thisName, $otherName) => $prefix$checkSets('
'$thisName, $otherName, $elementMatching, $elementEquivalence)';
case AstFieldKind.map:
MapFieldType mapFieldType = fieldType;
String keyMatching = computeMatchingHelper(mapFieldType.keyType);
String keyEquivalence = computeEquivalenceHelper(mapFieldType.keyType);
String valueEquivalence =
computeEquivalenceHelper(mapFieldType.valueType);
return '($thisName, $otherName) => $prefix$checkMaps('
'$thisName, $otherName, $keyMatching, '
'$keyEquivalence, $valueEquivalence)';
case AstFieldKind.utility:
StringBuffer sb = new StringBuffer();
UtilityFieldType utilityFieldType = fieldType;
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,
$otherName);
}''');
return sb.toString();
}
throw ArgumentError("Unexpected field type ${fieldType}");
}
/// Computes the expression code for checking the equivalence of two fields
/// of type [fieldType].
///
/// Checking is used to check the AST for equivalence and inequivalences are
/// registered.
///
/// [prefix] is used as the receiver of the invocation.
String computeEquivalenceHelper(FieldType fieldType, [String prefix = '']) {
String thisName = 'a';
String otherName = 'b';
switch (fieldType.kind) {
case AstFieldKind.value:
return '$prefix$checkValues';
case AstFieldKind.node:
return '$prefix$checkNodes';
case AstFieldKind.reference:
return '$prefix$checkReferences';
case AstFieldKind.use:
return '$prefix$checkDeclarations';
case AstFieldKind.list:
ListFieldType listFieldType = fieldType;
String elementEquivalence =
computeEquivalenceHelper(listFieldType.elementType);
return '($thisName, $otherName) => $prefix$checkLists('
'$thisName, $otherName, $elementEquivalence)';
case AstFieldKind.set:
SetFieldType setFieldType = fieldType;
String elementMatching =
computeMatchingHelper(setFieldType.elementType);
String elementEquivalence =
computeEquivalenceHelper(setFieldType.elementType);
return '($thisName, $otherName) => $prefix$checkSets('
'$thisName, $otherName, $elementMatching, $elementEquivalence)';
case AstFieldKind.map:
MapFieldType mapFieldType = fieldType;
String keyMatching = computeMatchingHelper(mapFieldType.keyType);
String keyEquivalence = computeEquivalenceHelper(mapFieldType.keyType);
String valueEquivalence =
computeEquivalenceHelper(mapFieldType.valueType);
return '($thisName, $otherName) => $prefix$checkMaps('
'$thisName, $otherName, $keyMatching, '
'$keyEquivalence, $valueEquivalence)';
case AstFieldKind.utility:
StringBuffer sb = new StringBuffer();
UtilityFieldType utilityFieldType = fieldType;
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,
$otherName);
}''');
return sb.toString();
}
throw ArgumentError("Unexpected field type ${fieldType}");
}
/// Registers that a strategy method is needed for checking [astClass].
///
/// If the method has not already been generated, it is generated and stored
/// in [_classStrategyMembers].
void registerAstClassEquivalence(AstClass astClass) {
if (_classStrategyMembers.containsKey(astClass)) return;
String thisName = 'node';
String otherName = 'other';
StringBuffer classStrategy = new StringBuffer();
classStrategy.writeln('''
bool ${classCheckName(astClass)}(
$visitorName visitor,
${astClass.name}? $thisName,
Object? $otherName) {''');
classStrategy.writeln('''
if (identical($thisName, $otherName)) return true;
if ($thisName is! ${astClass.name}) return false;
if ($otherName is! ${astClass.name}) return false;''');
if (astClass.kind == AstClassKind.named) {
classStrategy.writeln('''
if (!visitor.$matchNamedNodes($thisName, $otherName)) {
return false;
}''');
} else if (astClass.kind == AstClassKind.declarative) {
classStrategy.writeln('''
if (!visitor.$checkDeclarations($thisName, $otherName, '')) {
return false;
}''');
}
if (astClass.kind != AstClassKind.utilityAsStructure) {
classStrategy.writeln('''
visitor.pushNodeState($thisName, $otherName);''');
}
classStrategy.writeln('''
bool result = true;''');
for (AstField field in astClass.fields) {
registerAstFieldEquivalence(astClass, field);
classStrategy.writeln('''
if (!${fieldCheckName(astClass, field)}(visitor, $thisName, $otherName)) {
result = visitor.$resultOnInequivalence;
}''');
}
if (astClass.kind != AstClassKind.utilityAsStructure) {
classStrategy.writeln('''
visitor.popState();''');
}
classStrategy.writeln('''
return result;
}''');
_classStrategyMembers[astClass] = classStrategy.toString();
}
/// Registers that a strategy method is needed for checking [field] in
/// [astClass].
///
/// If the method has not already been generated, it is generated and stored
/// in [_fieldStrategyMembers].
void registerAstFieldEquivalence(AstClass astClass, AstField field) {
if (_fieldStrategyMembers.containsKey(field)) return;
String thisName = 'node';
String otherName = 'other';
StringBuffer fieldStrategy = new StringBuffer();
fieldStrategy.writeln('''
bool ${fieldCheckName(astClass, field)}(
$visitorName visitor,
${astClass.name} $thisName,
${astClass.name} $otherName) {''');
switch (field.type.kind) {
case AstFieldKind.value:
fieldStrategy.writeln('''
return visitor.$checkValues(
$thisName.${field.name},
$otherName.${field.name},
'${field.name}');''');
break;
case AstFieldKind.node:
fieldStrategy.writeln('''
return visitor.$checkNodes(
$thisName.${field.name},
$otherName.${field.name},
'${field.name}');''');
break;
case AstFieldKind.reference:
fieldStrategy.writeln('''
return visitor.$checkReferences(
$thisName.${field.name},
$otherName.${field.name},
'${field.name}');''');
break;
case AstFieldKind.use:
fieldStrategy.writeln('''
return visitor.$checkDeclarations(
$thisName.${field.name},
$otherName.${field.name},
'${field.name}');''');
break;
case AstFieldKind.list:
ListFieldType listFieldType = field.type;
fieldStrategy.writeln('''
return visitor.$checkLists(
$thisName.${field.name},
$otherName.${field.name},
${computeEquivalenceHelper(listFieldType.elementType, 'visitor.')},
'${field.name}');''');
break;
case AstFieldKind.set:
SetFieldType setFieldType = field.type;
fieldStrategy.writeln('''
return visitor.$checkSets(
$thisName.${field.name},
$otherName.${field.name},
${computeMatchingHelper(setFieldType.elementType, 'visitor.')},
${computeEquivalenceHelper(setFieldType.elementType, 'visitor.')},
'${field.name}');''');
break;
case AstFieldKind.map:
MapFieldType mapFieldType = field.type;
fieldStrategy.writeln('''
return visitor.$checkMaps(
$thisName.${field.name},
$otherName.${field.name},
${computeMatchingHelper(mapFieldType.keyType, 'visitor.')},
${computeEquivalenceHelper(mapFieldType.keyType, 'visitor.')},
${computeEquivalenceHelper(mapFieldType.valueType, 'visitor.')},
'${field.name}');''');
break;
case AstFieldKind.utility:
UtilityFieldType utilityFieldType = field.type;
registerAstClassEquivalence(utilityFieldType.astClass);
fieldStrategy.writeln('''
'${field.name}';
return ${classCheckName(utilityFieldType.astClass)}(
visitor,
$thisName.${field.name},
$otherName.${field.name});''');
break;
}
fieldStrategy.writeln('''
}''');
_fieldStrategyMembers[field] = fieldStrategy.toString();
}
@override
void handleVisit(AstClass astClass, StringBuffer sb) {
registerAstClassEquivalence(astClass);
sb.writeln('''
return strategy.${classCheckName(astClass)}(
this, node, $argumentName);''');
}
@override
void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {
sb.writeln('''
return false;''');
}
@override
void handleVisitReference(AstClass astClass, StringBuffer sb) {
sb.writeln('''
return false;''');
}
void generateHeader(AstModel astModel, StringBuffer sb) {
sb.writeln('''
// Copyright (c) 2021, 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.
// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
//
// Run 'dart pkg/front_end/tool/generate_ast_equivalence.dart' to update.
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 sand boxed 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, 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 ||
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, as defined by their
/// corresponding canonical names. Inequivalence is _not_ registered.
bool $matchReferences(Reference? a, Reference? b) {
return identical(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'''
}
''');
}
}

View file

@ -0,0 +1,304 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
import 'package:dart_style/dart_style.dart' show DartFormatter;
import 'ast_model.dart';
/// Generates a visitor library into [sb] based on [astModel] and [strategy].
String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
StringBuffer sb = new StringBuffer();
strategy.generateHeader(astModel, sb);
void addVisitNode(AstClass astClass) {
switch (astClass.kind) {
case AstClassKind.root:
case AstClassKind.inner:
if (astClass.hasVisitMethod) {
strategy.generateDefaultVisit(astClass, sb);
}
for (AstClass subclass in astClass.subclasses) {
addVisitNode(subclass);
}
break;
case AstClassKind.public:
case AstClassKind.named:
case AstClassKind.declarative:
if (astClass.hasVisitMethod) {
strategy.generateVisit(astClass, sb);
}
break;
case AstClassKind.implementation:
case AstClassKind.interface:
case AstClassKind.utilityAsStructure:
case AstClassKind.utilityAsValue:
break;
}
}
void addVisitReference(AstClass astClass) {
switch (astClass.kind) {
case AstClassKind.root:
case AstClassKind.inner:
if (astClass.hasVisitReferenceMethod) {
strategy.generateDefaultVisitReference(astClass, sb);
}
for (AstClass subclass in astClass.subclasses) {
addVisitReference(subclass);
}
break;
case AstClassKind.public:
case AstClassKind.named:
case AstClassKind.declarative:
if (astClass.hasVisitReferenceMethod) {
strategy.generateVisitReference(astClass, sb);
}
break;
case AstClassKind.implementation:
case AstClassKind.interface:
case AstClassKind.utilityAsStructure:
case AstClassKind.utilityAsValue:
break;
}
}
addVisitNode(astModel.nodeClass);
addVisitReference(astModel.namedNodeClass);
addVisitReference(astModel.constantClass);
strategy.generateFooter(astModel, sb);
String result = sb.toString();
result = new DartFormatter().format(result);
return result;
}
/// Strategy for generating a visitor in its own library based on an [AstModel].
abstract class VisitorStrategy {
const VisitorStrategy();
/// Generates the header of the visitor library, including preamble, imports
/// and visitor class declaration start.
void generateHeader(AstModel astModel, StringBuffer sb);
/// Generates a `defaultX` visitor method for [astClass].
void generateDefaultVisit(AstClass astClass, StringBuffer sb);
/// Generates a `visitX` visitor method for [astClass].
void generateVisit(AstClass astClass, StringBuffer sb);
/// Generates a `defaultXReference` visitor method for [astClass].
void generateDefaultVisitReference(AstClass astClass, StringBuffer sb);
/// Generates a `visitXReference` visitor method for [astClass].
void generateVisitReference(AstClass astClass, StringBuffer sb);
/// Generates the footer of the visitor library, including the visitor class
/// declaration end.
void generateFooter(AstModel astModel, StringBuffer sb);
}
/// Base strategy for creating a [Visitor] implementation.
abstract class Visitor0Strategy extends VisitorStrategy {
const Visitor0Strategy();
/// The name of the generated visitor class.
String get visitorName;
/// The type parameters of the generated visitor class.
String get visitorTypeParameters => '';
/// The return type for the visitor methods.
///
/// The generated visitor will implement `Visitor<$returnType>`.
String get returnType;
void generateHeader(AstModel astModel, StringBuffer sb) {
sb.writeln('''
import 'package:kernel/ast.dart';
class $visitorName$visitorTypeParameters implements Visitor<$returnType> {''');
}
void generateFooter(AstModel astModel, StringBuffer sb) {
sb.writeln('''
}''');
}
@override
void generateDefaultVisit(AstClass astClass, StringBuffer sb) {
sb.writeln('''
@override
${returnType} default${astClass.name}(
${astClass.name} node) {''');
handleDefaultVisit(astClass, sb);
sb.writeln('}');
}
/// Generates the body of a `defaultX` visitor method of [astClass].
void handleDefaultVisit(AstClass astClass, StringBuffer sb) {}
@override
void generateVisit(AstClass astClass, StringBuffer sb) {
sb.writeln('''
@override
${returnType} visit${astClass.name}(
${astClass.name} node) {''');
handleVisit(astClass, sb);
sb.writeln('}');
}
/// Generates the body of a `visitX` visitor method of [astClass].
void handleVisit(AstClass astClass, StringBuffer sb) {}
@override
void generateDefaultVisitReference(AstClass astClass, StringBuffer sb) {
sb.writeln(''''
@override
${returnType} default${astClass.name}Reference(
'${astClass.name} node) {''');
handleDefaultVisitReference(astClass, sb);
sb.writeln('}');
}
/// Generates the body of a `defaultXReference` visitor method of [astClass].
void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {}
@override
void generateVisitReference(AstClass astClass, StringBuffer sb) {
sb.writeln('''
@override
${returnType} visit${astClass.name}Reference(
${astClass.name} node) {''');
handleVisitReference(astClass, sb);
sb.writeln('}');
}
/// Generates the body of a `visitXReference` visitor method of [astClass].
void handleVisitReference(AstClass astClass, StringBuffer sb) {}
}
/// Strategy for creating an empty `Visitor<void>` implementation.
class VoidVisitor0Strategy extends Visitor0Strategy {
const VoidVisitor0Strategy();
@override
String get visitorName => 'VoidVisitor';
@override
String get returnType => 'void';
}
/// Base strategy for creating a [Visitor1] implementation.
abstract class Visitor1Strategy extends VisitorStrategy {
const Visitor1Strategy();
/// The name of the generated visitor class.
String get visitorName;
/// The type parameters of the generated visitor class.
String get visitorTypeParameters => '';
/// The type of the argument of the visitor methods.
///
/// The generated visitor will implement
/// `Visitor1<$returnType, $argumentType>`.
String get argumentType;
/// The name of the argument parameter name.
String get argumentName => 'arg';
/// The return type for the visitor methods.
///
/// The generated visitor will implement
/// `Visitor1<$returnType, $argumentType>`.
String get returnType;
void generateHeader(AstModel astModel, StringBuffer sb) {
sb.writeln('''
import 'package:kernel/ast.dart';
class $visitorName$visitorTypeParameters
implements Visitor1<$returnType, $argumentType> {''');
}
void generateFooter(AstModel astModel, StringBuffer sb) {
sb.writeln('''
}''');
}
@override
void generateDefaultVisit(AstClass astClass, StringBuffer sb) {
sb.writeln('''
@override
${returnType} default${astClass.name}(
${astClass.name} node, $argumentType $argumentName) {''');
handleDefaultVisit(astClass, sb);
sb.writeln('''
}''');
}
/// Generates the body of a `defaultX` visitor method of [astClass].
void handleDefaultVisit(AstClass astClass, StringBuffer sb) {}
@override
void generateVisit(AstClass astClass, StringBuffer sb) {
sb.writeln('''
@override
${returnType} visit${astClass.name}(
${astClass.name} node, $argumentType $argumentName) {''');
handleVisit(astClass, sb);
sb.writeln('''
}''');
}
/// Generates the body of a `visitX` visitor method of [astClass].
void handleVisit(AstClass astClass, StringBuffer sb) {}
@override
void generateDefaultVisitReference(AstClass astClass, StringBuffer sb) {
sb.writeln('''
@override
${returnType} default${astClass.name}Reference(
${astClass.name} node, $argumentType $argumentName) {''');
handleDefaultVisitReference(astClass, sb);
sb.writeln('''
}''');
}
/// Generates the body of a `defaultXReference` visitor method of [astClass].
void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {}
@override
void generateVisitReference(AstClass astClass, StringBuffer sb) {
sb.writeln('''
@override
${returnType} visit${astClass.name}Reference(
${astClass.name} node, $argumentType $argumentName) {''');
handleVisitReference(astClass, sb);
sb.writeln('''
}''');
}
/// Generates the body of a `visitXReference` visitor method of [astClass].
void handleVisitReference(AstClass astClass, StringBuffer sb) {}
}
/// Strategy for creating an empty `Visitor1<void,Null>` implementation.
class VoidVisitor1Strategy extends Visitor1Strategy {
const VoidVisitor1Strategy();
@override
String get visitorName => 'VoidVisitor';
@override
String get returnType => 'void';
@override
String get argumentType => 'Null';
@override
String get argumentName => '_';
}

View file

@ -92,6 +92,7 @@ abstract class Node {
const Node();
R accept<R>(Visitor<R> v);
R accept1<R, A>(Visitor1<R, A> v, A arg);
void visitChildren(Visitor v);
/// Returns the textual representation of this node for use in debugging.
@ -1191,7 +1192,7 @@ class Class extends NamedNode implements Annotatable, FileUriNode {
}
List<RedirectingFactory> _redirectingFactoriesInternal;
DirtifyingList<RedirectingFactory>? _redirectingFactoryConstructorsView;
DirtifyingList<RedirectingFactory>? _redirectingFactoriesView;
/// Redirecting factory constructors declared in the class.
///
@ -1200,7 +1201,7 @@ class Class extends NamedNode implements Annotatable, FileUriNode {
ensureLoaded();
// If already dirty the caller just might as well add stuff directly too.
if (dirty) return _redirectingFactoriesInternal;
return _redirectingFactoryConstructorsView ??=
return _redirectingFactoriesView ??=
new DirtifyingList(this, _redirectingFactoriesInternal);
}
@ -1211,7 +1212,7 @@ class Class extends NamedNode implements Annotatable, FileUriNode {
void set redirectingFactoryConstructorsInternal(
List<RedirectingFactory> redirectingFactoryConstructors) {
_redirectingFactoriesInternal = redirectingFactoryConstructors;
_redirectingFactoryConstructorsView = null;
_redirectingFactoriesView = null;
}
Class(
@ -10200,6 +10201,9 @@ abstract class Name extends Node {
@override
R accept<R>(Visitor<R> v) => v.visitName(this);
@override
R accept1<R, A>(Visitor1<R, A> v, A arg) => v.visitName(this, arg);
@override
void visitChildren(Visitor v) {
// DESIGN TODO: Should we visit the library as a library reference?
@ -11303,6 +11307,9 @@ class NamedType extends Node implements Comparable<NamedType> {
@override
R accept<R>(Visitor<R> v) => v.visitNamedType(this);
@override
R accept1<R, A>(Visitor1<R, A> v, A arg) => v.visitNamedType(this, arg);
@override
void visitChildren(Visitor v) {
type.accept(v);
@ -12059,6 +12066,9 @@ class Supertype extends Node {
R accept<R>(Visitor<R> v) => v.visitSupertype(this);
@override
R accept1<R, A>(Visitor1<R, A> v, A arg) => v.visitSupertype(this, arg);
visitChildren(Visitor v) {
classNode.acceptReference(v);
visitList(typeArguments, v);
@ -12112,31 +12122,46 @@ abstract class Constant extends Node {
///
/// (Note that a constant can be seen as a DAG (directed acyclic graph) and
/// not a tree!)
visitChildren(Visitor v);
@override
void visitChildren(Visitor v);
/// Calls the `visit*Constant()` method on the visitor [v].
@override
R accept<R>(ConstantVisitor<R> v);
/// Calls the `visit*Constant()` method on the visitor [v].
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg);
/// Calls the `visit*ConstantReference()` method on the visitor [v].
R acceptReference<R>(Visitor<R> v);
/// Calls the `visit*ConstantReference()` method on the visitor [v].
R acceptReference1<R, A>(Visitor1<R, A> v, A arg);
/// The Kernel AST will reference [Constant]s via [ConstantExpression]s. The
/// constants are not required to be canonicalized, but they have to be deeply
/// comparable via hashCode/==!
@override
int get hashCode;
@override
bool operator ==(Object other);
@override
String toString() => throw '$runtimeType';
/// Returns a textual representation of the this constant.
///
/// If [verbose] is `true`, qualified names will include the library name/uri.
@override
String toText(AstTextStrategy strategy) {
AstPrinter printer = new AstPrinter(strategy);
printer.writeConstant(this);
return printer.getText();
}
@override
void toTextInternal(AstPrinter printer);
/// Gets the type of this constant.
@ -12152,8 +12177,10 @@ abstract class PrimitiveConstant<T> extends Constant {
PrimitiveConstant(this.value);
@override
int get hashCode => value.hashCode;
@override
bool operator ==(Object other) =>
other is PrimitiveConstant<T> && other.value == value;
@ -12166,10 +12193,24 @@ abstract class PrimitiveConstant<T> extends Constant {
class NullConstant extends PrimitiveConstant<Null> {
NullConstant() : super(null);
visitChildren(Visitor v) {}
@override
void visitChildren(Visitor v) {}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitNullConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitNullConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitNullConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitNullConstantReference(this, arg);
@override
DartType getType(StaticTypeContext context) => const NullType();
@override
@ -12179,10 +12220,24 @@ class NullConstant extends PrimitiveConstant<Null> {
class BoolConstant extends PrimitiveConstant<bool> {
BoolConstant(bool value) : super(value);
visitChildren(Visitor v) {}
@override
void visitChildren(Visitor v) {}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitBoolConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitBoolConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitBoolConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitBoolConstantReference(this, arg);
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.coreTypes.boolRawType(context.nonNullable);
@ -12194,10 +12249,24 @@ class BoolConstant extends PrimitiveConstant<bool> {
class IntConstant extends PrimitiveConstant<int> {
IntConstant(int value) : super(value);
visitChildren(Visitor v) {}
@override
void visitChildren(Visitor v) {}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitIntConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitIntConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitIntConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitIntConstantReference(this, arg);
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.coreTypes.intRawType(context.nonNullable);
@ -12209,14 +12278,31 @@ class IntConstant extends PrimitiveConstant<int> {
class DoubleConstant extends PrimitiveConstant<double> {
DoubleConstant(double value) : super(value);
visitChildren(Visitor v) {}
@override
void visitChildren(Visitor v) {}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitDoubleConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitDoubleConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitDoubleConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitDoubleConstantReference(this, arg);
@override
int get hashCode => value.isNaN ? 199 : super.hashCode;
@override
bool operator ==(Object other) =>
other is DoubleConstant && identical(value, other.value);
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.coreTypes.doubleRawType(context.nonNullable);
@ -12230,10 +12316,23 @@ class StringConstant extends PrimitiveConstant<String> {
assert(value != null);
}
visitChildren(Visitor v) {}
@override
void visitChildren(Visitor v) {}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitStringConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitStringConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitStringConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitStringConstantReference(this, arg);
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.coreTypes.stringRawType(context.nonNullable);
@ -12245,6 +12344,7 @@ class StringConstant extends PrimitiveConstant<String> {
printer.write('"');
}
@override
String toString() => 'StringConstant(${toStringInternal()})';
}
@ -12254,22 +12354,37 @@ class SymbolConstant extends Constant {
SymbolConstant(this.name, this.libraryReference);
visitChildren(Visitor v) {}
@override
void visitChildren(Visitor v) {}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitSymbolConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitSymbolConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitSymbolConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitSymbolConstantReference(this, arg);
@override
String toString() => 'SymbolConstant(${toStringInternal()})';
@override
int get hashCode => _Hash.hash2(name, libraryReference);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is SymbolConstant &&
other.name == name &&
other.libraryReference == libraryReference);
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.coreTypes.symbolRawType(context.nonNullable);
@ -12291,7 +12406,8 @@ class MapConstant extends Constant {
MapConstant(this.keyType, this.valueType, this.entries);
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
keyType.accept(v);
valueType.accept(v);
for (final ConstantMapEntry entry in entries) {
@ -12300,9 +12416,20 @@ class MapConstant extends Constant {
}
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitMapConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitMapConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitMapConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitMapConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.write('const <');
@ -12326,6 +12453,7 @@ class MapConstant extends Constant {
late final int hashCode = _Hash.combine2Finish(
keyType.hashCode, valueType.hashCode, _Hash.combineListHash(entries));
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is MapConstant &&
@ -12333,6 +12461,7 @@ class MapConstant extends Constant {
other.valueType == valueType &&
listEquals(other.entries, entries));
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.mapType(keyType, valueType, context.nonNullable);
}
@ -12373,16 +12502,28 @@ class ListConstant extends Constant {
ListConstant(this.typeArgument, this.entries);
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
typeArgument.accept(v);
for (final Constant constant in entries) {
constant.acceptReference(v);
}
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitListConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitListConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitListConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitListConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.write('const <');
@ -12404,12 +12545,14 @@ class ListConstant extends Constant {
late final int hashCode = _Hash.combineFinish(
typeArgument.hashCode, _Hash.combineListHash(entries));
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is ListConstant &&
other.typeArgument == typeArgument &&
listEquals(other.entries, entries));
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.listType(typeArgument, context.nonNullable);
}
@ -12420,16 +12563,28 @@ class SetConstant extends Constant {
SetConstant(this.typeArgument, this.entries);
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
typeArgument.accept(v);
for (final Constant constant in entries) {
constant.acceptReference(v);
}
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitSetConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitSetConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitSetConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitSetConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.write('const <');
@ -12451,12 +12606,14 @@ class SetConstant extends Constant {
late final int hashCode = _Hash.combineFinish(
typeArgument.hashCode, _Hash.combineListHash(entries));
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is SetConstant &&
other.typeArgument == typeArgument &&
listEquals(other.entries, entries));
@override
DartType getType(StaticTypeContext context) =>
context.typeEnvironment.setType(typeArgument, context.nonNullable);
}
@ -12482,8 +12639,18 @@ class InstanceConstant extends Constant {
}
R accept<R>(ConstantVisitor<R> v) => v.visitInstanceConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitInstanceConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) => v.visitInstanceConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitInstanceConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.write('const ');
@ -12508,6 +12675,7 @@ class InstanceConstant extends Constant {
late final int hashCode = _Hash.combine2Finish(classReference.hashCode,
listHashCode(typeArguments), _Hash.combineMapHashUnordered(fieldValues));
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other is InstanceConstant &&
@ -12516,6 +12684,7 @@ class InstanceConstant extends Constant {
mapEquals(other.fieldValues, fieldValues));
}
@override
DartType getType(StaticTypeContext context) =>
new InterfaceType(classNode, context.nonNullable, typeArguments);
}
@ -12526,15 +12695,27 @@ class InstantiationConstant extends Constant {
InstantiationConstant(this.tearOffConstant, this.types);
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
tearOffConstant.acceptReference(v);
visitList(types, v);
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitInstantiationConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitInstantiationConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) =>
v.visitInstantiationConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitInstantiationConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.writeConstant(tearOffConstant);
@ -12544,15 +12725,18 @@ class InstantiationConstant extends Constant {
@override
String toString() => 'InstantiationConstant(${toStringInternal()})';
@override
int get hashCode => _Hash.combineFinish(
tearOffConstant.hashCode, _Hash.combineListHash(types));
@override
bool operator ==(Object other) {
return other is InstantiationConstant &&
other.tearOffConstant == tearOffConstant &&
listEquals(other.types, types);
}
@override
DartType getType(StaticTypeContext context) {
final FunctionType type = tearOffConstant.getType(context) as FunctionType;
final Map<TypeParameter, DartType> mapping = <TypeParameter, DartType>{};
@ -12586,14 +12770,26 @@ class StaticTearOffConstant extends Constant implements TearOffConstant {
@override
FunctionNode get function => target.function;
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
target.acceptReference(v);
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitStaticTearOffConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitStaticTearOffConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) =>
v.visitStaticTearOffConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitStaticTearOffConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.writeMemberName(targetReference);
@ -12602,13 +12798,16 @@ class StaticTearOffConstant extends Constant implements TearOffConstant {
@override
String toString() => 'StaticTearOffConstant(${toStringInternal()})';
@override
int get hashCode => targetReference.hashCode;
@override
bool operator ==(Object other) {
return other is StaticTearOffConstant &&
other.targetReference == targetReference;
}
@override
FunctionType getType(StaticTypeContext context) {
return target.function.computeFunctionType(context.nonNullable);
}
@ -12639,10 +12838,18 @@ class ConstructorTearOffConstant extends Constant implements TearOffConstant {
@override
R accept<R>(ConstantVisitor<R> v) => v.visitConstructorTearOffConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitConstructorTearOffConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) =>
v.visitConstructorTearOffConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitConstructorTearOffConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.writeMemberName(targetReference);
@ -12689,10 +12896,18 @@ class RedirectingFactoryTearOffConstant extends Constant
R accept<R>(ConstantVisitor<R> v) =>
v.visitRedirectingFactoryTearOffConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitRedirectingFactoryTearOffConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) =>
v.visitRedirectingFactoryTearOffConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitRedirectingFactoryTearOffConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.writeMemberName(targetReference);
@ -12723,16 +12938,28 @@ class TypedefTearOffConstant extends Constant {
TypedefTearOffConstant(this.parameters, this.tearOffConstant, this.types);
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
visitList(parameters, v);
tearOffConstant.acceptReference(v);
visitList(types, v);
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitTypedefTearOffConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitTypedefTearOffConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) =>
v.visitTypedefTearOffConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitTypedefTearOffConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.writeTypeParameters(parameters);
@ -12802,14 +13029,26 @@ class TypeLiteralConstant extends Constant {
TypeLiteralConstant(this.type);
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
type.accept(v);
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitTypeLiteralConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitTypeLiteralConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) =>
v.visitTypeLiteralConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitTypeLiteralConstantReference(this, arg);
@override
void toTextInternal(AstPrinter printer) {
printer.writeType(type);
@ -12818,8 +13057,10 @@ class TypeLiteralConstant extends Constant {
@override
String toString() => 'TypeLiteralConstant(${toStringInternal()})';
@override
int get hashCode => type.hashCode;
@override
bool operator ==(Object other) {
return other is TypeLiteralConstant && other.type == type;
}
@ -12835,14 +13076,26 @@ class UnevaluatedConstant extends Constant {
expression.parent = null;
}
visitChildren(Visitor v) {
@override
void visitChildren(Visitor v) {
expression.accept(v);
}
@override
R accept<R>(ConstantVisitor<R> v) => v.visitUnevaluatedConstant(this);
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) =>
v.visitUnevaluatedConstant(this, arg);
@override
R acceptReference<R>(Visitor<R> v) =>
v.visitUnevaluatedConstantReference(this);
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) =>
v.visitUnevaluatedConstantReference(this, arg);
DartType getType(StaticTypeContext context) =>
expression.getStaticType(context);

View file

@ -137,34 +137,34 @@ class CanonicalName {
}
CanonicalName getChildFromField(Field field) {
return getChild('@getters').getChildFromQualifiedName(field.name);
return getChild(gettersName).getChildFromQualifiedName(field.name);
}
CanonicalName getChildFromFieldSetter(Field field) {
return getChild('@setters').getChildFromQualifiedName(field.name);
return getChild(settersName).getChildFromQualifiedName(field.name);
}
CanonicalName getChildFromConstructor(Constructor constructor) {
return getChild('@constructors')
return getChild(constructorsName)
.getChildFromQualifiedName(constructor.name);
}
CanonicalName getChildFromRedirectingFactory(
RedirectingFactory redirectingFactoryConstructor) {
return getChild('@factories')
.getChildFromQualifiedName(redirectingFactoryConstructor.name);
RedirectingFactory redirectingFactory) {
return getChild(factoriesName)
.getChildFromQualifiedName(redirectingFactory.name);
}
CanonicalName getChildFromFieldWithName(Name name) {
return getChild('@getters').getChildFromQualifiedName(name);
return getChild(gettersName).getChildFromQualifiedName(name);
}
CanonicalName getChildFromFieldSetterWithName(Name name) {
return getChild('@setters').getChildFromQualifiedName(name);
return getChild(settersName).getChildFromQualifiedName(name);
}
CanonicalName getChildFromTypedef(Typedef typedef_) {
return getChild('@typedefs').getChild(typedef_.name);
return getChild(typedefsName).getChild(typedef_.name);
}
/// Take ownership of a child canonical name and its subtree.
@ -279,14 +279,7 @@ class CanonicalName {
Iterable<CanonicalName>? parentChildren = parent.childrenOrNull;
if (parentChildren != null) {
for (CanonicalName child in parentChildren) {
if (child.name != '@methods' &&
child.name != '@typedefs' &&
child.name != '@fields' &&
child.name != '@=fields' &&
child.name != '@getters' &&
child.name != '@setters' &&
child.name != '@factories' &&
child.name != '@constructors') {
if (!isSymbolicName(child.name)) {
bool checkReferenceNode = true;
if (child._reference == null) {
// OK for "if private: URI of library" part of "Qualified name"...
@ -333,11 +326,46 @@ class CanonicalName {
return sb.toString();
}
/// Symbolic name used for the [CanonicalName] node that holds all
/// constructors within a class.
static const String constructorsName = '@constructors';
/// Symbolic name used for the [CanonicalName] node that holds all factories
/// within a class.
static const String factoriesName = '@factories';
/// Symbolic name used for the [CanonicalName] node that holds all methods
/// within a library or a class.
static const String methodsName = '@methods';
/// Symbolic name used for the [CanonicalName] node that holds all getters and
/// readable fields within a library or class.
static const String gettersName = '@getters';
/// Symbolic name used for the [CanonicalName] node that holds all setters and
/// writable fields within a library or class.
static const String settersName = '@setters';
/// Symbolic name used for the [CanonicalName] node that holds all typedefs
/// within a library.
static const String typedefsName = '@typedefs';
static const Set<String> symbolicNames = {
constructorsName,
factoriesName,
methodsName,
gettersName,
settersName,
typedefsName,
};
static bool isSymbolicName(String name) => symbolicNames.contains(name);
static String getProcedureQualifier(Procedure procedure) {
if (procedure.isGetter) return '@getters';
if (procedure.isSetter) return '@setters';
if (procedure.isFactory) return '@factories';
return '@methods';
if (procedure.isGetter) return gettersName;
if (procedure.isSetter) return settersName;
if (procedure.isFactory) return factoriesName;
return methodsName;
}
/// Returns `true` if [node] is orphaned through its [reference].

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,400 @@
// Copyright (c) 2021, 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.
part of 'equivalence.dart';
/// The node or property currently visited by the [EquivalenceVisitor].
abstract class State {
const State();
State? get parent;
}
/// State for visiting two AST nodes in [EquivalenceVisitor].
class NodeState extends State {
final State? parent;
final Node a;
final Node b;
NodeState(this.a, this.b, [this.parent]);
}
/// State for visiting an AST property in [EquivalenceVisitor]
class PropertyState extends State {
final State? parent;
final String name;
PropertyState(this.name, [this.parent]);
}
/// The state of the equivalence visitor.
///
/// This holds the currently found inequivalences and the current assumptions.
/// This also determines whether inequivalence are currently reported.
class CheckingState {
/// If `true`, inequivalences are currently reported.
final bool isAsserting;
CheckingState(
{this.isAsserting: true,
UnionFind<Reference>? assumedReferences,
State? currentState})
: _assumedReferences = assumedReferences ?? new UnionFind<Reference>(),
_currentState = currentState;
/// Create a new [CheckingState] that inherits the [_currentState] and a copy
/// of the current assumptions. If [isAsserting] is `true`, the new state
/// will register inequivalences.
CheckingState createSubState({bool isAsserting: false}) {
return new CheckingState(
isAsserting: isAsserting,
assumedReferences: _assumedReferences.clone(),
currentState: _currentState)
.._assumedDeclarationMap.addAll(_assumedDeclarationMap);
}
/// Returns a state corresponding to the state which does _not_ register
/// inequivalences. If this state is already not registering inequivalences,
/// `this` is returned.
CheckingState toMatchingState() {
if (!isAsserting) return this;
return createSubState(isAsserting: false);
}
/// Returns that value that should be used as the result value when
/// inequivalence are found.
///
/// See [EquivalenceVisitor.resultOnInequivalence] for details.
bool get resultOnInequivalence => isAsserting;
/// Map of [Reference]s that are assumed to be equivalent. The keys are
/// the [Reference]s on the left side of the equivalence relation.
UnionFind<Reference> _assumedReferences;
/// Returns `true` if [a] and [b] are currently assumed to be equivalent.
bool checkAssumedReferences(Reference? a, Reference? b) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
return _assumedReferences.valuesInSameSet(a, b);
}
/// Assume that [a] and [b] are equivalent, if possible.
///
/// Returns `true` if [a] and [b] could be assumed to be equivalent. This
/// is not the case if either [a] or [b] is `null`.
bool assumeReferences(Reference? a, Reference? b) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
_assumedReferences.unionOfValues(a, b);
return true;
}
/// Map of declarations that are assumed to be equivalent.
Map<dynamic, dynamic> _assumedDeclarationMap = {};
/// Returns `true` if [a] and [b] are currently assumed to be equivalent.
bool checkAssumedDeclarations(dynamic a, dynamic b) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
return _assumedDeclarationMap.containsKey(a) &&
_assumedDeclarationMap[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) {
if (identical(a, b)) return true;
if (a == null || b == null) return false;
if (_assumedDeclarationMap.containsKey(a)) {
return _assumedDeclarationMap[a] == b;
} else {
_assumedDeclarationMap[a] = b;
return true;
}
}
/// The currently visited node or property.
State? _currentState;
/// Enters a new property state of a property named [propertyName].
void pushPropertyState(String propertyName) {
_currentState = new PropertyState(propertyName, _currentState);
}
/// Enters a new node state of nodes [a] and [b].
void pushNodeState(Node a, Node b) {
_currentState = new NodeState(a, b, _currentState);
}
/// Leaves the current node or property.
void popState() {
_currentState = _currentState?.parent;
}
/// List of registered inequivalences.
List<Inequivalence> _inequivalences = [];
/// Registers the inequivalence [message] on [propertyName].
void registerInequivalence(String propertyName, String message) {
_inequivalences.add(new Inequivalence(
new PropertyState(propertyName, _currentState), message));
}
/// Returns `true` if inequivalences have been registered.
bool get hasInequivalences => _inequivalences.isNotEmpty;
/// Returns the [EquivalenceResult] for the registered inequivalences. If
/// [hasInequivalences] is `true`, the result is marked has having
/// inequivalences, even when none have been registered.
EquivalenceResult toResult({bool hasInequivalences: false}) =>
new EquivalenceResult(
hasInequivalences: hasInequivalences,
registeredInequivalences: _inequivalences.toList());
}
/// The result of performing equivalence checking.
class EquivalenceResult {
final bool hasInequivalences;
final List<Inequivalence> registeredInequivalences;
EquivalenceResult(
{this.hasInequivalences: false, required this.registeredInequivalences});
bool get isEquivalent =>
!hasInequivalences && registeredInequivalences.isEmpty;
@override
String toString() {
StringBuffer sb = new StringBuffer();
for (Inequivalence inequivalence in registeredInequivalences) {
sb.writeln(inequivalence);
}
return sb.toString();
}
}
/// A registered inequivalence holding the [state] at which is was found and
/// details about the inequivalence.
class Inequivalence {
final State state;
final String message;
Inequivalence(this.state, this.message);
@override
String toString() {
List<State> states = [];
State? state = this.state;
while (state != null) {
states.add(state);
state = state.parent;
}
StringBuffer sb = new StringBuffer();
sb.writeln(message);
String indent = ' ';
for (State state in states.reversed) {
if (state is NodeState) {
sb.writeln();
sb.write(indent);
indent = ' $indent';
if (state.a.runtimeType == state.b.runtimeType) {
if (state.a is NamedNode) {
sb.write(state.a.runtimeType);
sb.write('(');
sb.write(state.a.toText(defaultAstTextStrategy));
sb.write(')');
} else {
sb.write(state.a.runtimeType);
}
} else {
sb.write('(${state.a.runtimeType}/${state.b.runtimeType})');
}
} else if (state is PropertyState) {
sb.write('.${state.name}');
} else {
throw new UnsupportedError('Unexpected state ${state.runtimeType}');
}
}
return sb.toString();
}
}
/// Enum for different kinds of [ReferenceName]s.
enum ReferenceNameKind {
/// A reference name without information.
Unknown,
/// A reference name of a library.
Library,
/// A reference name of a class or extension.
Declaration,
/// A reference name of a typedef or member.
Member,
}
/// Abstract representation of a [Reference] or [CanonicalName].
///
/// This is used to determine nominality of [Reference]s consistently,
/// regardless of whether the [Reference] has an attached node or canonical
/// name.
class ReferenceName {
final ReferenceNameKind kind;
final ReferenceName? parent;
final String? name;
final String? uri;
ReferenceName.internal(this.kind, this.name, [this.parent, this.uri]);
factory ReferenceName.fromNamedNode(NamedNode node) {
if (node is Library) {
return new ReferenceName.internal(
ReferenceNameKind.Library, node.importUri.toString());
} else if (node is Extension) {
return new ReferenceName.internal(ReferenceNameKind.Declaration,
node.name, new ReferenceName.fromNamedNode(node.enclosingLibrary));
} else if (node is Class) {
return new ReferenceName.internal(ReferenceNameKind.Declaration,
node.name, new ReferenceName.fromNamedNode(node.enclosingLibrary));
} else if (node is Typedef) {
return new ReferenceName.internal(ReferenceNameKind.Member, node.name,
new ReferenceName.fromNamedNode(node.enclosingLibrary));
} else if (node is Member) {
Class? enclosingClass = node.enclosingClass;
Reference? libraryReference = node.name.libraryName;
String? uri;
if (libraryReference != null) {
Library? library = libraryReference.node as Library?;
if (library != null) {
uri = library.importUri.toString();
} else {
uri = libraryReference.canonicalName?.name;
}
}
if (enclosingClass != null) {
return new ReferenceName.internal(
ReferenceNameKind.Member,
node.name.text,
new ReferenceName.fromNamedNode(enclosingClass),
uri);
} else {
return new ReferenceName.internal(
ReferenceNameKind.Member,
node.name.text,
new ReferenceName.fromNamedNode(node.enclosingLibrary),
uri);
}
} else {
throw new ArgumentError(
'Unexpected named node ${node} (${node.runtimeType})');
}
}
factory ReferenceName.fromCanonicalName(CanonicalName canonicalName) {
List<CanonicalName> parents = [];
CanonicalName? parent = canonicalName;
while (parent != null) {
parents.add(parent);
parent = parent.parent;
}
parents = parents.reversed.toList();
ReferenceName? referenceName;
ReferenceNameKind kind = ReferenceNameKind.Declaration;
for (int index = 1; index < parents.length; index++) {
if (index == 1) {
// Library reference.
referenceName = new ReferenceName.internal(
ReferenceNameKind.Library, parents[index].name);
} else if (CanonicalName.isSymbolicName(parents[index].name)) {
// Skip symbolic names
kind = ReferenceNameKind.Member;
} else {
if (index + 2 == parents.length) {
// This is a private name.
referenceName = new ReferenceName.internal(ReferenceNameKind.Member,
parents[index + 1].name, referenceName, parents[index].name);
break;
} else {
referenceName = new ReferenceName.internal(
kind, parents[index].name, referenceName);
}
}
}
return referenceName ??
new ReferenceName.internal(ReferenceNameKind.Unknown, null);
}
String? get libraryUri {
if (kind == ReferenceNameKind.Library) {
return name;
} else {
return parent?.libraryUri;
}
}
String? get declarationName {
if (kind == ReferenceNameKind.Declaration) {
return name;
} else {
return parent?.declarationName;
}
}
String? get memberName {
if (kind == ReferenceNameKind.Member) {
return name;
}
return null;
}
String? get memberUri {
if (kind == ReferenceNameKind.Member) {
return uri;
}
return null;
}
static ReferenceName? fromReference(Reference? reference) {
if (reference == null) {
return null;
}
NamedNode? node = reference.node;
if (node != null) {
return new ReferenceName.fromNamedNode(node);
}
CanonicalName? canonicalName = reference.canonicalName;
if (canonicalName != null) {
return new ReferenceName.fromCanonicalName(canonicalName);
}
return new ReferenceName.internal(ReferenceNameKind.Unknown, null);
}
@override
int get hashCode =>
name.hashCode * 13 + uri.hashCode * 17 + parent.hashCode * 19;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ReferenceName &&
name == other.name &&
uri == other.uri &&
parent == other.parent;
}
@override
String toString() {
if (parent != null) {
return '${parent}/$name';
} else if (name != null) {
return '/$name';
} else {
return '<null>';
}
}
}

View file

@ -0,0 +1,76 @@
// Copyright (c) 2021, 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:collection';
class UnionFindNode<T> {
final T value;
UnionFindNode<T>? parent;
UnionFindNode(this.value);
}
class UnionFind<T> {
final bool _useIdentity;
final Map<T, UnionFindNode<T>> _nodeMap;
UnionFind({bool useIdentity: false})
: _nodeMap = useIdentity ? new LinkedHashMap.identity() : {},
_useIdentity = useIdentity;
UnionFind<T> clone() {
UnionFind<T> newUnionFind = new UnionFind<T>(useIdentity: _useIdentity);
Map<UnionFindNode<T>, UnionFindNode<T>> oldToNewMap = {};
UnionFindNode<T> getNewNode(UnionFindNode<T> oldNode) {
UnionFindNode<T> newNode = newUnionFind[oldNode.value];
return oldToNewMap[oldNode] = newNode;
}
for (UnionFindNode<T> oldNode in _nodeMap.values) {
UnionFindNode<T> newNode = getNewNode(oldNode);
if (oldNode.parent != null) {
newNode.parent = getNewNode(oldNode.parent!);
}
}
return newUnionFind;
}
UnionFindNode<T> operator [](T value) =>
_nodeMap[value] ??= new UnionFindNode<T>(value);
Iterable<UnionFindNode<T>> get nodes => _nodeMap.values;
Iterable<T> get values => nodes.map((n) => n.value);
UnionFindNode<T> findNode(UnionFindNode<T> node) {
if (node.parent != null) {
return node.parent = findNode(node.parent!);
}
return node;
}
void unionOfValues(T a, T b) {
unionOfNodes(this[a], this[b]);
}
UnionFindNode<T> unionOfNodes(UnionFindNode<T> a, UnionFindNode<T> b) {
UnionFindNode<T> rootA = findNode(a);
UnionFindNode<T> rootB = findNode(b);
if (rootA != rootB) {
return rootB.parent = rootA;
}
return rootA;
}
bool valuesInSameSet(T a, T b) {
UnionFindNode<T>? node1 = _nodeMap[a];
UnionFindNode<T>? node2 = _nodeMap[b];
return node1 != null && node2 != null && nodesInSameSet(node1, node2);
}
bool nodesInSameSet(UnionFindNode<T> a, UnionFindNode<T> b) {
return findNode(a) == findNode(b);
}
}

View file

@ -590,6 +590,42 @@ abstract class ConstantVisitor<R> {
R visitUnevaluatedConstant(UnevaluatedConstant node) => defaultConstant(node);
}
abstract class ConstantVisitor1<R, A> {
const ConstantVisitor1();
R defaultConstant(Constant node, A arg);
R visitNullConstant(NullConstant node, A arg) => defaultConstant(node, arg);
R visitBoolConstant(BoolConstant node, A arg) => defaultConstant(node, arg);
R visitIntConstant(IntConstant node, A arg) => defaultConstant(node, arg);
R visitDoubleConstant(DoubleConstant node, A arg) =>
defaultConstant(node, arg);
R visitStringConstant(StringConstant node, A arg) =>
defaultConstant(node, arg);
R visitSymbolConstant(SymbolConstant node, A arg) =>
defaultConstant(node, arg);
R visitMapConstant(MapConstant node, A arg) => defaultConstant(node, arg);
R visitListConstant(ListConstant node, A arg) => defaultConstant(node, arg);
R visitSetConstant(SetConstant node, A arg) => defaultConstant(node, arg);
R visitInstanceConstant(InstanceConstant node, A arg) =>
defaultConstant(node, arg);
R visitInstantiationConstant(InstantiationConstant node, A arg) =>
defaultConstant(node, arg);
R visitStaticTearOffConstant(StaticTearOffConstant node, A arg) =>
defaultConstant(node, arg);
R visitTypedefTearOffConstant(TypedefTearOffConstant node, A arg) =>
defaultConstant(node, arg);
R visitConstructorTearOffConstant(ConstructorTearOffConstant node, A arg) =>
defaultConstant(node, arg);
R visitRedirectingFactoryTearOffConstant(
RedirectingFactoryTearOffConstant node, A arg) =>
defaultConstant(node, arg);
R visitTypeLiteralConstant(TypeLiteralConstant node, A arg) =>
defaultConstant(node, arg);
R visitUnevaluatedConstant(UnevaluatedConstant node, A arg) =>
defaultConstant(node, arg);
}
abstract class _ConstantCallback<R> {
R defaultConstant(Constant node);
@ -803,6 +839,21 @@ abstract class MemberReferenceVisitor<R> {
}
}
abstract class MemberReferenceVisitor1<R, A> {
const MemberReferenceVisitor1();
R defaultMemberReference(Member node, A arg);
R visitFieldReference(Field node, A arg) => defaultMemberReference(node, arg);
R visitConstructorReference(Constructor node, A arg) =>
defaultMemberReference(node, arg);
R visitProcedureReference(Procedure node, A arg) =>
defaultMemberReference(node, arg);
R visitRedirectingFactoryReference(RedirectingFactory node, A arg) {
return defaultMemberReference(node, arg);
}
}
abstract class Visitor<R> extends TreeVisitor<R>
implements
DartTypeVisitor<R>,
@ -915,6 +966,118 @@ abstract class Visitor<R> extends TreeVisitor<R>
R visitNamedType(NamedType node) => defaultNode(node);
}
abstract class Visitor1<R, A> extends TreeVisitor1<R, A>
implements
DartTypeVisitor1<R, A>,
ConstantVisitor1<R, A>,
MemberReferenceVisitor1<R, A> {
const Visitor1();
/// The catch-all case, except for references.
R defaultNode(Node node, A arg);
R defaultTreeNode(TreeNode node, A arg) => defaultNode(node, arg);
// DartTypes
R defaultDartType(DartType node, A arg) => defaultNode(node, arg);
R visitInvalidType(InvalidType node, A arg) => defaultDartType(node, arg);
R visitDynamicType(DynamicType node, A arg) => defaultDartType(node, arg);
R visitVoidType(VoidType node, A arg) => defaultDartType(node, arg);
R visitInterfaceType(InterfaceType node, A arg) => defaultDartType(node, arg);
R visitFutureOrType(FutureOrType node, A arg) => defaultDartType(node, arg);
R visitFunctionType(FunctionType node, A arg) => defaultDartType(node, arg);
R visitTypeParameterType(TypeParameterType node, A arg) =>
defaultDartType(node, arg);
R visitTypedefType(TypedefType node, A arg) => defaultDartType(node, arg);
R visitNeverType(NeverType node, A arg) => defaultDartType(node, arg);
R visitNullType(NullType node, A arg) => defaultDartType(node, arg);
R visitExtensionType(ExtensionType node, A arg) => defaultDartType(node, arg);
// Constants
R defaultConstant(Constant node, A arg) => defaultNode(node, arg);
R visitNullConstant(NullConstant node, A arg) => defaultConstant(node, arg);
R visitBoolConstant(BoolConstant node, A arg) => defaultConstant(node, arg);
R visitIntConstant(IntConstant node, A arg) => defaultConstant(node, arg);
R visitDoubleConstant(DoubleConstant node, A arg) =>
defaultConstant(node, arg);
R visitStringConstant(StringConstant node, A arg) =>
defaultConstant(node, arg);
R visitSymbolConstant(SymbolConstant node, A arg) =>
defaultConstant(node, arg);
R visitMapConstant(MapConstant node, A arg) => defaultConstant(node, arg);
R visitListConstant(ListConstant node, A arg) => defaultConstant(node, arg);
R visitSetConstant(SetConstant node, A arg) => defaultConstant(node, arg);
R visitInstanceConstant(InstanceConstant node, A arg) =>
defaultConstant(node, arg);
R visitInstantiationConstant(InstantiationConstant node, A arg) =>
defaultConstant(node, arg);
R visitTypeLiteralConstant(TypeLiteralConstant node, A arg) =>
defaultConstant(node, arg);
R visitUnevaluatedConstant(UnevaluatedConstant node, A arg) =>
defaultConstant(node, arg);
// Class references
R visitClassReference(Class node, A arg);
R visitTypedefReference(Typedef node, A arg);
R visitExtensionReference(Extension node, A arg);
// Constant references
R defaultConstantReference(Constant node, A arg);
R visitNullConstantReference(NullConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitBoolConstantReference(BoolConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitIntConstantReference(IntConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitDoubleConstantReference(DoubleConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitStringConstantReference(StringConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitSymbolConstantReference(SymbolConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitMapConstantReference(MapConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitListConstantReference(ListConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitSetConstantReference(SetConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitInstanceConstantReference(InstanceConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitInstantiationConstantReference(InstantiationConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitConstructorTearOffConstantReference(
ConstructorTearOffConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitRedirectingFactoryTearOffConstantReference(
RedirectingFactoryTearOffConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitStaticTearOffConstantReference(StaticTearOffConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitTypedefTearOffConstantReference(TypedefTearOffConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitTypeLiteralConstantReference(TypeLiteralConstant node, A arg) =>
defaultConstantReference(node, arg);
R visitUnevaluatedConstantReference(UnevaluatedConstant node, A arg) =>
defaultConstantReference(node, arg);
// Member references
R defaultMemberReference(Member node, A arg);
R visitFieldReference(Field node, A arg) => defaultMemberReference(node, arg);
R visitConstructorReference(Constructor node, A arg) =>
defaultMemberReference(node, arg);
R visitProcedureReference(Procedure node, A arg) =>
defaultMemberReference(node, arg);
R visitRedirectingFactoryReference(RedirectingFactory node, A arg) =>
defaultMemberReference(node, arg);
R visitName(Name node, A arg) => defaultNode(node, arg);
R visitSupertype(Supertype node, A arg) => defaultNode(node, arg);
R visitNamedType(NamedType node, A arg) => defaultNode(node, arg);
}
/// Visitor mixin that throws as its base case.
mixin VisitorThrowingMixin<R> implements Visitor<R> {
@override

View file

@ -40,13 +40,15 @@ main() {
// Canonical names are now set: Verify that the field is marked as such,
// canonical-name-wise.
String getterCanonicalName = '${field.getterReference.canonicalName}';
if (field.getterReference.canonicalName!.parent!.name != "@getters") {
throw "Expected @getters parent, but had "
if (field.getterReference.canonicalName!.parent!.name !=
CanonicalName.gettersName) {
throw "Expected ${CanonicalName.gettersName} parent, but had "
"${field.getterReference.canonicalName!.parent!.name}";
}
String setterCanonicalName = '${field.setterReference!.canonicalName}';
if (field.setterReference!.canonicalName!.parent!.name != "@setters") {
throw "Expected @setters parent, but had "
if (field.setterReference!.canonicalName!.parent!.name !=
CanonicalName.settersName) {
throw "Expected ${CanonicalName.settersName} parent, but had "
"${field.setterReference!.canonicalName!.parent!.name}";
}

View file

@ -0,0 +1,204 @@
// Copyright (c) 2021, 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';
final Component component1 = createComponent();
final Component component2 = createComponent();
List<Test> tests = [
Test(IntLiteral(0), IntLiteral(0)),
Test(IntLiteral(42), IntLiteral(42)),
Test(IntLiteral(0), IntLiteral(42), inequivalence: '''
Values 0 and 42 are not equivalent
.root
IntLiteral.value
'''),
Test(StringLiteral('0'), StringLiteral('0')),
Test(StringLiteral('42'), StringLiteral('42')),
Test(StringLiteral('0'), StringLiteral('42'), inequivalence: '''
Values 0 and 42 are not equivalent
.root
StringLiteral.value
'''),
Test(IntLiteral(0), IntLiteral(42), strategy: const IgnoreIntLiteralValue()),
Test(StringLiteral('0'), StringLiteral('42'),
strategy: const IgnoreIntLiteralValue(), inequivalence: '''
Values 0 and 42 are not equivalent
.root
StringLiteral.value
'''),
Test(IntLiteral(0), StringLiteral('0'), inequivalence: '''
Inequivalent nodes
1: IntLiteral(0)
2: StringLiteral("0")
.root
'''),
Test(Not(Not(Not(BoolLiteral(true)))), Not(Not(Not(BoolLiteral(true))))),
Test(Not(Not(BoolLiteral(true))), Not(Not(Not(BoolLiteral(true)))),
inequivalence: '''
Inequivalent nodes
1: BoolLiteral(true)
2: Not(!true)
.root
Not.operand
Not.operand
'''),
Test(Not(Not(Not(BoolLiteral(true)))), Not(Not(BoolLiteral(true))),
inequivalence: '''
Inequivalent nodes
1: Not(!true)
2: BoolLiteral(true)
.root
Not.operand
Not.operand
'''),
Test(Not(Not(Not(BoolLiteral(true)))), Not(Not(Not(BoolLiteral(false)))),
inequivalence: '''
Values true and false are not equivalent
.root
Not.operand
Not.operand
Not.operand
BoolLiteral.value
'''),
Test(component1, component2),
Test(component1.libraries[0], component2.libraries[0]),
Test(component1.libraries[0], component2.libraries[0]),
Test(component1.libraries[0], component2.libraries[1], inequivalence: '''
Inequivalent nodes
1: library file://uri1/
2: library file://uri2/
.root
'''),
Test(component1.libraries[1], component2.libraries[2], inequivalence: '''
Inequivalent nodes
1: library file://uri2/
2: library file://uri3/
.root
'''),
Test(component1.libraries[1], component2.libraries[3], inequivalence: '''
Values file://uri2/ and file://uri3/ are not equivalent
.root
Library(library file://uri2/).fileUri
'''),
Test(component1.libraries[0].procedures[0],
component2.libraries[0].procedures[1],
inequivalence: '''
Inequivalent nodes
1: foo
2: bar
.root
'''),
// TODO(johnniwinther): Improve message for inequivalent references with the
// same simple name.
Test(component1.libraries[0].procedures[0],
component2.libraries[2].procedures[0],
inequivalence: '''
Inequivalent nodes
1: foo
2: foo
.root
'''),
Test(StaticTearOff.byReference(Reference()),
StaticTearOff.byReference(Reference())),
Test(
StaticTearOff.byReference(
component1.libraries[0].procedures[0].reference),
StaticTearOff.byReference(
component2.libraries[0].procedures[0].reference)),
// TODO(johnniwinther): Improve message for inequivalent references with the
// same simple name.
Test(
StaticTearOff.byReference(
component1.libraries[0].procedures[0].reference),
StaticTearOff.byReference(
component2.libraries[2].procedures[0].reference),
inequivalence: '''
Inequivalent references:
1: Reference to foo
2: Reference to foo
.root
StaticTearOff.targetReference
'''),
];
main() {
for (Test test in tests) {
EquivalenceResult result =
checkEquivalence(test.a, test.b, strategy: test.strategy);
if (test.isEquivalent) {
Expect.equals(result.isEquivalent, test.isEquivalent,
'Unexpected result for\n${test.a}\n${test.b}:\n$result');
} 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---');
}
}
}
class Test {
final Node a;
final Node b;
final String? inequivalence;
final EquivalenceStrategy strategy;
Test(this.a, this.b,
{this.inequivalence, this.strategy = const EquivalenceStrategy()});
bool get isEquivalent => inequivalence == null;
}
class IgnoreIntLiteralValue extends EquivalenceStrategy {
const IgnoreIntLiteralValue();
@override
bool checkIntLiteral_value(
EquivalenceVisitor visitor, IntLiteral a, IntLiteral b) =>
true;
}
Component createComponent() {
Component component = new Component();
Uri uri1 = Uri.parse('file://uri1');
Uri uri2 = Uri.parse('file://uri2');
Uri uri3 = Uri.parse('file://uri3');
Library library1 = new Library(uri1, fileUri: uri1);
component.libraries.add(library1);
Procedure procedure1foo = new Procedure(
new Name('foo'), ProcedureKind.Method, new FunctionNode(null),
fileUri: uri1);
library1.addProcedure(procedure1foo);
Procedure procedure1bar = new Procedure(
new Name('bar'), ProcedureKind.Method, new FunctionNode(null),
fileUri: uri1);
library1.addProcedure(procedure1bar);
Library library2 = new Library(uri2, fileUri: uri2);
component.libraries.add(library2);
Library library3 = new Library(uri3, fileUri: uri2);
component.libraries.add(library3);
Procedure procedure3foo = new Procedure(
new Name('foo'), ProcedureKind.Method, new FunctionNode(null),
fileUri: uri1);
library3.addProcedure(procedure3foo);
Library library4 = new Library(uri2, fileUri: uri3);
component.libraries.add(library4);
return component;
}

View file

@ -0,0 +1,112 @@
// Copyright (c) 2020, 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.
// @dart = 2.9
import 'dart:io';
import 'package:kernel/binary/ast_from_binary.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/src/equivalence.dart';
main(List<String> args) {
String resolvedExecutable = Platform.environment['resolvedExecutable'];
File exe =
new File(resolvedExecutable ?? Platform.resolvedExecutable).absolute;
int steps = 0;
Directory parent = exe.parent.parent;
while (true) {
Set<String> foundDirs = {};
for (FileSystemEntity entry in parent.listSync(recursive: false)) {
if (entry is Directory) {
List<String> pathSegments = entry.uri.pathSegments;
String name = pathSegments[pathSegments.length - 2];
foundDirs.add(name);
}
}
if (foundDirs.contains("pkg") &&
foundDirs.contains("tools") &&
foundDirs.contains("tests")) {
break;
}
steps++;
if (parent.uri == parent.parent.uri) {
throw "Reached end without finding the root.";
}
parent = parent.parent;
}
// We had to go $steps steps to reach the "root" --- now we should go 2 steps
// shorter to be in the "compiled dir".
parent = exe.parent;
for (int i = steps - 2; i >= 0; i--) {
parent = parent.parent;
}
List<File> dills = [];
for (FileSystemEntity entry in parent.listSync(recursive: false)) {
if (entry is File) {
if (entry.path.toLowerCase().endsWith(".dill")) {
dills.add(entry);
}
}
}
Directory sdk = new Directory.fromUri(parent.uri.resolve("dart-sdk/"));
for (FileSystemEntity entry in sdk.listSync(recursive: true)) {
if (entry is File) {
if (entry.path.toLowerCase().endsWith(".dill")) {
dills.add(entry);
}
}
}
print("Found ${dills.length} dills!");
List<File> errors = [];
for (File dill in dills) {
if (args.isNotEmpty &&
!args.any((arg) => dill.absolute.path.endsWith(arg))) {
print('Skipping $dill');
continue;
}
if (!canRead(dill)) {
errors.add(dill);
}
}
if (errors.isEmpty) {
print("Read all OK.");
} else {
print("Errors when reading:");
for (File error in errors) {
print(error);
}
exitCode = 1;
}
}
bool canRead(File dill) {
print("Reading $dill");
List<int> bytes = dill.readAsBytesSync();
try {
Component component1 = new Component();
new BinaryBuilder(bytes).readComponent(component1);
Component component2 = new Component();
new BinaryBuilder(bytes).readComponent(component2);
EquivalenceResult result = checkEquivalence(component1, component2);
if (!result.isEquivalent) {
print(result);
}
return result.isEquivalent;
} catch (e, st) {
print("Error for $dill:");
print(e);
print(st);
print("");
print("--------------------");
print("");
return false;
}
}

View file

@ -0,0 +1,108 @@
// Copyright (c) 2021, 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:kernel/src/union_find.dart';
testSame<T>(UnionFind<T> unionFind, T a, T b, bool expected) {
expect(expected, unionFind.nodesInSameSet(unionFind[a], unionFind[b]));
}
testSets<T>(UnionFind<T> unionFind, Set<Set<T>> sets) {
for (Set<T> set in sets) {
UnionFindNode<T> root = unionFind.findNode(unionFind[set.first]);
for (T value in unionFind.values) {
testSame(unionFind, value, root.value, set.contains(value));
}
}
}
testFind<T>(UnionFind<T> unionFind, T value, T expected) {
expect(expected, unionFind.findNode(unionFind[value]).value);
}
testUnion<T>(UnionFind<T> unionFind, T a, T b, T expected) {
expect(expected, unionFind.unionOfNodes(unionFind[a], unionFind[b]).value);
}
main() {
UnionFind<int> unionFind = new UnionFind();
// {0}
testFind(unionFind, 0, 0);
testSame(unionFind, 0, 0, true);
testSets(unionFind, {
{0}
});
// {0}, {1}
testFind(unionFind, 1, 1);
testSame(unionFind, 0, 1, false);
testSame(unionFind, 1, 0, false);
testSame(unionFind, 1, 1, true);
testSets(unionFind, {
{0},
{1}
});
// {0}, {1}, {2}
testFind(unionFind, 2, 2);
testSame(unionFind, 0, 2, false);
testSame(unionFind, 1, 2, false);
testSame(unionFind, 2, 2, true);
testSets(unionFind, {
{0},
{1},
{2}
});
// {0}, {1}, {2}
testUnion(unionFind, 0, 0, 0);
testSame(unionFind, 0, 0, true);
testSame(unionFind, 0, 1, false);
testSame(unionFind, 0, 2, false);
testSets(unionFind, {
{0},
{1},
{2}
});
// {0, 1}, {2}
testUnion(unionFind, 0, 1, 0);
testSame(unionFind, 0, 0, true);
testSame(unionFind, 0, 1, true);
testSame(unionFind, 1, 0, true);
testSame(unionFind, 0, 2, false);
testFind(unionFind, 0, 0);
testFind(unionFind, 1, 0);
testSets(unionFind, {
{0, 1},
{2}
});
// {0, 1}, {2, 3}
testUnion(unionFind, 2, 3, 2);
testSame(unionFind, 0, 0, true);
testSame(unionFind, 0, 1, true);
testSame(unionFind, 0, 2, false);
testSame(unionFind, 0, 3, false);
testSame(unionFind, 0, 0, true);
testSame(unionFind, 0, 1, true);
testSame(unionFind, 0, 2, false);
testSame(unionFind, 0, 3, false);
testFind(unionFind, 2, 2);
testFind(unionFind, 3, 2);
testSets(unionFind, {
{0, 1},
{2, 3}
});
// {0, 1, 2, 3}
testUnion(unionFind, 1, 2, 0);
testSets(unionFind, {
{0, 1, 2, 3}
});
}
expect(expected, actual) {
if (expected != actual) throw 'Expected $expected, actual $actual';
}