[cfe] Add test for type arguments on map().toList()
This updates the analysis helpers to add a test for the `map().toList()`
problem addressed in https://dart-review.googlesource.com/c/sdk/+/241605
Included in the CL is the addition of the 'analyze.dart' tools, which
can be used for advanced code searches. This was used to find the
occurences of `map().toList()`.
Change-Id: I6e9da282e37fde4534ed5e308260f092779d0750
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241744
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fasta/incremental_compiler.dart b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
index ccaf338..ac929fb 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -1952,7 +1952,7 @@
FunctionNode parameters = new FunctionNode(null,
typeParameters: typeDefinitions,
positionalParameters: definitions.keys
- .map((name) =>
+ .map<VariableDeclaration>((name) =>
new VariableDeclarationImpl(name, 0, type: definitions[name])
..fileOffset = cls?.fileOffset ??
extension?.fileOffset ??
diff --git a/pkg/front_end/lib/src/testing/analysis_helper.dart b/pkg/front_end/lib/src/testing/analysis_helper.dart
new file mode 100644
index 0000000..77a3b77
--- /dev/null
+++ b/pkg/front_end/lib/src/testing/analysis_helper.dart
@@ -0,0 +1,315 @@
+// Copyright (c) 2022, 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:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:front_end/src/api_prototype/compiler_options.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/compute_platform_binaries_location.dart';
+import 'package:front_end/src/fasta/command_line_reporting.dart';
+import 'package:front_end/src/fasta/fasta_codes.dart';
+import 'package:front_end/src/fasta/kernel/redirecting_factory_body.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/type_environment.dart';
+
+typedef PerformAnalysisFunction = void Function(
+ DiagnosticMessageHandler onDiagnostic, Component component);
+typedef UriFilter = bool Function(Uri uri);
+
+Future<void> runAnalysis(
+ List<Uri> entryPoints, PerformAnalysisFunction performAnalysis) async {
+ CompilerOptions options = new CompilerOptions();
+ options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
+ options.packagesFileUri = Uri.base.resolve('.dart_tool/package_config.json');
+
+ options.onDiagnostic = (DiagnosticMessage message) {
+ printDiagnosticMessage(message, print);
+ };
+ InternalCompilerResult compilerResult = await kernelForProgramInternal(
+ entryPoints.first, options,
+ retainDataForTesting: true,
+ requireMain: false,
+ additionalSources: entryPoints.skip(1).toList())
+ as InternalCompilerResult;
+
+ performAnalysis(options.onDiagnostic!, compilerResult.component!);
+}
+
+class StaticTypeVisitorBase extends RecursiveVisitor {
+ final TypeEnvironment typeEnvironment;
+
+ StaticTypeContext? staticTypeContext;
+
+ StaticTypeVisitorBase(Component component, ClassHierarchy classHierarchy)
+ : typeEnvironment =
+ new TypeEnvironment(new CoreTypes(component), classHierarchy);
+
+ @override
+ void visitProcedure(Procedure node) {
+ if (node.kind == ProcedureKind.Factory && isRedirectingFactory(node)) {
+ // Don't visit redirecting factories.
+ return;
+ }
+ staticTypeContext = new StaticTypeContext(node, typeEnvironment);
+ super.visitProcedure(node);
+ staticTypeContext = null;
+ }
+
+ @override
+ void visitField(Field node) {
+ if (isRedirectingFactoryField(node)) {
+ // Skip synthetic .dill members.
+ return;
+ }
+ staticTypeContext = new StaticTypeContext(node, typeEnvironment);
+ super.visitField(node);
+ staticTypeContext = null;
+ }
+
+ @override
+ void visitConstructor(Constructor node) {
+ staticTypeContext = new StaticTypeContext(node, typeEnvironment);
+ super.visitConstructor(node);
+ staticTypeContext = null;
+ }
+}
+
+class AnalysisVisitor extends StaticTypeVisitorBase {
+ final DiagnosticMessageHandler onDiagnostic;
+ final Component component;
+ final UriFilter? uriFilter;
+ late final AnalysisInterface interface;
+
+ Map<String, Map<String, List<FormattedMessage>>> _messages = {};
+
+ AnalysisVisitor(this.onDiagnostic, this.component, this.uriFilter)
+ : super(component,
+ new ClassHierarchy(component, new CoreTypes(component))) {
+ interface = new AnalysisInterface(this);
+ }
+
+ @override
+ void visitLibrary(Library node) {
+ if (uriFilter != null) {
+ if (uriFilter!(node.importUri)) {
+ super.visitLibrary(node);
+ }
+ } else {
+ super.visitLibrary(node);
+ }
+ }
+
+ void registerMessage(TreeNode node, String message) {
+ Location location = node.location!;
+ Uri uri = location.file;
+ String uriString = relativizeUri(uri)!;
+ Map<String, List<FormattedMessage>> actualMap = _messages.putIfAbsent(
+ uriString, () => <String, List<FormattedMessage>>{});
+ if (uri.isScheme('org-dartlang-sdk')) {
+ location = new Location(Uri.base.resolve(uri.path.substring(1)),
+ location.line, location.column);
+ }
+ LocatedMessage locatedMessage = templateUnspecified
+ .withArguments(message)
+ .withLocation(uri, node.fileOffset, noLength);
+ FormattedMessage diagnosticMessage = locatedMessage.withFormatting(
+ format(locatedMessage, Severity.warning,
+ location: location, uriToSource: component.uriToSource),
+ location.line,
+ location.column,
+ Severity.warning,
+ []);
+ actualMap
+ .putIfAbsent(message, () => <FormattedMessage>[])
+ .add(diagnosticMessage);
+ }
+
+ void forEachMessage(
+ void Function(String, Map<String, List<FormattedMessage>>) f) {
+ _messages.forEach(f);
+ }
+
+ Map<String, List<FormattedMessage>>? getMessagesForUri(String uri) {
+ return _messages[uri];
+ }
+
+ void printMessages() {
+ forEachMessage((String uri, Map<String, List<FormattedMessage>> messages) {
+ messages.forEach((String message, List<FormattedMessage> actualMessages) {
+ for (FormattedMessage message in actualMessages) {
+ onDiagnostic(message);
+ }
+ });
+ });
+ }
+}
+
+/// Convenience interface for performing analysis.
+class AnalysisInterface {
+ final AnalysisVisitor _visitor;
+ final ComponentLookup _componentLookup;
+
+ AnalysisInterface(this._visitor)
+ : _componentLookup = new ComponentLookup(_visitor.component);
+
+ void reportMessage(TreeNode node, String message) {
+ _visitor.registerMessage(node, message);
+ }
+
+ InterfaceType createInterfaceType(String className,
+ {String? uri, List<DartType>? typeArguments}) {
+ LibraryLookup libraryLookup =
+ _componentLookup.getLibrary(Uri.parse(uri ?? 'dart:core'));
+ ClassLookup classLookup = libraryLookup.getClass(className);
+ Class cls = classLookup.cls;
+ return new InterfaceType(
+ cls,
+ Nullability.nonNullable,
+ typeArguments ??
+ new List<DartType>.generate(
+ cls.typeParameters.length, (index) => const DynamicType()));
+ }
+
+ bool isSubtypeOf(DartType subtype, DartType supertype) {
+ return _visitor.typeEnvironment
+ .isSubtypeOf(subtype, supertype, SubtypeCheckMode.withNullabilities);
+ }
+}
+
+typedef GeneralAnalysisFunction = void Function(
+ TreeNode node, AnalysisInterface interface);
+
+/// Generalized analyzer that uses a single [GeneralAnalysisFunction] on all
+/// [TreeNode]s.
+class GeneralAnalyzer extends AnalysisVisitor {
+ final GeneralAnalysisFunction analyzer;
+
+ GeneralAnalyzer(DiagnosticMessageHandler onDiagnostic, Component component,
+ bool Function(Uri uri)? analyzedUrisFilter, this.analyzer)
+ : super(onDiagnostic, component, analyzedUrisFilter);
+
+ @override
+ void defaultTreeNode(TreeNode node) {
+ analyzer(node, interface);
+ super.defaultTreeNode(node);
+ }
+}
+
+/// Returns a function that will perform [analysisFunction] on [TreeNode]s
+/// in a component, using [uriFilter] to filter which libraries that will be
+/// visited.
+PerformAnalysisFunction performGeneralAnalysis(
+ UriFilter? uriFilter, GeneralAnalysisFunction analysisFunction) {
+ return (DiagnosticMessageHandler onDiagnostic, Component component) {
+ GeneralAnalyzer analyzer = new GeneralAnalyzer(
+ onDiagnostic, component, uriFilter, analysisFunction);
+ component.accept(analyzer);
+ analyzer.printMessages();
+ };
+}
+
+/// Helper class for looking up libraries in a [Component].
+class ComponentLookup {
+ final Component _component;
+
+ ComponentLookup(this._component);
+
+ Map<Uri, LibraryLookup>? _libraries;
+
+ LibraryLookup getLibrary(Uri uri) {
+ LibraryLookup? libraryLookup = (_libraries ??= new Map.fromIterable(
+ _component.libraries,
+ key: (library) => library.importUri,
+ value: (library) => new LibraryLookup(library)))[uri];
+ if (libraryLookup == null) {
+ throw "Couldn't find library for '$uri'.";
+ }
+ return libraryLookup;
+ }
+}
+
+/// Helper class for looking up classes and members in a [Library].
+// TODO(johnniwinther): Support member lookup.
+class LibraryLookup {
+ final Library library;
+
+ LibraryLookup(this.library);
+
+ Map<String, ClassLookup>? _classes;
+
+ ClassLookup getClass(String name) {
+ ClassLookup? classLookup = (_classes ??= new Map.fromIterable(
+ library.classes,
+ key: (cls) => cls.name,
+ value: (cls) => new ClassLookup(cls)))[name];
+ if (classLookup == null) {
+ throw "Couldn't find class '$name' in ${library.importUri}.";
+ }
+ return classLookup;
+ }
+}
+
+/// Helper class for looking up members in a [Class].
+// TODO(johnniwinther): Support member lookup.
+class ClassLookup {
+ final Class cls;
+
+ ClassLookup(this.cls);
+}
+
+/// Entry points used for analyzing cfe source code.
+// TODO(johnniwinther): Update this to include all files in the cfe, and not
+// only those reachable from 'compiler.dart'.
+final List<Uri> cfeOnlyEntryPoints = [
+ Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart')
+];
+
+/// Filter function used to only analyze cfe source code.
+bool cfeOnly(Uri uri) {
+ String text = '$uri';
+ for (String path in [
+ 'package:_fe_analyzer_shared/',
+ 'package:kernel/',
+ 'package:front_end/',
+ ]) {
+ if (text.startsWith(path)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Entry points used for analyzing cfe and backend source code.
+// TODO(johnniwinther): Update this to include all files in cfe and backends,
+// and not only those reachable from these entry points.
+List<Uri> cfeAndBackendsEntryPoints = [
+ Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart'),
+ Uri.base.resolve('pkg/vm/lib/kernel_front_end.dart'),
+ Uri.base.resolve('pkg/compiler/bin/dart2js.dart'),
+ Uri.base.resolve('pkg/dev_compiler/bin/dartdevc.dart'),
+ Uri.base.resolve('pkg/frontend_server/bin/frontend_server_starter.dart'),
+];
+
+/// Filter function used to only analyze cfe and backend source code.
+bool cfeAndBackends(Uri uri) {
+ String text = '$uri';
+ for (String path in [
+ 'package:_fe_analyzer_shared/',
+ 'package:kernel/',
+ 'package:front_end/',
+ 'package:frontend_server/',
+ 'package:vm/',
+ 'package:compiler/',
+ 'package:dartdevc/',
+ 'package:_js_interop_checks/',
+ ]) {
+ if (text.startsWith(path)) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index bb4b5ed..5590566 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -645,6 +645,7 @@
interleaved
intermediate
internet
+interop
interpolations
interrupted
intersects
diff --git a/pkg/front_end/test/static_types/analysis_helper.dart b/pkg/front_end/test/static_types/analysis_helper.dart
deleted file mode 100644
index ab60ab7..0000000
--- a/pkg/front_end/test/static_types/analysis_helper.dart
+++ /dev/null
@@ -1,333 +0,0 @@
-// 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.
-
-import 'dart:convert' as json;
-import 'dart:io';
-
-import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
-import 'package:expect/expect.dart';
-import 'package:front_end/src/api_prototype/compiler_options.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/compute_platform_binaries_location.dart';
-import 'package:front_end/src/fasta/command_line_reporting.dart';
-import 'package:front_end/src/fasta/fasta_codes.dart';
-import 'package:front_end/src/fasta/kernel/redirecting_factory_body.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/type_environment.dart';
-
-Future<void> run(Uri entryPoint, String allowedListPath,
- {bool verbose = false,
- bool generate = false,
- bool Function(Uri uri)? analyzedUrisFilter}) async {
- CompilerOptions options = new CompilerOptions();
- options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
-
- options.onDiagnostic = (DiagnosticMessage message) {
- printDiagnosticMessage(message, print);
- };
- InternalCompilerResult compilerResult = await kernelForProgramInternal(
- entryPoint, options, retainDataForTesting: true, requireMain: false)
- as InternalCompilerResult;
-
- new DynamicVisitor(options.onDiagnostic!, compilerResult.component!,
- allowedListPath, analyzedUrisFilter)
- .run(verbose: verbose, generate: generate);
-}
-
-class StaticTypeVisitorBase extends RecursiveVisitor {
- final TypeEnvironment typeEnvironment;
-
- StaticTypeContext? staticTypeContext;
-
- StaticTypeVisitorBase(Component component, ClassHierarchy classHierarchy)
- : typeEnvironment =
- new TypeEnvironment(new CoreTypes(component), classHierarchy);
-
- @override
- void visitProcedure(Procedure node) {
- if (node.kind == ProcedureKind.Factory && isRedirectingFactory(node)) {
- // Don't visit redirecting factories.
- return;
- }
- staticTypeContext = new StaticTypeContext(node, typeEnvironment);
- super.visitProcedure(node);
- staticTypeContext = null;
- }
-
- @override
- void visitField(Field node) {
- if (isRedirectingFactoryField(node)) {
- // Skip synthetic .dill members.
- return;
- }
- staticTypeContext = new StaticTypeContext(node, typeEnvironment);
- super.visitField(node);
- staticTypeContext = null;
- }
-
- @override
- void visitConstructor(Constructor node) {
- staticTypeContext = new StaticTypeContext(node, typeEnvironment);
- super.visitConstructor(node);
- staticTypeContext = null;
- }
-}
-
-class DynamicVisitor extends StaticTypeVisitorBase {
- // TODO(johnniwinther): Enable this when it is less noisy.
- static const bool checkReturnTypes = false;
-
- final DiagnosticMessageHandler onDiagnostic;
- final Component component;
- final String? _allowedListPath;
- final bool Function(Uri uri)? analyzedUrisFilter;
-
- Map _expectedJson = {};
- Map<String, Map<String, List<FormattedMessage>>> _actualMessages = {};
-
- DynamicVisitor(this.onDiagnostic, this.component, this._allowedListPath,
- this.analyzedUrisFilter)
- : super(
- component, new ClassHierarchy(component, new CoreTypes(component)));
-
- void run({bool verbose = false, bool generate = false}) {
- if (!generate && _allowedListPath != null) {
- File file = new File(_allowedListPath!);
- if (file.existsSync()) {
- try {
- _expectedJson = json.jsonDecode(file.readAsStringSync());
- } catch (e) {
- Expect.fail('Error reading allowed list from $_allowedListPath: $e');
- }
- }
- }
- component.accept(this);
- if (generate && _allowedListPath != null) {
- Map<String, Map<String, int>> actualJson = {};
- _actualMessages.forEach(
- (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
- Map<String, int> map = {};
- actualMessagesMap
- .forEach((String message, List<FormattedMessage> actualMessages) {
- map[message] = actualMessages.length;
- });
- actualJson[uri] = map;
- });
-
- new File(_allowedListPath!).writeAsStringSync(
- new json.JsonEncoder.withIndent(' ').convert(actualJson));
- return;
- }
-
- int errorCount = 0;
- _expectedJson.forEach((uri, expectedMessages) {
- Map<String, List<FormattedMessage>>? actualMessagesMap =
- _actualMessages[uri];
- if (actualMessagesMap == null) {
- print("Error: Allowed-listing of uri '$uri' isn't used. "
- "Remove it from the allowed-list.");
- errorCount++;
- } else {
- expectedMessages.forEach((expectedMessage, expectedCount) {
- List<FormattedMessage>? actualMessages =
- actualMessagesMap[expectedMessage];
- if (actualMessages == null) {
- print("Error: Allowed-listing of message '$expectedMessage' "
- "in uri '$uri' isn't used. Remove it from the allowed-list.");
- errorCount++;
- } else {
- int actualCount = actualMessages.length;
- if (actualCount != expectedCount) {
- print("Error: Unexpected count of allowed message "
- "'$expectedMessage' in uri '$uri'. "
- "Expected $expectedCount, actual $actualCount:");
- print(
- '----------------------------------------------------------');
- for (FormattedMessage message in actualMessages) {
- onDiagnostic(message);
- }
- print(
- '----------------------------------------------------------');
- errorCount++;
- }
- }
- });
- actualMessagesMap
- .forEach((String message, List<FormattedMessage> actualMessages) {
- if (!expectedMessages.containsKey(message)) {
- for (FormattedMessage message in actualMessages) {
- onDiagnostic(message);
- errorCount++;
- }
- }
- });
- }
- });
- _actualMessages.forEach(
- (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
- if (!_expectedJson.containsKey(uri)) {
- actualMessagesMap
- .forEach((String message, List<FormattedMessage> actualMessages) {
- for (FormattedMessage message in actualMessages) {
- onDiagnostic(message);
- errorCount++;
- }
- });
- }
- });
- if (errorCount != 0) {
- print('$errorCount error(s) found.');
- print("""
-
-********************************************************************************
-* Unexpected dynamic invocations found by test:
-*
-* ${relativizeUri(Platform.script)}
-*
-* Please address the reported errors, or, if the errors are as expected, run
-*
-* dart ${relativizeUri(Platform.script)} -g
-*
-* to update the expectation file.
-********************************************************************************
-""");
- exit(-1);
- }
- if (verbose) {
- _actualMessages.forEach(
- (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
- actualMessagesMap
- .forEach((String message, List<FormattedMessage> actualMessages) {
- for (FormattedMessage message in actualMessages) {
- // TODO(johnniwinther): It is unnecessarily complicated to just
- // add ' (allowed)' to an existing message!
- LocatedMessage locatedMessage = message.locatedMessage;
- String newMessageText =
- '${locatedMessage.messageObject.problemMessage} (allowed)';
- message = locatedMessage.withFormatting(
- format(
- new LocatedMessage(
- locatedMessage.uri,
- locatedMessage.charOffset,
- locatedMessage.length,
- new Message(locatedMessage.messageObject.code,
- problemMessage: newMessageText,
- correctionMessage:
- locatedMessage.messageObject.correctionMessage,
- arguments: locatedMessage.messageObject.arguments)),
- Severity.warning,
- location: new Location(
- message.uri!, message.line, message.column),
- uriToSource: component.uriToSource),
- message.line,
- message.column,
- Severity.warning,
- []);
- onDiagnostic(message);
- }
- });
- });
- } else {
- int total = 0;
- _actualMessages.forEach(
- (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
- int count = 0;
- actualMessagesMap
- .forEach((String message, List<FormattedMessage> actualMessages) {
- count += actualMessages.length;
- });
-
- print('${count} error(s) allowed in $uri');
- total += count;
- });
- if (total > 0) {
- print('${total} error(s) allowed in total.');
- }
- }
- }
-
- @override
- void visitLibrary(Library node) {
- if (analyzedUrisFilter != null) {
- if (analyzedUrisFilter!(node.importUri)) {
- super.visitLibrary(node);
- }
- } else {
- super.visitLibrary(node);
- }
- }
-
- @override
- void visitDynamicGet(DynamicGet node) {
- registerError(node, "Dynamic access of '${node.name}'.");
- super.visitDynamicGet(node);
- }
-
- @override
- void visitDynamicSet(DynamicSet node) {
- registerError(node, "Dynamic update to '${node.name}'.");
- super.visitDynamicSet(node);
- }
-
- @override
- void visitDynamicInvocation(DynamicInvocation node) {
- registerError(node, "Dynamic invocation of '${node.name}'.");
- super.visitDynamicInvocation(node);
- }
-
- @override
- void visitFunctionDeclaration(FunctionDeclaration node) {
- if (checkReturnTypes && node.function.returnType is DynamicType) {
- registerError(node, "Dynamic return type");
- }
- super.visitFunctionDeclaration(node);
- }
-
- @override
- void visitFunctionExpression(FunctionExpression node) {
- if (checkReturnTypes && node.function.returnType is DynamicType) {
- registerError(node, "Dynamic return type");
- }
- super.visitFunctionExpression(node);
- }
-
- @override
- void visitProcedure(Procedure node) {
- if (checkReturnTypes &&
- node.function.returnType is DynamicType &&
- node.name.text != 'noSuchMethod') {
- registerError(node, "Dynamic return type on $node");
- }
- super.visitProcedure(node);
- }
-
- void registerError(TreeNode node, String message) {
- Location location = node.location!;
- Uri uri = location.file;
- String uriString = relativizeUri(uri)!;
- Map<String, List<FormattedMessage>> actualMap = _actualMessages.putIfAbsent(
- uriString, () => <String, List<FormattedMessage>>{});
- if (uri.isScheme('org-dartlang-sdk')) {
- location = new Location(Uri.base.resolve(uri.path.substring(1)),
- location.line, location.column);
- }
- LocatedMessage locatedMessage = templateUnspecified
- .withArguments(message)
- .withLocation(uri, node.fileOffset, noLength);
- FormattedMessage diagnosticMessage = locatedMessage.withFormatting(
- format(locatedMessage, Severity.warning,
- location: location, uriToSource: component.uriToSource),
- location.line,
- location.column,
- Severity.warning,
- []);
- actualMap
- .putIfAbsent(message, () => <FormattedMessage>[])
- .add(diagnosticMessage);
- }
-}
diff --git a/pkg/front_end/test/static_types/cfe_dynamic_test.dart b/pkg/front_end/test/static_types/cfe_dynamic_test.dart
index 06d08c1..260823d 100644
--- a/pkg/front_end/test/static_types/cfe_dynamic_test.dart
+++ b/pkg/front_end/test/static_types/cfe_dynamic_test.dart
@@ -2,27 +2,81 @@
// 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 'analysis_helper.dart';
+import 'package:front_end/src/testing/analysis_helper.dart';
+import 'verifying_analysis.dart';
-/// Filter function used to only analysis cfe source code.
-bool cfeOnly(Uri uri) {
- String text = '$uri';
- for (String path in [
- 'package:_fe_analyzer_shared/',
- 'package:kernel/',
- 'package:front_end/',
- ]) {
- if (text.startsWith(path)) {
- return true;
- }
- }
- return false;
-}
+import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:kernel/ast.dart';
Future<void> main(List<String> args) async {
- await run(Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart'),
- 'pkg/front_end/test/static_types/cfe_allowed.json',
+ await run(
+ cfeOnlyEntryPoints, 'pkg/front_end/test/static_types/cfe_allowed.json',
analyzedUrisFilter: cfeOnly,
verbose: args.contains('-v'),
generate: args.contains('-g'));
}
+
+Future<void> run(List<Uri> entryPoints, String allowedListPath,
+ {bool verbose = false,
+ bool generate = false,
+ bool Function(Uri uri)? analyzedUrisFilter}) async {
+ await runAnalysis(entryPoints,
+ (DiagnosticMessageHandler onDiagnostic, Component component) {
+ new DynamicVisitor(
+ onDiagnostic, component, allowedListPath, analyzedUrisFilter)
+ .run(verbose: verbose, generate: generate);
+ });
+}
+
+class DynamicVisitor extends VerifyingAnalysis {
+ // TODO(johnniwinther): Enable this when it is less noisy.
+ static const bool checkReturnTypes = false;
+
+ DynamicVisitor(DiagnosticMessageHandler onDiagnostic, Component component,
+ String? allowedListPath, UriFilter? analyzedUrisFilter)
+ : super(onDiagnostic, component, allowedListPath, analyzedUrisFilter);
+
+ @override
+ void visitDynamicGet(DynamicGet node) {
+ registerError(node, "Dynamic access of '${node.name}'.");
+ super.visitDynamicGet(node);
+ }
+
+ @override
+ void visitDynamicSet(DynamicSet node) {
+ registerError(node, "Dynamic update to '${node.name}'.");
+ super.visitDynamicSet(node);
+ }
+
+ @override
+ void visitDynamicInvocation(DynamicInvocation node) {
+ registerError(node, "Dynamic invocation of '${node.name}'.");
+ super.visitDynamicInvocation(node);
+ }
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {
+ if (checkReturnTypes && node.function.returnType is DynamicType) {
+ registerError(node, "Dynamic return type");
+ }
+ super.visitFunctionDeclaration(node);
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {
+ if (checkReturnTypes && node.function.returnType is DynamicType) {
+ registerError(node, "Dynamic return type");
+ }
+ super.visitFunctionExpression(node);
+ }
+
+ @override
+ void visitProcedure(Procedure node) {
+ if (checkReturnTypes &&
+ node.function.returnType is DynamicType &&
+ node.name.text != 'noSuchMethod') {
+ registerError(node, "Dynamic return type on $node");
+ }
+ super.visitProcedure(node);
+ }
+}
diff --git a/pkg/front_end/test/static_types/type_arguments.json b/pkg/front_end/test/static_types/type_arguments.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/pkg/front_end/test/static_types/type_arguments.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/pkg/front_end/test/static_types/type_arguments_test.dart b/pkg/front_end/test/static_types/type_arguments_test.dart
new file mode 100644
index 0000000..8998846
--- /dev/null
+++ b/pkg/front_end/test/static_types/type_arguments_test.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, 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:front_end/src/testing/analysis_helper.dart';
+import 'verifying_analysis.dart';
+
+import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:kernel/ast.dart';
+
+Future<void> main(List<String> args) async {
+ await run(cfeAndBackendsEntryPoints,
+ 'pkg/front_end/test/static_types/type_arguments.json',
+ analyzedUrisFilter: cfeAndBackends,
+ verbose: args.contains('-v'),
+ generate: args.contains('-g'));
+}
+
+Future<void> run(List<Uri> entryPoints, String allowedListPath,
+ {bool verbose = false,
+ bool generate = false,
+ bool Function(Uri uri)? analyzedUrisFilter}) async {
+ await runAnalysis(entryPoints,
+ (DiagnosticMessageHandler onDiagnostic, Component component) {
+ new TypeArgumentsVisitor(
+ onDiagnostic, component, allowedListPath, analyzedUrisFilter)
+ .run(verbose: verbose, generate: generate);
+ });
+}
+
+class TypeArgumentsVisitor extends VerifyingAnalysis {
+ TypeArgumentsVisitor(
+ DiagnosticMessageHandler onDiagnostic,
+ Component component,
+ String? allowedListPath,
+ UriFilter? analyzedUrisFilter)
+ : super(onDiagnostic, component, allowedListPath, analyzedUrisFilter);
+
+ @override
+ void visitInstanceInvocation(InstanceInvocation node) {
+ if (node.name.text == 'toList') {
+ TreeNode receiver = node.receiver;
+ if (receiver is InstanceInvocation &&
+ receiver.name.text == 'map' &&
+ receiver.arguments.types.length == 1) {
+ String astUri = 'package:kernel/ast.dart';
+ InterfaceType expressionType =
+ interface.createInterfaceType('Expression', uri: astUri);
+ InterfaceType statementType =
+ interface.createInterfaceType('Statement', uri: astUri);
+ InterfaceType assertStatementType =
+ interface.createInterfaceType('AssertStatement', uri: astUri);
+ InterfaceType variableDeclarationType =
+ interface.createInterfaceType('VariableDeclaration', uri: astUri);
+ DartType typeArgument = receiver.arguments.types.single;
+ if (interface.isSubtypeOf(typeArgument, expressionType) &&
+ typeArgument != expressionType) {
+ registerError(
+ node,
+ "map().toList() with type argument "
+ "${typeArgument} instead of ${expressionType}");
+ }
+ if (interface.isSubtypeOf(typeArgument, statementType)) {
+ if (interface.isSubtypeOf(typeArgument, assertStatementType)) {
+ // [AssertStatement] is used as an exclusive member of
+ // `InstanceCreation.asserts`.
+ if (typeArgument != assertStatementType) {
+ registerError(
+ node,
+ "map().toList() with type argument "
+ "${typeArgument} instead of ${assertStatementType}");
+ }
+ } else if (interface.isSubtypeOf(
+ typeArgument, variableDeclarationType)) {
+ // [VariableDeclaration] is used as an exclusive member of, for
+ // instance, `FunctionNode.positionalParameters`.
+ if (typeArgument != variableDeclarationType) {
+ registerError(
+ node,
+ "map().toList() with type argument "
+ "${typeArgument} instead of ${variableDeclarationType}");
+ }
+ } else if (typeArgument != statementType) {
+ registerError(
+ node,
+ "map().toList() with type argument "
+ "${typeArgument} instead of ${statementType}");
+ }
+ }
+ }
+ }
+ super.visitInstanceInvocation(node);
+ }
+}
diff --git a/pkg/front_end/test/static_types/verifying_analysis.dart b/pkg/front_end/test/static_types/verifying_analysis.dart
new file mode 100644
index 0000000..4305459
--- /dev/null
+++ b/pkg/front_end/test/static_types/verifying_analysis.dart
@@ -0,0 +1,185 @@
+// 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.
+
+import 'dart:convert' as json;
+import 'dart:io';
+
+import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
+import 'package:expect/expect.dart';
+import 'package:front_end/src/fasta/command_line_reporting.dart';
+import 'package:front_end/src/fasta/fasta_codes.dart';
+import 'package:front_end/src/testing/analysis_helper.dart';
+import 'package:kernel/ast.dart';
+
+/// [AnalysisVisitor] that supports tracking error/problem occurrences in an
+/// allowed list file.
+class VerifyingAnalysis extends AnalysisVisitor {
+ final String? _allowedListPath;
+
+ Map _expectedJson = {};
+
+ VerifyingAnalysis(DiagnosticMessageHandler onDiagnostic, Component component,
+ this._allowedListPath, UriFilter? analyzedUrisFilter)
+ : super(onDiagnostic, component, analyzedUrisFilter);
+
+ void run({bool verbose = false, bool generate = false}) {
+ if (!generate && _allowedListPath != null) {
+ File file = new File(_allowedListPath!);
+ if (file.existsSync()) {
+ try {
+ _expectedJson = json.jsonDecode(file.readAsStringSync());
+ } catch (e) {
+ Expect.fail('Error reading allowed list from $_allowedListPath: $e');
+ }
+ }
+ }
+ component.accept(this);
+ if (generate && _allowedListPath != null) {
+ Map<String, Map<String, int>> actualJson = {};
+ forEachMessage(
+ (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+ Map<String, int> map = {};
+ actualMessagesMap
+ .forEach((String message, List<FormattedMessage> actualMessages) {
+ map[message] = actualMessages.length;
+ });
+ actualJson[uri] = map;
+ });
+
+ new File(_allowedListPath!).writeAsStringSync(
+ new json.JsonEncoder.withIndent(' ').convert(actualJson));
+ return;
+ }
+
+ int errorCount = 0;
+ _expectedJson.forEach((uri, expectedMessages) {
+ Map<String, List<FormattedMessage>>? actualMessagesMap =
+ getMessagesForUri(uri);
+ if (actualMessagesMap == null) {
+ print("Error: Allowed-listing of uri '$uri' isn't used. "
+ "Remove it from the allowed-list.");
+ errorCount++;
+ } else {
+ expectedMessages.forEach((expectedMessage, expectedCount) {
+ List<FormattedMessage>? actualMessages =
+ actualMessagesMap[expectedMessage];
+ if (actualMessages == null) {
+ print("Error: Allowed-listing of message '$expectedMessage' "
+ "in uri '$uri' isn't used. Remove it from the allowed-list.");
+ errorCount++;
+ } else {
+ int actualCount = actualMessages.length;
+ if (actualCount != expectedCount) {
+ print("Error: Unexpected count of allowed message "
+ "'$expectedMessage' in uri '$uri'. "
+ "Expected $expectedCount, actual $actualCount:");
+ print(
+ '----------------------------------------------------------');
+ for (FormattedMessage message in actualMessages) {
+ onDiagnostic(message);
+ }
+ print(
+ '----------------------------------------------------------');
+ errorCount++;
+ }
+ }
+ });
+ actualMessagesMap
+ .forEach((String message, List<FormattedMessage> actualMessages) {
+ if (!expectedMessages.containsKey(message)) {
+ for (FormattedMessage message in actualMessages) {
+ onDiagnostic(message);
+ errorCount++;
+ }
+ }
+ });
+ }
+ });
+ forEachMessage(
+ (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+ if (!_expectedJson.containsKey(uri)) {
+ actualMessagesMap
+ .forEach((String message, List<FormattedMessage> actualMessages) {
+ for (FormattedMessage message in actualMessages) {
+ onDiagnostic(message);
+ errorCount++;
+ }
+ });
+ }
+ });
+ if (errorCount != 0) {
+ print('$errorCount error(s) found.');
+ print("""
+
+********************************************************************************
+* Unexpected code patterns found by test:
+*
+* ${relativizeUri(Platform.script)}
+*
+* Please address the reported errors, or, if the errors are as expected, run
+*
+* dart ${relativizeUri(Platform.script)} -g
+*
+* to update the expectation file.
+********************************************************************************
+""");
+ exit(-1);
+ }
+ if (verbose) {
+ forEachMessage(
+ (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+ actualMessagesMap
+ .forEach((String message, List<FormattedMessage> actualMessages) {
+ for (FormattedMessage message in actualMessages) {
+ // TODO(johnniwinther): It is unnecessarily complicated to just
+ // add ' (allowed)' to an existing message!
+ LocatedMessage locatedMessage = message.locatedMessage;
+ String newMessageText =
+ '${locatedMessage.messageObject.problemMessage} (allowed)';
+ message = locatedMessage.withFormatting(
+ format(
+ new LocatedMessage(
+ locatedMessage.uri,
+ locatedMessage.charOffset,
+ locatedMessage.length,
+ new Message(locatedMessage.messageObject.code,
+ problemMessage: newMessageText,
+ correctionMessage:
+ locatedMessage.messageObject.correctionMessage,
+ arguments: locatedMessage.messageObject.arguments)),
+ Severity.warning,
+ location: new Location(
+ message.uri!, message.line, message.column),
+ uriToSource: component.uriToSource),
+ message.line,
+ message.column,
+ Severity.warning,
+ []);
+ onDiagnostic(message);
+ }
+ });
+ });
+ } else {
+ int total = 0;
+ forEachMessage(
+ (String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
+ int count = 0;
+ actualMessagesMap
+ .forEach((String message, List<FormattedMessage> actualMessages) {
+ count += actualMessages.length;
+ });
+
+ print('${count} error(s) allowed in $uri');
+ total += count;
+ });
+ if (total > 0) {
+ print('${total} error(s) allowed in total.');
+ }
+ }
+ }
+
+ void registerError(TreeNode node, String message) {
+ registerMessage(node, message);
+ }
+}
diff --git a/pkg/front_end/tool/analyze.dart b/pkg/front_end/tool/analyze.dart
new file mode 100644
index 0000000..d729500
--- /dev/null
+++ b/pkg/front_end/tool/analyze.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, 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:front_end/src/testing/analysis_helper.dart';
+import 'package:kernel/kernel.dart';
+
+Future<void> main(List<String> arguments) async {
+ await runAnalysis(
+ cfeAndBackendsEntryPoints,
+ performGeneralAnalysis(cfeAndBackends,
+ (TreeNode node, AnalysisInterface interface) {
+ // Use 'analyze.dart' to perform advanced analysis/code search by
+ // replacing the "example analysis" with a custom analysis.
+
+ // Example analysis:
+ if (node is InstanceInvocation && node.name.text == 'toList') {
+ TreeNode receiver = node.receiver;
+ if (receiver is InstanceInvocation &&
+ receiver.name.text == 'map' &&
+ receiver.arguments.types.length == 1) {
+ InterfaceType expressionType = interface.createInterfaceType(
+ 'Expression',
+ uri: 'package:kernel/ast.dart');
+ DartType typeArgument = receiver.arguments.types.single;
+ if (interface.isSubtypeOf(typeArgument, expressionType) &&
+ typeArgument != expressionType) {
+ interface.reportMessage(node, "map().toList()");
+ }
+ }
+ }
+ }));
+}