[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>
This commit is contained in:
Johnni Winther 2022-04-20 12:28:18 +00:00 committed by Commit Bot
parent f26c0e2056
commit 6bf48988c8
8 changed files with 530 additions and 180 deletions

View file

@ -1952,7 +1952,7 @@ class IncrementalCompiler implements IncrementalKernelGenerator {
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 ??

View file

@ -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;
}

View file

@ -645,6 +645,7 @@ interior
interleaved
intermediate
internet
interop
interpolations
interrupted
intersects

View file

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

View file

@ -0,0 +1 @@
{}

View file

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

View file

@ -7,93 +7,21 @@ 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:front_end/src/testing/analysis_helper.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;
/// [AnalysisVisitor] that supports tracking error/problem occurrences in an
/// allowed list file.
class VerifyingAnalysis extends AnalysisVisitor {
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)));
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) {
@ -109,7 +37,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
component.accept(this);
if (generate && _allowedListPath != null) {
Map<String, Map<String, int>> actualJson = {};
_actualMessages.forEach(
forEachMessage(
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
Map<String, int> map = {};
actualMessagesMap
@ -127,7 +55,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
int errorCount = 0;
_expectedJson.forEach((uri, expectedMessages) {
Map<String, List<FormattedMessage>>? actualMessagesMap =
_actualMessages[uri];
getMessagesForUri(uri);
if (actualMessagesMap == null) {
print("Error: Allowed-listing of uri '$uri' isn't used. "
"Remove it from the allowed-list.");
@ -168,7 +96,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
});
}
});
_actualMessages.forEach(
forEachMessage(
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
if (!_expectedJson.containsKey(uri)) {
actualMessagesMap
@ -185,7 +113,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
print("""
********************************************************************************
* Unexpected dynamic invocations found by test:
* Unexpected code patterns found by test:
*
* ${relativizeUri(Platform.script)}
*
@ -199,7 +127,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
exit(-1);
}
if (verbose) {
_actualMessages.forEach(
forEachMessage(
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
actualMessagesMap
.forEach((String message, List<FormattedMessage> actualMessages) {
@ -234,7 +162,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
});
} else {
int total = 0;
_actualMessages.forEach(
forEachMessage(
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
int count = 0;
actualMessagesMap
@ -251,83 +179,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
}
}
@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);
registerMessage(node, message);
}
}

View file

@ -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()");
}
}
}
}));
}