mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 22:59:47 +00:00
[cfe] Add NodeCreator and clone test
Change-Id: I3b0f9c716a25eed05e83dbd653f50a478242a7a5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210240 Reviewed-by: Dmitry Stefantsov <dmitryas@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
parent
02599e58ae
commit
c13746df2b
|
@ -13,7 +13,9 @@ 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/ast_model.dart';
|
||||
import '../tool/generate_ast_equivalence.dart' as generateAstEquivalence;
|
||||
import '../tool/generate_ast_coverage.dart' as generateAstCoverage;
|
||||
import 'utils/io_utils.dart' show computeRepoDirUri;
|
||||
|
||||
final Uri repoDir = computeRepoDirUri();
|
||||
|
@ -24,7 +26,9 @@ main() async {
|
|||
directParserAstHelper();
|
||||
parserTestListener();
|
||||
parserTestParser();
|
||||
await astEquivalence();
|
||||
AstModel astModel = await deriveAstModel(repoDir);
|
||||
await astEquivalence(astModel);
|
||||
await astCoverage(astModel);
|
||||
}
|
||||
|
||||
void parserTestParser() {
|
||||
|
@ -50,14 +54,22 @@ void directParserAstHelper() {
|
|||
"dart pkg/front_end/tool/_fasta/direct_parser_ast_helper_creator.dart");
|
||||
}
|
||||
|
||||
Future<void> astEquivalence() async {
|
||||
Future<void> astEquivalence(AstModel astModel) async {
|
||||
Uri generatedFile = generateAstEquivalence.computeEquivalenceUri(repoDir);
|
||||
String generated =
|
||||
await generateAstEquivalence.generateAstEquivalence(repoDir);
|
||||
await generateAstEquivalence.generateAstEquivalence(repoDir, astModel);
|
||||
check(generated, generatedFile,
|
||||
"dart pkg/front_end/tool/generate_ast_equivalence.dart");
|
||||
}
|
||||
|
||||
Future<void> astCoverage(AstModel astModel) async {
|
||||
Uri generatedFile = generateAstCoverage.computeCoverageUri(repoDir);
|
||||
String generated =
|
||||
await generateAstCoverage.generateAstCoverage(repoDir, astModel);
|
||||
check(generated, generatedFile,
|
||||
"dart pkg/front_end/tool/generate_ast_coverage.dart");
|
||||
}
|
||||
|
||||
void experimentalFlags() {
|
||||
{
|
||||
Uri generatedFile =
|
||||
|
|
|
@ -262,6 +262,7 @@ covariances
|
|||
coverage
|
||||
cr
|
||||
creator
|
||||
creators
|
||||
criterion
|
||||
cross
|
||||
cruft
|
||||
|
@ -457,6 +458,7 @@ finv
|
|||
firsts
|
||||
fishy
|
||||
fishythefish
|
||||
fits
|
||||
fixnum
|
||||
fleshed
|
||||
float32
|
||||
|
|
|
@ -548,6 +548,7 @@ instrument
|
|||
insufficient
|
||||
intdiv
|
||||
interactive
|
||||
interchangeable
|
||||
interested
|
||||
internet
|
||||
interpolate
|
||||
|
|
|
@ -55,6 +55,20 @@ const Set<String> _classesWithoutVisitMethods = const {
|
|||
'PrimitiveConstant',
|
||||
};
|
||||
|
||||
/// Names of inner [Node] classes that are used as interfaces for (generally)
|
||||
/// interchangeable classes.
|
||||
///
|
||||
/// For instance, when [Expression] is used as the field type, any subtype of
|
||||
/// [Expression] can be used to populate the field.
|
||||
const Set<String> _interchangeableClasses = const {
|
||||
'Member',
|
||||
'Statement',
|
||||
'Expression',
|
||||
'Constant',
|
||||
'DartType',
|
||||
'Initializer',
|
||||
};
|
||||
|
||||
/// Names of subclasses of [NamedNode] that do _not_ have `visitXReference` or
|
||||
/// `defaultXReference` methods.
|
||||
const Set<String> _classesWithoutVisitReference = const {
|
||||
|
@ -96,8 +110,7 @@ const Map<String /*?*/, Map<String, FieldRule /*?*/ >> _fieldRuleMap = {
|
|||
'_proceduresView': null,
|
||||
'_proceduresInternal': FieldRule(name: 'procedures'),
|
||||
'_redirectingFactoriesView': null,
|
||||
'_redirectingFactoriesInternal':
|
||||
FieldRule(name: 'redirectingFactories'),
|
||||
'_redirectingFactoriesInternal': FieldRule(name: 'redirectingFactories'),
|
||||
'lazyBuilder': null,
|
||||
'dirty': null,
|
||||
},
|
||||
|
@ -264,6 +277,7 @@ class AstClass {
|
|||
final Class node;
|
||||
AstClassKind _kind;
|
||||
final String declarativeName;
|
||||
final bool isInterchangeable;
|
||||
|
||||
AstClass superclass;
|
||||
List<AstClass> interfaces = [];
|
||||
|
@ -273,8 +287,12 @@ class AstClass {
|
|||
List<AstField> fields = [];
|
||||
|
||||
AstClass(this.node,
|
||||
{this.superclass, AstClassKind kind, this.declarativeName})
|
||||
: _kind = kind {
|
||||
{this.superclass,
|
||||
AstClassKind kind,
|
||||
this.declarativeName,
|
||||
this.isInterchangeable})
|
||||
: _kind = kind,
|
||||
assert(isInterchangeable != null) {
|
||||
if (superclass != null) {
|
||||
superclass.subclasses.add(this);
|
||||
}
|
||||
|
@ -520,10 +538,13 @@ Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async {
|
|||
_classesWithoutVisitReference.toSet();
|
||||
Map<String, Map<String, FieldRule>> fieldRuleMap = {..._fieldRuleMap};
|
||||
Map<String, FieldRule> nullFieldRules = {...?fieldRuleMap.remove(null)};
|
||||
Set<String> interchangeableClasses = _interchangeableClasses.toSet();
|
||||
for (Class cls in astLibrary.classes) {
|
||||
declarativeClassesNames.remove(cls.name);
|
||||
classesWithoutVisitMethods.remove(cls.name);
|
||||
classesWithoutVisitReference.remove(cls.name);
|
||||
interchangeableClasses.remove(cls.name);
|
||||
|
||||
Map<String, FieldRule> fieldRules = {...?fieldRuleMap.remove(cls.name)};
|
||||
Set<String> renames = {};
|
||||
Class parent = cls;
|
||||
|
@ -575,6 +596,10 @@ Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async {
|
|||
reportError('Unknown classes without visit reference methods: '
|
||||
'${classesWithoutVisitReference}');
|
||||
}
|
||||
if (interchangeableClasses.isNotEmpty) {
|
||||
reportError('Unknown interchangeable classes: '
|
||||
'${interchangeableClasses}');
|
||||
}
|
||||
if (fieldRuleMap.isNotEmpty) {
|
||||
reportError('Unknown classes with field rules: ${fieldRuleMap.keys}');
|
||||
}
|
||||
|
@ -615,8 +640,10 @@ Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async {
|
|||
|
||||
AstClass astClass = classMap[node];
|
||||
if (astClass == null) {
|
||||
bool isInterchangeable = _interchangeableClasses.contains(node.name);
|
||||
if (node == classNode) {
|
||||
astClass = new AstClass(node, kind: AstClassKind.root);
|
||||
astClass = new AstClass(node,
|
||||
kind: AstClassKind.root, isInterchangeable: isInterchangeable);
|
||||
} else if (classHierarchy.isSubtypeOf(node, classNode)) {
|
||||
AstClass superclass = computeAstClass(node.superclass);
|
||||
AstClassKind kind;
|
||||
|
@ -631,7 +658,8 @@ Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async {
|
|||
astClass = new AstClass(node,
|
||||
superclass: superclass,
|
||||
kind: kind,
|
||||
declarativeName: declarativeName);
|
||||
declarativeName: declarativeName,
|
||||
isInterchangeable: isInterchangeable);
|
||||
for (Supertype supertype in node.implementedTypes) {
|
||||
AstClass astSupertype = computeAstClass(supertype.classNode);
|
||||
if (astSupertype != null) {
|
||||
|
@ -640,11 +668,15 @@ Future<AstModel> deriveAstModel(Uri repoDir, {bool printDump: false}) async {
|
|||
}
|
||||
}
|
||||
} else if (node.isEnum || _utilityClassesAsValues.contains(node.name)) {
|
||||
astClass = new AstClass(node, kind: AstClassKind.utilityAsValue);
|
||||
astClass = new AstClass(node,
|
||||
kind: AstClassKind.utilityAsValue,
|
||||
isInterchangeable: isInterchangeable);
|
||||
} else {
|
||||
AstClass superclass = computeAstClass(node.superclass);
|
||||
astClass = new AstClass(node,
|
||||
superclass: superclass, kind: AstClassKind.utilityAsStructure);
|
||||
superclass: superclass,
|
||||
kind: AstClassKind.utilityAsStructure,
|
||||
isInterchangeable: isInterchangeable);
|
||||
}
|
||||
if (astClass != null) {
|
||||
classMap[node] = astClass;
|
||||
|
|
121
pkg/front_end/tool/generate_ast_coverage.dart
Normal file
121
pkg/front_end/tool/generate_ast_coverage.dart
Normal file
|
@ -0,0 +1,121 @@
|
|||
// 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 computeCoverageUri(Uri repoDir) {
|
||||
return repoDir.resolve('pkg/kernel/lib/src/coverage.dart');
|
||||
}
|
||||
|
||||
main(List<String> args) async {
|
||||
Uri output = args.isEmpty
|
||||
? computeCoverageUri(Uri.base)
|
||||
: new File(args[0]).absolute.uri;
|
||||
String result = await generateAstCoverage(Uri.base);
|
||||
new File.fromUri(output).writeAsStringSync(result);
|
||||
}
|
||||
|
||||
Future<String> generateAstCoverage(Uri repoDir, [AstModel astModel]) async {
|
||||
astModel ??= await deriveAstModel(repoDir);
|
||||
return generateVisitor(astModel, new CoverageVisitorStrategy());
|
||||
}
|
||||
|
||||
class CoverageVisitorStrategy extends Visitor0Strategy {
|
||||
Map<String, Set<String>> nestedClassNames = {};
|
||||
|
||||
@override
|
||||
String get returnType => 'void';
|
||||
|
||||
@override
|
||||
String get visitorName => 'CoverageVisitor';
|
||||
|
||||
@override
|
||||
String get visitorComment => '''
|
||||
/// Recursive visitor that collects kinds for all visited nodes.
|
||||
///
|
||||
/// This can be used to verify that tests have the intended coverage.''';
|
||||
|
||||
@override
|
||||
void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
AstClass superAstClass = astClass.superclass;
|
||||
while (superAstClass != null && !superAstClass.isInterchangeable) {
|
||||
superAstClass = superAstClass.superclass;
|
||||
}
|
||||
String innerName = superAstClass?.name ?? 'Node';
|
||||
(nestedClassNames[innerName] ??= {}).add(astClass.name);
|
||||
sb.writeln('''
|
||||
visited.add(${innerName}Kind.${astClass.name});
|
||||
node.visitChildren(this);''');
|
||||
}
|
||||
|
||||
@override
|
||||
void handleVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
AstClass superAstClass = astClass.superclass;
|
||||
while (superAstClass != null && !superAstClass.isInterchangeable) {
|
||||
superAstClass = superAstClass.superclass;
|
||||
}
|
||||
if (superAstClass == astModel.constantClass) {
|
||||
// Constants are only visited as references.
|
||||
String innerName = superAstClass.name;
|
||||
(nestedClassNames[innerName] ??= {}).add(astClass.name);
|
||||
sb.writeln('''
|
||||
visited.add(${innerName}Kind.${astClass.name});
|
||||
node.visitChildren(this);''');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void generateHeader(AstModel astModel, StringBuffer sb) {
|
||||
super.generateHeader(astModel, sb);
|
||||
sb.writeln('''
|
||||
Set<Object> visited = {};''');
|
||||
}
|
||||
|
||||
@override
|
||||
void generateFooter(AstModel astModel, StringBuffer sb) {
|
||||
super.generateFooter(astModel, sb);
|
||||
nestedClassNames.forEach((String innerName, Set<String> classNames) {
|
||||
sb.writeln('''
|
||||
|
||||
enum ${innerName}Kind {''');
|
||||
for (String className in classNames.toList()..sort()) {
|
||||
sb.writeln('''
|
||||
$className,''');
|
||||
}
|
||||
sb.writeln('''
|
||||
}''');
|
||||
});
|
||||
sb.writeln('''
|
||||
|
||||
/// Returns the set of node kinds that were not visited by [visitor].
|
||||
Set<Object> missingNodes($visitorName visitor) {
|
||||
Set<Object> all = {''');
|
||||
nestedClassNames.forEach((String innerName, Set<String> classNames) {
|
||||
sb.writeln('''
|
||||
...${innerName}Kind.values,''');
|
||||
});
|
||||
sb.writeln('''
|
||||
};
|
||||
all.removeAll(visitor.visited);
|
||||
return all;
|
||||
}''');
|
||||
nestedClassNames.forEach((String innerName, Set<String> classNames) {
|
||||
if (innerName == 'Node') return;
|
||||
sb.writeln('''
|
||||
/// Returns the set of [${innerName}Kind]s that were not visited by [visitor].
|
||||
Set<${innerName}Kind> missing${innerName}s($visitorName visitor) {
|
||||
Set<${innerName}Kind> all =
|
||||
new Set<${innerName}Kind>.from(${innerName}Kind.values);
|
||||
all.removeAll(visitor.visited);
|
||||
return all;
|
||||
}''');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -21,8 +21,8 @@ main(List<String> args) async {
|
|||
new File.fromUri(output).writeAsStringSync(result);
|
||||
}
|
||||
|
||||
Future<String> generateAstEquivalence(Uri repoDir) async {
|
||||
AstModel astModel = await deriveAstModel(repoDir);
|
||||
Future<String> generateAstEquivalence(Uri repoDir, [AstModel astModel]) async {
|
||||
astModel ??= await deriveAstModel(repoDir);
|
||||
return generateVisitor(astModel, new EquivalenceVisitorStrategy());
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,8 @@ class EquivalenceVisitorStrategy extends Visitor1Strategy {
|
|||
'check${astClass.name}_${field.name}';
|
||||
|
||||
@override
|
||||
void handleDefaultVisit(AstClass astClass, StringBuffer sb) {
|
||||
void handleDefaultVisit(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
return false;''');
|
||||
}
|
||||
|
@ -387,7 +388,7 @@ class EquivalenceVisitorStrategy extends Visitor1Strategy {
|
|||
}
|
||||
|
||||
@override
|
||||
void handleVisit(AstClass astClass, StringBuffer sb) {
|
||||
void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
registerAstClassEquivalence(astClass);
|
||||
sb.writeln('''
|
||||
return strategy.${classCheckName(astClass)}(
|
||||
|
@ -395,13 +396,15 @@ class EquivalenceVisitorStrategy extends Visitor1Strategy {
|
|||
}
|
||||
|
||||
@override
|
||||
void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {
|
||||
void handleDefaultVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
return false;''');
|
||||
}
|
||||
|
||||
@override
|
||||
void handleVisitReference(AstClass astClass, StringBuffer sb) {
|
||||
void handleVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
return false;''');
|
||||
}
|
||||
|
@ -504,12 +507,14 @@ class $visitorName$visitorTypeParameters
|
|||
return result;
|
||||
}
|
||||
|
||||
/// Returns `true` if [a] and [b] are equivalent, as defined by their
|
||||
/// corresponding canonical names. Inequivalence is _not_ registered.
|
||||
/// Returns `true` if [a] and [b] are equivalent, either by existing
|
||||
/// assumption or as defined by their corresponding canonical names.
|
||||
/// Inequivalence is _not_ registered.
|
||||
bool $matchNamedNodes(NamedNode? a, NamedNode? b) {
|
||||
return identical(a, b) ||
|
||||
a == null ||
|
||||
b == null ||
|
||||
checkAssumedReferences(a.reference, b.reference) ||
|
||||
new ReferenceName.fromNamedNode(a) ==
|
||||
new ReferenceName.fromNamedNode(b);
|
||||
}
|
||||
|
@ -527,10 +532,12 @@ class $visitorName$visitorTypeParameters
|
|||
return $checkingState.$assumeReferences(a, b);
|
||||
}
|
||||
|
||||
/// Returns `true` if [a] and [b] are equivalent, as defined by their
|
||||
/// corresponding canonical names. Inequivalence is _not_ registered.
|
||||
/// Returns `true` if [a] and [b] are equivalent, either by existing
|
||||
/// assumption or as defined by their corresponding canonical names.
|
||||
/// Inequivalence is _not_ registered.
|
||||
bool $matchReferences(Reference? a, Reference? b) {
|
||||
return identical(a, b) ||
|
||||
checkAssumedReferences(a, b) ||
|
||||
ReferenceName.fromReference(a) ==
|
||||
ReferenceName.fromReference(b);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,12 @@ 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) {
|
||||
///
|
||||
/// If [format] is `false`, the generated output will _not_ be formatted using
|
||||
/// the Dart formatter. Use this during development to support incomplete
|
||||
/// generation.
|
||||
String generateVisitor(AstModel astModel, VisitorStrategy strategy,
|
||||
{bool format: true}) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
strategy.generateHeader(astModel, sb);
|
||||
|
||||
|
@ -18,7 +23,7 @@ String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
|
|||
case AstClassKind.root:
|
||||
case AstClassKind.inner:
|
||||
if (astClass.hasVisitMethod) {
|
||||
strategy.generateDefaultVisit(astClass, sb);
|
||||
strategy.generateDefaultVisit(astModel, astClass, sb);
|
||||
}
|
||||
for (AstClass subclass in astClass.subclasses) {
|
||||
addVisitNode(subclass);
|
||||
|
@ -28,7 +33,7 @@ String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
|
|||
case AstClassKind.named:
|
||||
case AstClassKind.declarative:
|
||||
if (astClass.hasVisitMethod) {
|
||||
strategy.generateVisit(astClass, sb);
|
||||
strategy.generateVisit(astModel, astClass, sb);
|
||||
}
|
||||
break;
|
||||
case AstClassKind.implementation:
|
||||
|
@ -44,7 +49,7 @@ String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
|
|||
case AstClassKind.root:
|
||||
case AstClassKind.inner:
|
||||
if (astClass.hasVisitReferenceMethod) {
|
||||
strategy.generateDefaultVisitReference(astClass, sb);
|
||||
strategy.generateDefaultVisitReference(astModel, astClass, sb);
|
||||
}
|
||||
for (AstClass subclass in astClass.subclasses) {
|
||||
addVisitReference(subclass);
|
||||
|
@ -54,7 +59,7 @@ String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
|
|||
case AstClassKind.named:
|
||||
case AstClassKind.declarative:
|
||||
if (astClass.hasVisitReferenceMethod) {
|
||||
strategy.generateVisitReference(astClass, sb);
|
||||
strategy.generateVisitReference(astModel, astClass, sb);
|
||||
}
|
||||
break;
|
||||
case AstClassKind.implementation:
|
||||
|
@ -71,7 +76,9 @@ String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
|
|||
strategy.generateFooter(astModel, sb);
|
||||
|
||||
String result = sb.toString();
|
||||
result = new DartFormatter().format(result);
|
||||
if (format) {
|
||||
result = new DartFormatter().format(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -79,21 +86,27 @@ String generateVisitor(AstModel astModel, VisitorStrategy strategy) {
|
|||
abstract class VisitorStrategy {
|
||||
const VisitorStrategy();
|
||||
|
||||
/// Comment used as doc comment for the generated visitor class.
|
||||
String get visitorComment => '';
|
||||
|
||||
/// 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);
|
||||
void generateDefaultVisit(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb);
|
||||
|
||||
/// Generates a `visitX` visitor method for [astClass].
|
||||
void generateVisit(AstClass astClass, StringBuffer sb);
|
||||
void generateVisit(AstModel astModel, AstClass astClass, StringBuffer sb);
|
||||
|
||||
/// Generates a `defaultXReference` visitor method for [astClass].
|
||||
void generateDefaultVisitReference(AstClass astClass, StringBuffer sb);
|
||||
void generateDefaultVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb);
|
||||
|
||||
/// Generates a `visitXReference` visitor method for [astClass].
|
||||
void generateVisitReference(AstClass astClass, StringBuffer sb);
|
||||
void generateVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb);
|
||||
|
||||
/// Generates the footer of the visitor library, including the visitor class
|
||||
/// declaration end.
|
||||
|
@ -119,6 +132,7 @@ abstract class Visitor0Strategy extends VisitorStrategy {
|
|||
sb.writeln('''
|
||||
import 'package:kernel/ast.dart';
|
||||
|
||||
$visitorComment
|
||||
class $visitorName$visitorTypeParameters implements Visitor<$returnType> {''');
|
||||
}
|
||||
|
||||
|
@ -128,56 +142,62 @@ class $visitorName$visitorTypeParameters implements Visitor<$returnType> {''');
|
|||
}
|
||||
|
||||
@override
|
||||
void generateDefaultVisit(AstClass astClass, StringBuffer sb) {
|
||||
void generateDefaultVisit(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} default${astClass.name}(
|
||||
${astClass.name} node) {''');
|
||||
handleDefaultVisit(astClass, sb);
|
||||
handleDefaultVisit(astModel, astClass, sb);
|
||||
sb.writeln('}');
|
||||
}
|
||||
|
||||
/// Generates the body of a `defaultX` visitor method of [astClass].
|
||||
void handleDefaultVisit(AstClass astClass, StringBuffer sb) {}
|
||||
void handleDefaultVisit(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
|
||||
@override
|
||||
void generateVisit(AstClass astClass, StringBuffer sb) {
|
||||
void generateVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} visit${astClass.name}(
|
||||
${astClass.name} node) {''');
|
||||
handleVisit(astClass, sb);
|
||||
handleVisit(astModel, astClass, sb);
|
||||
sb.writeln('}');
|
||||
}
|
||||
|
||||
/// Generates the body of a `visitX` visitor method of [astClass].
|
||||
void handleVisit(AstClass astClass, StringBuffer sb) {}
|
||||
void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
|
||||
@override
|
||||
void generateDefaultVisitReference(AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln(''''
|
||||
void generateDefaultVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} default${astClass.name}Reference(
|
||||
'${astClass.name} node) {''');
|
||||
handleDefaultVisitReference(astClass, sb);
|
||||
${astClass.name} node) {''');
|
||||
handleDefaultVisitReference(astModel, astClass, sb);
|
||||
sb.writeln('}');
|
||||
}
|
||||
|
||||
/// Generates the body of a `defaultXReference` visitor method of [astClass].
|
||||
void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {}
|
||||
void handleDefaultVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
|
||||
@override
|
||||
void generateVisitReference(AstClass astClass, StringBuffer sb) {
|
||||
void generateVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} visit${astClass.name}Reference(
|
||||
${astClass.name} node) {''');
|
||||
handleVisitReference(astClass, sb);
|
||||
handleVisitReference(astModel, astClass, sb);
|
||||
sb.writeln('}');
|
||||
}
|
||||
|
||||
/// Generates the body of a `visitXReference` visitor method of [astClass].
|
||||
void handleVisitReference(AstClass astClass, StringBuffer sb) {}
|
||||
void handleVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
}
|
||||
|
||||
/// Strategy for creating an empty `Visitor<void>` implementation.
|
||||
|
@ -220,6 +240,7 @@ abstract class Visitor1Strategy extends VisitorStrategy {
|
|||
sb.writeln('''
|
||||
import 'package:kernel/ast.dart';
|
||||
|
||||
$visitorComment
|
||||
class $visitorName$visitorTypeParameters
|
||||
implements Visitor1<$returnType, $argumentType> {''');
|
||||
}
|
||||
|
@ -230,60 +251,66 @@ class $visitorName$visitorTypeParameters
|
|||
}
|
||||
|
||||
@override
|
||||
void generateDefaultVisit(AstClass astClass, StringBuffer sb) {
|
||||
void generateDefaultVisit(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} default${astClass.name}(
|
||||
${astClass.name} node, $argumentType $argumentName) {''');
|
||||
handleDefaultVisit(astClass, sb);
|
||||
handleDefaultVisit(astModel, astClass, sb);
|
||||
sb.writeln('''
|
||||
}''');
|
||||
}
|
||||
|
||||
/// Generates the body of a `defaultX` visitor method of [astClass].
|
||||
void handleDefaultVisit(AstClass astClass, StringBuffer sb) {}
|
||||
void handleDefaultVisit(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
|
||||
@override
|
||||
void generateVisit(AstClass astClass, StringBuffer sb) {
|
||||
void generateVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} visit${astClass.name}(
|
||||
${astClass.name} node, $argumentType $argumentName) {''');
|
||||
handleVisit(astClass, sb);
|
||||
handleVisit(astModel, astClass, sb);
|
||||
sb.writeln('''
|
||||
}''');
|
||||
}
|
||||
|
||||
/// Generates the body of a `visitX` visitor method of [astClass].
|
||||
void handleVisit(AstClass astClass, StringBuffer sb) {}
|
||||
void handleVisit(AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
|
||||
@override
|
||||
void generateDefaultVisitReference(AstClass astClass, StringBuffer sb) {
|
||||
void generateDefaultVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} default${astClass.name}Reference(
|
||||
${astClass.name} node, $argumentType $argumentName) {''');
|
||||
handleDefaultVisitReference(astClass, sb);
|
||||
handleDefaultVisitReference(astModel, astClass, sb);
|
||||
sb.writeln('''
|
||||
}''');
|
||||
}
|
||||
|
||||
/// Generates the body of a `defaultXReference` visitor method of [astClass].
|
||||
void handleDefaultVisitReference(AstClass astClass, StringBuffer sb) {}
|
||||
void handleDefaultVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
|
||||
@override
|
||||
void generateVisitReference(AstClass astClass, StringBuffer sb) {
|
||||
void generateVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {
|
||||
sb.writeln('''
|
||||
@override
|
||||
${returnType} visit${astClass.name}Reference(
|
||||
${astClass.name} node, $argumentType $argumentName) {''');
|
||||
handleVisitReference(astClass, sb);
|
||||
handleVisitReference(astModel, astClass, sb);
|
||||
sb.writeln('''
|
||||
}''');
|
||||
}
|
||||
|
||||
/// Generates the body of a `visitXReference` visitor method of [astClass].
|
||||
void handleVisitReference(AstClass astClass, StringBuffer sb) {}
|
||||
void handleVisitReference(
|
||||
AstModel astModel, AstClass astClass, StringBuffer sb) {}
|
||||
}
|
||||
|
||||
/// Strategy for creating an empty `Visitor1<void,Null>` implementation.
|
||||
|
|
|
@ -5328,6 +5328,7 @@ class InstanceGetterInvocation extends InstanceInvocationExpression {
|
|||
interfaceTarget.acceptReference(v);
|
||||
name.accept(v);
|
||||
arguments.accept(v);
|
||||
functionType?.accept(v);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -5342,6 +5343,9 @@ class InstanceGetterInvocation extends InstanceInvocationExpression {
|
|||
arguments = v.transform(arguments);
|
||||
arguments.parent = this;
|
||||
}
|
||||
if (functionType != null) {
|
||||
functionType = v.visitDartType(functionType!) as FunctionType;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -5356,6 +5360,10 @@ class InstanceGetterInvocation extends InstanceInvocationExpression {
|
|||
arguments = v.transform(arguments);
|
||||
arguments.parent = this;
|
||||
}
|
||||
if (functionType != null) {
|
||||
functionType =
|
||||
v.visitDartType(functionType!, cannotRemoveSentinel) as FunctionType;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
1179
pkg/kernel/lib/src/coverage.dart
Normal file
1179
pkg/kernel/lib/src/coverage.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -950,12 +950,14 @@ class EquivalenceVisitor implements Visitor1<bool, Node> {
|
|||
return result;
|
||||
}
|
||||
|
||||
/// Returns `true` if [a] and [b] are equivalent, as defined by their
|
||||
/// corresponding canonical names. Inequivalence is _not_ registered.
|
||||
/// Returns `true` if [a] and [b] are equivalent, either by existing
|
||||
/// assumption or as defined by their corresponding canonical names.
|
||||
/// Inequivalence is _not_ registered.
|
||||
bool matchNamedNodes(NamedNode? a, NamedNode? b) {
|
||||
return identical(a, b) ||
|
||||
a == null ||
|
||||
b == null ||
|
||||
checkAssumedReferences(a.reference, b.reference) ||
|
||||
new ReferenceName.fromNamedNode(a) ==
|
||||
new ReferenceName.fromNamedNode(b);
|
||||
}
|
||||
|
@ -973,10 +975,12 @@ class EquivalenceVisitor implements Visitor1<bool, Node> {
|
|||
return _checkingState.assumeReferences(a, b);
|
||||
}
|
||||
|
||||
/// Returns `true` if [a] and [b] are equivalent, as defined by their
|
||||
/// corresponding canonical names. Inequivalence is _not_ registered.
|
||||
/// Returns `true` if [a] and [b] are equivalent, either by existing
|
||||
/// assumption or as defined by their corresponding canonical names.
|
||||
/// Inequivalence is _not_ registered.
|
||||
bool matchReferences(Reference? a, Reference? b) {
|
||||
return identical(a, b) ||
|
||||
checkAssumedReferences(a, b) ||
|
||||
ReferenceName.fromReference(a) == ReferenceName.fromReference(b);
|
||||
}
|
||||
|
||||
|
|
|
@ -265,7 +265,7 @@ class ReferenceName {
|
|||
return new ReferenceName.internal(ReferenceNameKind.Member, node.name,
|
||||
new ReferenceName.fromNamedNode(node.enclosingLibrary));
|
||||
} else if (node is Member) {
|
||||
Class? enclosingClass = node.enclosingClass;
|
||||
TreeNode? parent = node.parent;
|
||||
Reference? libraryReference = node.name.libraryName;
|
||||
String? uri;
|
||||
if (libraryReference != null) {
|
||||
|
@ -276,18 +276,15 @@ class ReferenceName {
|
|||
uri = libraryReference.canonicalName?.name;
|
||||
}
|
||||
}
|
||||
if (enclosingClass != null) {
|
||||
return new ReferenceName.internal(
|
||||
ReferenceNameKind.Member,
|
||||
node.name.text,
|
||||
new ReferenceName.fromNamedNode(enclosingClass),
|
||||
uri);
|
||||
if (parent is Class) {
|
||||
return new ReferenceName.internal(ReferenceNameKind.Member,
|
||||
node.name.text, new ReferenceName.fromNamedNode(parent), uri);
|
||||
} else if (parent is Library) {
|
||||
return new ReferenceName.internal(ReferenceNameKind.Member,
|
||||
node.name.text, new ReferenceName.fromNamedNode(parent), uri);
|
||||
} else {
|
||||
return new ReferenceName.internal(
|
||||
ReferenceNameKind.Member,
|
||||
node.name.text,
|
||||
new ReferenceName.fromNamedNode(node.enclosingLibrary),
|
||||
uri);
|
||||
ReferenceNameKind.Member, node.name.text, null, uri);
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentError(
|
||||
|
|
1354
pkg/kernel/lib/src/node_creator.dart
Normal file
1354
pkg/kernel/lib/src/node_creator.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -33,10 +33,9 @@ String libraryNameToString(Library? node) {
|
|||
|
||||
String qualifiedClassNameToString(Class node,
|
||||
{bool includeLibraryName: false}) {
|
||||
if (includeLibraryName) {
|
||||
return libraryNameToString(node.enclosingLibrary) +
|
||||
'::' +
|
||||
classNameToString(node);
|
||||
TreeNode? parent = node.parent;
|
||||
if (parent is Library && includeLibraryName) {
|
||||
return libraryNameToString(parent) + '::' + classNameToString(node);
|
||||
} else {
|
||||
return classNameToString(node);
|
||||
}
|
||||
|
@ -90,10 +89,9 @@ String classNameToString(Class? node) {
|
|||
|
||||
String qualifiedExtensionNameToString(Extension node,
|
||||
{bool includeLibraryName: false}) {
|
||||
if (includeLibraryName) {
|
||||
return libraryNameToString(node.enclosingLibrary) +
|
||||
'::' +
|
||||
extensionNameToString(node);
|
||||
TreeNode? parent = node.parent;
|
||||
if (parent is Library && includeLibraryName) {
|
||||
return libraryNameToString(parent) + '::' + extensionNameToString(node);
|
||||
} else {
|
||||
return extensionNameToString(node);
|
||||
}
|
||||
|
@ -127,10 +125,9 @@ String extensionNameToString(Extension? node) {
|
|||
|
||||
String qualifiedTypedefNameToString(Typedef node,
|
||||
{bool includeLibraryName: false}) {
|
||||
if (includeLibraryName) {
|
||||
return libraryNameToString(node.enclosingLibrary) +
|
||||
'::' +
|
||||
typedefNameToString(node);
|
||||
TreeNode? parent = node.parent;
|
||||
if (parent is Library && includeLibraryName) {
|
||||
return libraryNameToString(parent) + '::' + typedefNameToString(node);
|
||||
} else {
|
||||
return typedefNameToString(node);
|
||||
}
|
||||
|
@ -164,15 +161,14 @@ String typedefNameToString(Typedef? node) {
|
|||
|
||||
String qualifiedMemberNameToString(Member node,
|
||||
{bool includeLibraryName: false}) {
|
||||
if (node.enclosingClass != null) {
|
||||
return qualifiedClassNameToString(node.enclosingClass!,
|
||||
TreeNode? parent = node.parent;
|
||||
if (parent is Class) {
|
||||
return qualifiedClassNameToString(parent,
|
||||
includeLibraryName: includeLibraryName) +
|
||||
'.' +
|
||||
memberNameToString(node);
|
||||
} else if (includeLibraryName) {
|
||||
return libraryNameToString(node.enclosingLibrary) +
|
||||
'::' +
|
||||
memberNameToString(node);
|
||||
} else if (parent is Library && includeLibraryName) {
|
||||
return libraryNameToString(parent) + '::' + memberNameToString(node);
|
||||
} else {
|
||||
return memberNameToString(node);
|
||||
}
|
||||
|
|
170
pkg/kernel/test/clone_test.dart
Normal file
170
pkg/kernel/test/clone_test.dart
Normal file
|
@ -0,0 +1,170 @@
|
|||
// 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/clone.dart';
|
||||
import 'package:kernel/src/coverage.dart';
|
||||
import 'package:kernel/src/equivalence.dart';
|
||||
import 'package:kernel/src/node_creator.dart';
|
||||
|
||||
main() {
|
||||
testBodyCloning();
|
||||
testMemberCloning();
|
||||
}
|
||||
|
||||
void testBodyCloning() {
|
||||
// TODO(johnniwinther): Add a test for cloning in context.
|
||||
NodeCreator creator =
|
||||
new NodeCreator(initializers: [], members: [], nodes: inBodyNodeKinds);
|
||||
List<TreeNode> nodes = creator.generateBodies();
|
||||
|
||||
CoverageVisitor coverageVisitor = new CoverageVisitor();
|
||||
for (TreeNode node in nodes) {
|
||||
node.accept(coverageVisitor);
|
||||
CloneVisitorNotMembers cloner = new CloneVisitorNotMembers();
|
||||
TreeNode clone = cloner.clone(node);
|
||||
EquivalenceResult result = checkEquivalence(node, clone);
|
||||
if (!result.isEquivalent) {
|
||||
print(result);
|
||||
}
|
||||
Expect.isTrue(result.isEquivalent, "$node");
|
||||
}
|
||||
Expect.isEmpty(
|
||||
creator.createdKinds.toSet()..removeAll(coverageVisitor.visited),
|
||||
'Nodes not covered in testing.');
|
||||
}
|
||||
|
||||
void testMemberCloning() {
|
||||
NodeCreator creator = new NodeCreator(nodes: inBodyNodeKinds);
|
||||
Component component = creator.generateComponent();
|
||||
|
||||
CoverageVisitor coverageVisitor = new CoverageVisitor();
|
||||
|
||||
void testMembers<M extends Member>(
|
||||
Iterable<M> members,
|
||||
M Function(CloneVisitorWithMembers, M) cloneFunction,
|
||||
String Function(M) toStringFunction) {
|
||||
for (M member in members) {
|
||||
member.accept(coverageVisitor);
|
||||
CloneVisitorWithMembers cloner = new CloneVisitorWithMembers();
|
||||
M clone = cloneFunction(cloner, member);
|
||||
EquivalenceResult result = checkEquivalence(member, clone,
|
||||
strategy: const MemberEquivalenceStrategy());
|
||||
if (!result.isEquivalent) {
|
||||
print(result);
|
||||
}
|
||||
Expect.isTrue(result.isEquivalent, toStringFunction(member));
|
||||
}
|
||||
}
|
||||
|
||||
void testProcedures(Iterable<Procedure> procedures) {
|
||||
testMembers<Procedure>(
|
||||
procedures,
|
||||
(cloner, procedure) => cloner.cloneProcedure(procedure, null),
|
||||
(procedure) => "${procedure.runtimeType}(${procedure.name}):"
|
||||
"${procedure.function.body}");
|
||||
}
|
||||
|
||||
void testFields(Iterable<Field> fields) {
|
||||
testMembers<Field>(
|
||||
fields,
|
||||
(cloner, field) => cloner.cloneField(field, null, null),
|
||||
(field) => "${field.runtimeType}(${field.name}):"
|
||||
"${field.initializer}");
|
||||
}
|
||||
|
||||
void testConstructors(Iterable<Constructor> constructors) {
|
||||
testMembers<Constructor>(
|
||||
constructors,
|
||||
(cloner, constructor) => cloner.cloneConstructor(constructor, null),
|
||||
(constructor) => "${constructor.runtimeType}(${constructor.name}):"
|
||||
"${constructor.initializers}:"
|
||||
"${constructor.function.body}");
|
||||
}
|
||||
|
||||
void testRedirectingFactories(
|
||||
Iterable<RedirectingFactory> redirectingFactory) {
|
||||
testMembers<RedirectingFactory>(
|
||||
redirectingFactory,
|
||||
(cloner, redirectingFactory) =>
|
||||
cloner.cloneRedirectingFactory(redirectingFactory, null),
|
||||
(redirectingFactory) =>
|
||||
"${redirectingFactory.runtimeType}(${redirectingFactory.name}):"
|
||||
"${redirectingFactory.function.body}");
|
||||
}
|
||||
|
||||
for (Library library in component.libraries) {
|
||||
testProcedures(library.procedures);
|
||||
testFields(library.fields);
|
||||
for (Class cls in library.classes) {
|
||||
testProcedures(cls.procedures);
|
||||
testFields(cls.fields);
|
||||
testConstructors(cls.constructors);
|
||||
testRedirectingFactories(cls.redirectingFactories);
|
||||
}
|
||||
}
|
||||
Expect.isEmpty(
|
||||
creator.createdKinds.toSet()..removeAll(coverageVisitor.visited),
|
||||
'Nodes not covered in testing.');
|
||||
}
|
||||
|
||||
class MemberEquivalenceStrategy extends EquivalenceStrategy {
|
||||
const MemberEquivalenceStrategy();
|
||||
|
||||
void assumeClonedReferences(EquivalenceVisitor visitor, Member member1,
|
||||
Reference? reference1, Member member2, Reference? reference2) {
|
||||
if (reference1 != null && reference2 != null) {
|
||||
ReferenceName referenceName1 = ReferenceName.fromNamedNode(member1);
|
||||
ReferenceName referenceName2 = ReferenceName.fromNamedNode(member2);
|
||||
if (referenceName1.memberName == referenceName2.memberName &&
|
||||
referenceName1.memberUri == referenceName2.memberUri &&
|
||||
referenceName2.declarationName == null ||
|
||||
referenceName2.libraryUri == null) {
|
||||
visitor.assumeReferences(reference1, reference2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool checkProcedure(
|
||||
EquivalenceVisitor visitor, Procedure? node, Object? other) {
|
||||
if (node is Procedure && other is Procedure) {
|
||||
assumeClonedReferences(
|
||||
visitor, node, node.reference, other, other.reference);
|
||||
}
|
||||
return super.checkProcedure(visitor, node, other);
|
||||
}
|
||||
|
||||
@override
|
||||
bool checkConstructor(
|
||||
EquivalenceVisitor visitor, Constructor? node, Object? other) {
|
||||
if (node is Constructor && other is Constructor) {
|
||||
assumeClonedReferences(
|
||||
visitor, node, node.reference, other, other.reference);
|
||||
}
|
||||
return super.checkConstructor(visitor, node, other);
|
||||
}
|
||||
|
||||
@override
|
||||
bool checkRedirectingFactory(
|
||||
EquivalenceVisitor visitor, RedirectingFactory? node, Object? other) {
|
||||
if (node is RedirectingFactory && other is RedirectingFactory) {
|
||||
assumeClonedReferences(
|
||||
visitor, node, node.reference, other, other.reference);
|
||||
}
|
||||
return super.checkRedirectingFactory(visitor, node, other);
|
||||
}
|
||||
|
||||
@override
|
||||
bool checkField(EquivalenceVisitor visitor, Field? node, Object? other) {
|
||||
if (node is Field && other is Field) {
|
||||
assumeClonedReferences(
|
||||
visitor, node, node.getterReference, other, other.getterReference);
|
||||
assumeClonedReferences(
|
||||
visitor, node, node.setterReference, other, other.setterReference);
|
||||
}
|
||||
return super.checkField(visitor, node, other);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue