mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 01:45:06 +00:00
[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:
parent
1e0a1d9732
commit
8038ecab65
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
771
pkg/front_end/tool/ast_model.dart
Normal file
771
pkg/front_end/tool/ast_model.dart
Normal 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);
|
||||
}
|
956
pkg/front_end/tool/generate_ast_equivalence.dart
Normal file
956
pkg/front_end/tool/generate_ast_equivalence.dart
Normal 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'''
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
304
pkg/front_end/tool/visitor_generator.dart
Normal file
304
pkg/front_end/tool/visitor_generator.dart
Normal 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 => '_';
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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].
|
||||
|
|
7220
pkg/kernel/lib/src/equivalence.dart
Normal file
7220
pkg/kernel/lib/src/equivalence.dart
Normal file
File diff suppressed because it is too large
Load diff
400
pkg/kernel/lib/src/equivalence_helpers.dart
Normal file
400
pkg/kernel/lib/src/equivalence_helpers.dart
Normal 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>';
|
||||
}
|
||||
}
|
||||
}
|
76
pkg/kernel/lib/src/union_find.dart
Normal file
76
pkg/kernel/lib/src/union_find.dart
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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}";
|
||||
}
|
||||
|
||||
|
|
204
pkg/kernel/test/equivalence_test.dart
Normal file
204
pkg/kernel/test/equivalence_test.dart
Normal 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;
|
||||
}
|
112
pkg/kernel/test/equivalent_dill_test.dart
Normal file
112
pkg/kernel/test/equivalent_dill_test.dart
Normal 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;
|
||||
}
|
||||
}
|
108
pkg/kernel/test/union_find_test.dart
Normal file
108
pkg/kernel/test/union_find_test.dart
Normal 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';
|
||||
}
|
Loading…
Reference in a new issue