Version 2.12.0-50.0.dev
Merge commit 'bb6714c051f177e0c8d1d33ad85de562d76abf62' into 'dev'
diff --git a/BUILD.gn b/BUILD.gn
index f1332bf..3962d40 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -68,6 +68,12 @@
}
}
+# A separate target and not included in group("runtime"). This way the target\
+# "runtime" does not get many executables extra as build output.
+group("run_ffi_unit_tests") {
+ deps = [ "runtime/bin/ffi_unit_test:run_ffi_unit_tests" ]
+}
+
group("runtime_kernel") {
if (targetting_fuchsia) {
# Fuchsia has run_vm_tests marked testonly.
diff --git a/DEPS b/DEPS
index f53c12e..4dc0fef 100644
--- a/DEPS
+++ b/DEPS
@@ -102,7 +102,7 @@
"chromedriver_tag": "83.0.4103.39",
"dartdoc_rev" : "6935dcd8f2d78cf1797e6365b4b71505e6579659",
- "ffi_rev": "a90bd424116fb6f416337db67425171f2dc4c98f",
+ "ffi_rev": "a5d4232cd38562c75a3ed847baa340e399538028",
"fixnum_rev": "16d3890c6dc82ca629659da1934e412292508bba",
"glob_rev": "e9f4e6b7ae8abe5071461cf8f47191bb19cf7ef6",
"html_rev": "22f17e97fedeacaa1e945cf84d8016284eed33a6",
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 4ee4034..98e3a03 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -17,6 +17,9 @@
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analysis_server/src/services/completion/token_details/token_detail_builder.dart';
+import 'package:analysis_server/src/services/completion/yaml/analysis_options_generator.dart';
+import 'package:analysis_server/src/services/completion/yaml/fix_data_generator.dart';
+import 'package:analysis_server/src/services/completion/yaml/pubspec_generator.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/exception/exception.dart';
@@ -145,6 +148,25 @@
request.replacementOffset, request.replacementLength, suggestions);
}
+ /// Return the suggestions that should be presented in the YAML [file] at the
+ /// given [offset].
+ List<CompletionSuggestion> computeYamlSuggestions(String file, int offset) {
+ var provider = server.resourceProvider;
+ if (AnalysisEngine.isAnalysisOptionsFileName(file)) {
+ var generator = AnalysisOptionsGenerator(provider);
+ return generator.getSuggestions(file, offset);
+ }
+ var fileName = provider.pathContext.basename(file);
+ if (fileName == AnalysisEngine.PUBSPEC_YAML_FILE) {
+ var generator = PubspecGenerator(provider);
+ return generator.getSuggestions(file, offset);
+ } else if (fileName == AnalysisEngine.FIX_DATA_FILE) {
+ var generator = FixDataGenerator(provider);
+ return generator.getSuggestions(file, offset);
+ }
+ return <CompletionSuggestion>[];
+ }
+
/// Process a `completion.getSuggestionDetails` request.
void getSuggestionDetails(Request request) async {
var params = CompletionGetSuggestionDetailsParams.fromRequest(request);
@@ -297,6 +319,24 @@
if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
return;
}
+ if (file.endsWith('.yaml')) {
+ // Return the response without results.
+ var completionId = (_nextCompletionId++).toString();
+ server.sendResponse(CompletionGetSuggestionsResult(completionId)
+ .toResponse(request.id));
+ // Send a notification with results.
+ sendCompletionNotification(
+ completionId,
+ 0, // replacementOffset
+ 0, // replacementLength,
+ computeYamlSuggestions(file, offset),
+ null,
+ null,
+ null,
+ null,
+ );
+ return;
+ }
var resolvedUnit = await server.getResolvedUnit(file);
server.requestStatistics?.addItemTimeNow(request, 'resolvedUnit');
diff --git a/pkg/analysis_server/lib/src/server/driver.dart b/pkg/analysis_server/lib/src/server/driver.dart
index 734177d..db988fa 100644
--- a/pkg/analysis_server/lib/src/server/driver.dart
+++ b/pkg/analysis_server/lib/src/server/driver.dart
@@ -38,176 +38,12 @@
import 'package:telemetry/crash_reporting.dart';
import 'package:telemetry/telemetry.dart' as telemetry;
-/// Commandline argument parser. (Copied from analyzer/lib/options.dart)
-/// TODO(pquitslund): replaces with a simple [ArgParser] instance
-/// when the args package supports ignoring unrecognized
-/// options/flags (https://github.com/dart-lang/args/issues/9).
-/// TODO(devoncarew): Consider removing the ability to support unrecognized
-/// flags for the analysis server.
-class CommandLineParser {
- final List<String> _knownFlags;
- final ArgParser _parser;
-
- /// Creates a new command line parser
- CommandLineParser()
- : _knownFlags = <String>[],
- _parser = ArgParser(allowTrailingOptions: true);
-
- ArgParser get parser => _parser;
-
- /// Defines a flag.
- /// See [ArgParser.addFlag()].
- void addFlag(String name,
- {String abbr,
- String help,
- bool defaultsTo = false,
- bool negatable = true,
- void Function(bool value) callback,
- bool hide = false}) {
- _knownFlags.add(name);
- _parser.addFlag(name,
- abbr: abbr,
- help: help,
- defaultsTo: defaultsTo,
- negatable: negatable,
- callback: callback,
- hide: hide);
- }
-
- /// Defines an option that takes multiple values.
- /// See [ArgParser.addMultiOption].
- void addMultiOption(String name,
- {String abbr,
- String help,
- String valueHelp,
- Iterable<String> allowed,
- Map<String, String> allowedHelp,
- Iterable<String> defaultsTo,
- void Function(List<String> values) callback,
- bool splitCommas = true,
- bool hide = false}) {
- _knownFlags.add(name);
- _parser.addMultiOption(name,
- abbr: abbr,
- help: help,
- valueHelp: valueHelp,
- allowed: allowed,
- allowedHelp: allowedHelp,
- defaultsTo: defaultsTo,
- callback: callback,
- splitCommas: splitCommas,
- hide: hide);
- }
-
- /// Defines a value-taking option.
- /// See [ArgParser.addOption()].
- void addOption(String name,
- {String abbr,
- String help,
- String valueHelp,
- List<String> allowed,
- Map<String, String> allowedHelp,
- String defaultsTo,
- void Function(Object) callback,
- bool hide = false}) {
- _knownFlags.add(name);
- _parser.addOption(name,
- abbr: abbr,
- help: help,
- valueHelp: valueHelp,
- allowed: allowed,
- allowedHelp: allowedHelp,
- defaultsTo: defaultsTo,
- callback: callback,
- hide: hide);
- }
-
- /// Generates a string displaying usage information for the defined options.
- /// See [ArgParser.usage].
- String getUsage() => _parser.usage;
-
- /// Parses [args], a list of command-line arguments, matches them against the
- /// flags and options defined by this parser, and returns the result. The
- /// values of any defined variables are captured in the given map.
- /// See [ArgParser].
- ArgResults parse(List<String> args, Map<String, String> definedVariables) =>
- _parser.parse(
- _filterUnknowns(parseDefinedVariables(args, definedVariables)));
-
- List<String> parseDefinedVariables(
- List<String> args, Map<String, String> definedVariables) {
- var count = args.length;
- var remainingArgs = <String>[];
- for (var i = 0; i < count; i++) {
- var arg = args[i];
- if (arg == '--') {
- while (i < count) {
- remainingArgs.add(args[i++]);
- }
- } else if (arg.startsWith('-D')) {
- definedVariables[arg.substring(2)] = args[++i];
- } else {
- remainingArgs.add(arg);
- }
- }
- return remainingArgs;
- }
-
- List<String> _filterUnknowns(List<String> args) {
- // TODO(devoncarew): Consider dropping support for the
- // --ignore-unrecognized-flags option.
-
- // Only filter args if the ignore flag is specified.
- if (args.contains('--ignore-unrecognized-flags')) {
- // Filter all unrecognized flags and options.
- var filtered = <String>[];
- for (var i = 0; i < args.length; ++i) {
- var arg = args[i];
- if (arg.startsWith('--') && arg.length > 2) {
- var option = arg.substring(2);
- // remove any leading 'no-'
- if (option.startsWith('no-')) {
- option = option.substring(3);
- }
- // strip the last '=value'
- var equalsOffset = option.lastIndexOf('=');
- if (equalsOffset != -1) {
- option = option.substring(0, equalsOffset);
- }
- // check the option
- if (!_knownFlags.contains(option)) {
- //"eat" params by advancing to the next flag/option
- i = _getNextFlagIndex(args, i);
- } else {
- filtered.add(arg);
- }
- } else {
- filtered.add(arg);
- }
- }
-
- return filtered;
- } else {
- return args;
- }
- }
-
- int _getNextFlagIndex(List<String> args, int i) {
- for (; i < args.length; ++i) {
- if (args[i].startsWith('--')) {
- return i;
- }
- }
- return i;
- }
-}
-
/// The [Driver] class represents a single running instance of the analysis
/// server application. It is responsible for parsing command line options
/// and starting the HTTP and/or stdio servers.
class Driver implements ServerStarter {
/// The name of the application that is used to start a server.
- static const BINARY_NAME = 'server';
+ static const BINARY_NAME = 'analysis_server';
/// The name of the option used to set the identifier for the client.
static const String CLIENT_ID = 'client-id';
@@ -286,7 +122,7 @@
@override
void start(List<String> arguments, [SendPort sendPort]) {
var parser = _createArgParser();
- var results = parser.parse(arguments, <String, String>{});
+ var results = parser.parse(arguments);
var analysisServerOptions = AnalysisServerOptions();
analysisServerOptions.newAnalysisDriverLog =
@@ -370,7 +206,7 @@
}
if (results[HELP_OPTION]) {
- _printUsage(parser.parser, analytics, fromHelp: true);
+ _printUsage(parser, analytics, fromHelp: true);
return null;
}
@@ -418,7 +254,7 @@
} on FormatException {
print('Invalid port number: ${results[PORT_OPTION]}');
print('');
- _printUsage(parser.parser, analytics);
+ _printUsage(parser, analytics);
exitCode = 1;
return null;
}
@@ -450,7 +286,7 @@
void startAnalysisServer(
ArgResults results,
AnalysisServerOptions analysisServerOptions,
- CommandLineParser parser,
+ ArgParser parser,
DartSdkManager dartSdkManager,
CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder,
InstrumentationService instrumentationService,
@@ -639,8 +475,8 @@
}
/// Create and return the parser used to parse the command-line arguments.
- CommandLineParser _createArgParser() {
- var parser = CommandLineParser();
+ ArgParser _createArgParser() {
+ var parser = ArgParser();
parser.addFlag(HELP_OPTION,
abbr: 'h', negatable: false, help: 'Print this usage information.');
parser.addOption(CLIENT_ID,
@@ -716,6 +552,8 @@
parser.addFlag('enable-instrumentation', hide: true);
// Removed 11/12/2020.
parser.addOption('file-read-mode', hide: true);
+ // Removed 11/12/2020.
+ parser.addFlag('ignore-unrecognized-flags', hide: true);
// Removed 11/8/2020.
parser.addFlag('preview-dart-2', hide: true);
// Removed 11/12/2020.
@@ -752,8 +590,11 @@
}
/// Print information about how to use the server.
- void _printUsage(ArgParser parser, telemetry.Analytics analytics,
- {bool fromHelp = false}) {
+ void _printUsage(
+ ArgParser parser,
+ telemetry.Analytics analytics, {
+ bool fromHelp = false,
+ }) {
print('Usage: $BINARY_NAME [flags]');
print('');
print('Supported flags are:');
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
new file mode 100644
index 0000000..7f9228a
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
@@ -0,0 +1,67 @@
+// 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 'package:analysis_server/src/protocol_server.dart';
+import 'package:analysis_server/src/services/completion/yaml/producer.dart';
+import 'package:analysis_server/src/services/completion/yaml/yaml_completion_generator.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/lint/registry.dart';
+import 'package:analyzer/src/task/options.dart';
+
+/// A completion generator that can produce completion suggestions for analysis
+/// options files.
+class AnalysisOptionsGenerator extends YamlCompletionGenerator {
+ /// The producer representing the known valid structure of an analysis options
+ /// file.
+ // TODO(brianwilkerson) We need to support multiple valid formats.
+ // For example, the lint rules can either be a list or a map, but we only
+ // suggest list items.
+ static const MapProducer analysisOptionsProducer = MapProducer({
+ AnalyzerOptions.analyzer: MapProducer({
+ AnalyzerOptions.enableExperiment: EmptyProducer(),
+ AnalyzerOptions.errors: EmptyProducer(),
+ AnalyzerOptions.exclude: EmptyProducer(),
+ AnalyzerOptions.language: MapProducer({
+ AnalyzerOptions.strictInference: EmptyProducer(),
+ AnalyzerOptions.strictRawTypes: EmptyProducer(),
+ }),
+ AnalyzerOptions.optionalChecks: MapProducer({
+ AnalyzerOptions.chromeOsManifestChecks: EmptyProducer(),
+ }),
+ AnalyzerOptions.plugins: EmptyProducer(),
+ AnalyzerOptions.strong_mode: MapProducer({
+ AnalyzerOptions.declarationCasts: EmptyProducer(),
+ AnalyzerOptions.implicitCasts: EmptyProducer(),
+ AnalyzerOptions.implicitDynamic: EmptyProducer(),
+ }),
+ }),
+ // TODO(brianwilkerson) Create a producer to produce `package:` URIs.
+ AnalyzerOptions.include: EmptyProducer(),
+ // TODO(brianwilkerson) Create constants for 'linter' and 'rules'.
+ 'linter': MapProducer({
+ 'rules': ListProducer(LintRuleProducer()),
+ }),
+ });
+
+ /// Initialize a newly created suggestion generator for analysis options
+ /// files.
+ AnalysisOptionsGenerator(ResourceProvider resourceProvider)
+ : super(resourceProvider);
+
+ @override
+ Producer get topLevelProducer => analysisOptionsProducer;
+}
+
+class LintRuleProducer extends Producer {
+ /// Initialize a location whose valid values are the names of the registered
+ /// lint rules.
+ const LintRuleProducer();
+
+ @override
+ Iterable<CompletionSuggestion> suggestions() sync* {
+ for (var rule in Registry.ruleRegistry.rules) {
+ yield identifier(rule.name);
+ }
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/fix_data_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/fix_data_generator.dart
new file mode 100644
index 0000000..4fd54bf
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/fix_data_generator.dart
@@ -0,0 +1,71 @@
+// 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 'package:analysis_server/src/services/completion/yaml/producer.dart';
+import 'package:analysis_server/src/services/completion/yaml/yaml_completion_generator.dart';
+import 'package:analyzer/file_system/file_system.dart';
+
+/// A completion generator that can produce completion suggestions for fix data
+/// files.
+class FixDataGenerator extends YamlCompletionGenerator {
+ /// The producer representing the known valid structure of a fix data file.
+ static const MapProducer fixDataProducer = MapProducer({
+ 'version': EmptyProducer(),
+ 'transforms': ListProducer(MapProducer({
+ 'title': EmptyProducer(),
+ 'date': EmptyProducer(),
+ 'bulkApply': BooleanProducer(),
+ 'element': MapProducer({
+ // TODO(brianwilkerson) Support suggesting uris.
+ 'uris': EmptyProducer(),
+ 'class': EmptyProducer(),
+ 'constant': EmptyProducer(),
+ 'constructor': EmptyProducer(),
+ 'enum': EmptyProducer(),
+ 'extension': EmptyProducer(),
+ 'field': EmptyProducer(),
+ 'function': EmptyProducer(),
+ 'getter': EmptyProducer(),
+ 'method': EmptyProducer(),
+ 'mixin': EmptyProducer(),
+ 'setter': EmptyProducer(),
+ 'typedef': EmptyProducer(),
+ 'variable': EmptyProducer(),
+ 'inClass': EmptyProducer(),
+ 'inEnum': EmptyProducer(),
+ 'inExtension': EmptyProducer(),
+ 'inMixin': EmptyProducer(),
+ }),
+ 'changes': ListProducer(MapProducer({
+ // TODO(brianwilkerson) Create a way to tailor the list of additional
+ // keys based on the kind when a kind has already been provided.
+ 'kind': EnumProducer([
+ 'addParameter',
+ 'addTypeParameter',
+ 'removeParameter',
+ 'rename',
+ 'renameParameter',
+ ]),
+ 'index': EmptyProducer(),
+ 'name': EmptyProducer(),
+ 'style': EmptyProducer(),
+ 'argumentValue': MapProducer({
+ 'expression': EmptyProducer(),
+ 'statements': EmptyProducer(),
+ // TODO(brianwilkerson) Figure out how to support 'variables'.
+ 'variables': EmptyProducer(),
+ }),
+ 'extends': EmptyProducer(),
+ 'oldName': EmptyProducer(),
+ 'newName': EmptyProducer(),
+ })),
+ })),
+ });
+
+ /// Initialize a newly created suggestion generator for fix data files.
+ FixDataGenerator(ResourceProvider resourceProvider) : super(resourceProvider);
+
+ @override
+ Producer get topLevelProducer => fixDataProducer;
+}
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/producer.dart b/pkg/analysis_server/lib/src/services/completion/yaml/producer.dart
new file mode 100644
index 0000000..834adac
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/producer.dart
@@ -0,0 +1,127 @@
+// 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 'package:analysis_server/src/protocol_server.dart';
+import 'package:analyzer/file_system/file_system.dart';
+
+/// An object that represents the location of a Boolean value.
+class BooleanProducer extends Producer {
+ /// Initialize a location whose valid values are Booleans.
+ const BooleanProducer();
+
+ @override
+ Iterable<CompletionSuggestion> suggestions() sync* {
+ yield identifier('true');
+ yield identifier('false');
+ }
+}
+
+/// An object that represents the location of an arbitrary value. They serve as
+/// placeholders when there are no reasonable suggestions for a given location.
+class EmptyProducer extends Producer {
+ /// Initialize a location whose valid values are arbitrary.
+ const EmptyProducer();
+
+ @override
+ Iterable<CompletionSuggestion> suggestions() sync* {
+ // Returns nothing.
+ }
+}
+
+/// An object that represents the location of a value from a finite set of
+/// choices.
+class EnumProducer extends Producer {
+ /// The list of valid values at this location.
+ final List<String> values;
+
+ /// Initialize a location whose valid values are in the list of [values].
+ const EnumProducer(this.values);
+
+ @override
+ Iterable<CompletionSuggestion> suggestions() sync* {
+ for (var value in values) {
+ yield identifier(value);
+ }
+ }
+}
+
+/// An object that represents the location of a possibly relative file path.
+class FilePathProducer extends Producer {
+ /// The resource provider used to access the content of the file in which
+ /// completion was requested.
+ final ResourceProvider provider;
+
+ /// Initialize a location whose valid values are file paths.
+ FilePathProducer(this.provider);
+
+ @override
+ Iterable<CompletionSuggestion> suggestions() sync* {
+ // TODO(brianwilkerson) Implement this.
+ }
+}
+
+/// An object that represents the location of an element in a list.
+class ListProducer extends Producer {
+ /// The producer used to produce suggestions for an element of the list.
+ final Producer element;
+
+ /// Initialize a location whose valid values are determined by the [element]
+ /// producer.
+ const ListProducer(this.element);
+
+ @override
+ Iterable<CompletionSuggestion> suggestions() sync* {
+ for (var suggestion in element.suggestions()) {
+ // TODO(brianwilkerson) Consider prepending the suggestion with a hyphen
+ // when the current node isn't already preceded by a hyphen. The
+ // cleanest way to do this is probably to access the [element] producer
+ // in the place where we're choosing a producer in that situation.
+ // suggestion.completion = '- ${suggestion.completion}';
+ yield suggestion;
+ }
+ }
+}
+
+/// An object that represents the location of the keys in a map.
+class MapProducer extends Producer {
+ /// A table from the value of a key to the producer used to make suggestions
+ /// for the value following the key.
+ final Map<String, Producer> children;
+
+ /// Initialize a location whose valid values are the keys of a map as encoded
+ /// by the map of [children].
+ const MapProducer(this.children);
+
+ @override
+ Iterable<CompletionSuggestion> suggestions() sync* {
+ for (var entry in children.entries) {
+ if (entry.value is ListProducer) {
+ yield identifier('${entry.key}:');
+ } else {
+ yield identifier('${entry.key}: ');
+ }
+ }
+ }
+}
+
+/// An object that represents a specific location in the structure of the valid
+/// YAML representation and can produce completion suggestions appropriate for
+/// that location.
+abstract class Producer {
+ /// Initialize a newly created instance of this class.
+ const Producer();
+
+ /// A utility method used to create a suggestion for the [identifier].
+ CompletionSuggestion identifier(String identifier) => CompletionSuggestion(
+ CompletionSuggestionKind.IDENTIFIER,
+ 1000,
+ identifier,
+ identifier.length,
+ 0,
+ false,
+ false);
+
+ /// Return the completion suggestions appropriate to this location.
+ Iterable<CompletionSuggestion> suggestions();
+}
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/pubspec_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/pubspec_generator.dart
new file mode 100644
index 0000000..e38bddc
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/pubspec_generator.dart
@@ -0,0 +1,39 @@
+// 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 'package:analysis_server/src/services/completion/yaml/producer.dart';
+import 'package:analysis_server/src/services/completion/yaml/yaml_completion_generator.dart';
+import 'package:analyzer/file_system/file_system.dart';
+
+/// A completion generator that can produce completion suggestions for pubspec
+/// files.
+class PubspecGenerator extends YamlCompletionGenerator {
+ /// The producer representing the known valid structure of a pubspec file.
+ static const MapProducer pubspecProducer = MapProducer({
+ 'name': EmptyProducer(),
+ 'version': EmptyProducer(),
+ 'description': EmptyProducer(),
+ 'homepage': EmptyProducer(),
+ 'repository': EmptyProducer(),
+ 'issue_tracker': EmptyProducer(),
+ 'documentation': EmptyProducer(),
+ 'executables': EmptyProducer(),
+ 'publish_to': EmptyProducer(),
+ 'environment': MapProducer({
+ 'flutter': EmptyProducer(),
+ 'sdk': EmptyProducer(),
+ }),
+ 'dependencies': EmptyProducer(),
+ 'dev_dependencies': EmptyProducer(),
+ // TODO(brianwilkerson) Suggest names already listed under 'dependencies'
+ // and 'dev_dependencies'.
+ 'dependency_overrides': EmptyProducer(),
+ });
+
+ /// Initialize a newly created suggestion generator for pubspec files.
+ PubspecGenerator(ResourceProvider resourceProvider) : super(resourceProvider);
+
+ @override
+ Producer get topLevelProducer => pubspecProducer;
+}
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/yaml_completion_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/yaml_completion_generator.dart
new file mode 100644
index 0000000..3d0b64c
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/yaml_completion_generator.dart
@@ -0,0 +1,224 @@
+// 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 'package:analysis_server/src/protocol_server.dart';
+import 'package:analysis_server/src/services/completion/yaml/producer.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:yaml/yaml.dart';
+
+/// A completion generator that can produce completion suggestions for files
+/// containing a YAML structure.
+abstract class YamlCompletionGenerator {
+ /// The resource provider used to access the content of the file in which
+ /// completion was requested.
+ final ResourceProvider resourceProvider;
+
+ /// Initialize a newly created generator to use the [resourceProvider] to
+ /// access the content of the file in which completion was requested.
+ YamlCompletionGenerator(this.resourceProvider);
+
+ /// Return the producer used to produce suggestions at the top-level of the
+ /// file.
+ Producer get topLevelProducer;
+
+ /// Return the completion suggestions appropriate for the given [offset] in
+ /// the file at the given [filePath].
+ List<CompletionSuggestion> getSuggestions(String filePath, int offset) {
+ var file = resourceProvider.getFile(filePath);
+ String content;
+ try {
+ content = file.readAsStringSync();
+ } on FileSystemException {
+ // If the file doesn't exist or can't be read, then there are no
+ // suggestions.
+ return const <CompletionSuggestion>[];
+ }
+ var root = _parseYaml(content);
+ if (root == null) {
+ // If the contents can't be parsed, then there are no suggestions.
+ return const <CompletionSuggestion>[];
+ }
+ var path = _pathToOffset(root, offset);
+ var completionNode = path.last;
+ if (completionNode is YamlScalar) {
+ var value = completionNode.value;
+ if (value == null) {
+ return getSuggestionsForPath(path, offset);
+ } else if (value is String && completionNode.style == ScalarStyle.PLAIN) {
+ return getSuggestionsForPath(path, offset);
+ }
+ } else {
+ return getSuggestionsForPath(path, offset);
+ }
+ // There are no completions at the given location.
+ return const <CompletionSuggestion>[];
+ }
+
+ /// Given a [path] to the node in which completions are being requested and
+ /// the offset of the cursor, return the completions appropriate at that
+ /// location.
+ List<CompletionSuggestion> getSuggestionsForPath(
+ List<YamlNode> path, int offset) {
+ var producer = _producerForPath(path);
+ if (producer == null) {
+ return const <CompletionSuggestion>[];
+ }
+ var invalidSuggestions = _siblingsOnPath(path);
+ var suggestions = <CompletionSuggestion>[];
+ for (var suggestion in producer.suggestions()) {
+ if (!invalidSuggestions.contains(suggestion.completion)) {
+ suggestions.add(suggestion);
+ }
+ }
+ return suggestions;
+ }
+
+ /// Return the result of parsing the file [content] into a YAML node.
+ YamlNode _parseYaml(String content) {
+ try {
+ return loadYamlNode(content);
+ } on YamlException {
+ // If the file can't be parsed, then fall through to return `null`.
+ }
+ return null;
+ }
+
+ /// Return a list containing the node containing the [offset] and all of the
+ /// nodes between that and the [root] node. The root node is first in the list
+ /// and the node containing the offset is the last element in the list.
+ List<YamlNode> _pathToOffset(YamlNode root, int offset) {
+ var path = <YamlNode>[];
+ var node = root;
+ while (node != null) {
+ path.add(node);
+ node = node.childContainingOffset(offset);
+ }
+ return path;
+ }
+
+ /// Return the producer that should be used to produce completion suggestions
+ /// for the last node in the node [path].
+ Producer _producerForPath(List<YamlNode> path) {
+ var producer = topLevelProducer;
+ for (var i = 0; i < path.length - 1; i++) {
+ var node = path[i];
+ if (node is YamlMap && producer is MapProducer) {
+ var key = node.keyAtValue(path[i + 1]);
+ if (key is YamlScalar) {
+ producer = (producer as MapProducer).children[key.value];
+ } else {
+ return null;
+ }
+ } else if (node is YamlList && producer is ListProducer) {
+ producer = (producer as ListProducer).element;
+ } else {
+ return producer;
+ }
+ }
+ return producer;
+ }
+
+ /// Return a list of the suggestions that should not be suggested because they
+ /// are already in the structure.
+ List<String> _siblingsOnPath(List<YamlNode> path) {
+ List<String> siblingsInList(YamlList list, YamlNode currentElement) {
+ var siblings = <String>[];
+ for (var element in list.nodes) {
+ if (element != currentElement &&
+ element is YamlScalar &&
+ element.value is String) {
+ siblings.add(element.value);
+ }
+ }
+ return siblings;
+ }
+
+ List<String> siblingsInMap(YamlMap map, YamlNode currentKey) {
+ var siblings = <String>[];
+ for (var key in map.nodes.keys) {
+ if (key != currentKey && key is YamlScalar && key.value is String) {
+ siblings.add('${key.value}: ');
+ }
+ }
+ return siblings;
+ }
+
+ var length = path.length;
+ if (length < 2) {
+ return const <String>[];
+ }
+ var node = path[length - 1];
+ if (node is YamlList) {
+ return siblingsInList(node, null);
+ } else if (node is YamlMap) {
+ return siblingsInMap(node, null);
+ }
+ var parent = path[length - 2];
+ if (parent is YamlList) {
+ return siblingsInList(parent, node);
+ } else if (parent is YamlMap) {
+ return siblingsInMap(parent, node);
+ }
+ return const <String>[];
+ }
+}
+
+extension on YamlMap {
+ /// Return the node representing the key that corresponds to the value
+ /// represented by the [value] node.
+ YamlNode keyAtValue(YamlNode value) {
+ for (var entry in nodes.entries) {
+ if (entry.value == value) {
+ return entry.key;
+ }
+ }
+ return null;
+ }
+}
+
+extension on YamlNode {
+ /// Return the child of this node that contains the given [offset], or `null`
+ /// if none of the children contains the offset.
+ YamlNode childContainingOffset(int offset) {
+ var node = this;
+ if (node is YamlList) {
+ for (var element in node.nodes) {
+ if (element.containsOffset(offset)) {
+ return element;
+ }
+ }
+ for (var element in node.nodes) {
+ if (element is YamlScalar && element.value == null) {
+ // TODO(brianwilkerson) Testing for a null value probably gets
+ // confused when there are multiple null values.
+ return element;
+ }
+ }
+ } else if (node is YamlMap) {
+ for (var entry in node.nodes.entries) {
+ if ((entry.key as YamlNode).containsOffset(offset)) {
+ return entry.key;
+ }
+ var value = entry.value;
+ if (value.containsOffset(offset) ||
+ (value is YamlScalar && value.value == null)) {
+ // TODO(brianwilkerson) Testing for a null value probably gets
+ // confused when there are multiple null values or when there is a
+ // null value before the node that actually contains the offset.
+ return entry.value;
+ }
+ }
+ }
+ return null;
+ }
+
+ /// Return `true` if this node contains the given [offset].
+ bool containsOffset(int offset) {
+ // TODO(brianwilkerson) Nodes at the end of the file contain any trailing
+ // whitespace. This needs to be accounted for, here or elsewhere.
+ var nodeOffset = span.start.offset;
+ var nodeEnd = nodeOffset + span.length;
+ return nodeOffset <= offset && offset <= nodeEnd;
+ }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/add_async.dart b/pkg/analysis_server/lib/src/services/correction/dart/add_async.dart
index ef18db3..59739f3 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/add_async.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/add_async.dart
@@ -5,24 +5,92 @@
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
class AddAsync extends CorrectionProducer {
+ /// A flag indicating whether this producer is producing a fix in the case
+ /// where a function is missing a return at the end.
+ final bool isForMissingReturn;
+
+ /// Initialize a newly created producer.
+ AddAsync(this.isForMissingReturn);
+
@override
FixKind get fixKind => DartFixKind.ADD_ASYNC;
@override
Future<void> compute(ChangeBuilder builder) async {
- var body = node.thisOrAncestorOfType<FunctionBody>();
- if (body != null && body.keyword == null) {
- var typeProvider = this.typeProvider;
- await builder.addDartFileEdit(file, (builder) {
- builder.convertFunctionFromSyncToAsync(body, typeProvider);
- });
+ if (isForMissingReturn) {
+ var parent = node.parent;
+ DartType returnType;
+ FunctionBody body;
+ if (parent is FunctionDeclaration) {
+ returnType = parent.returnType.type;
+ body = parent.functionExpression.body;
+ } else if (parent is MethodDeclaration) {
+ returnType = parent.returnType.type;
+ body = parent.body;
+ }
+ if (body == null) {
+ return;
+ }
+ if (_isFutureVoid(returnType) && _hasNoReturns(body)) {
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addSimpleInsertion(body.offset, 'async ');
+ });
+ }
+ } else {
+ var body = node.thisOrAncestorOfType<FunctionBody>();
+ if (body != null && body.keyword == null) {
+ var typeProvider = this.typeProvider;
+ await builder.addDartFileEdit(file, (builder) {
+ builder.convertFunctionFromSyncToAsync(body, typeProvider);
+ });
+ }
}
}
+ /// Return `true` if there are no return statements in the given function
+ /// [body].
+ bool _hasNoReturns(FunctionBody body) {
+ var finder = _ReturnFinder();
+ body.accept(finder);
+ return !finder.foundReturn;
+ }
+
+ /// Return `true` if the [type] is `Future<void>`.
+ bool _isFutureVoid(DartType type) {
+ if (type is InterfaceType && type.isDartAsyncFuture) {
+ return type.typeArguments[0].isVoid;
+ }
+ return false;
+ }
+
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
- static AddAsync newInstance() => AddAsync();
+ static AddAsync missingReturn() => AddAsync(true);
+
+ /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+ static AddAsync newInstance() => AddAsync(false);
+}
+
+/// An AST visitor used to find return statements in function bodies.
+class _ReturnFinder extends RecursiveAstVisitor<void> {
+ /// A flag indicating whether a return statement was visited.
+ bool foundReturn = false;
+
+ /// Initialize a newly created visitor.
+ _ReturnFinder();
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {
+ // Return statements within closures aren't counted.
+ }
+
+ @override
+ void visitReturnStatement(ReturnStatement node) {
+ foundReturn = true;
+ }
}
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_set_literal.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_set_literal.dart
index f374346..f6a8db3 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_set_literal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_set_literal.dart
@@ -178,6 +178,11 @@
parent2.type?.type?.element == typeProvider.setElement) {
return true;
}
+ } else if (parent.parent is InvocationExpression) {
+ var parameterElement = creation.staticParameterElement;
+ if (parameterElement?.type?.element == typeProvider.setElement) {
+ return true;
+ }
}
return _hasUnambiguousElement(creation);
}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 881714d..5995aaf 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -646,6 +646,9 @@
CompileTimeErrorCode.AWAIT_IN_WRONG_CONTEXT: [
AddAsync.newInstance,
],
+ CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY: [
+ AddAsync.missingReturn,
+ ],
CompileTimeErrorCode.CAST_TO_NON_TYPE: [
ChangeTo.classOrMixin,
CreateClass.newInstance,
@@ -959,6 +962,9 @@
HintCode.MISSING_REQUIRED_PARAM_WITH_DETAILS: [
AddMissingRequiredArgument.newInstance,
],
+ HintCode.MISSING_RETURN: [
+ AddAsync.missingReturn,
+ ],
HintCode.NULLABLE_TYPE_IN_CATCH_CLAUSE: [
RemoveQuestionMark.newInstance,
],
diff --git a/pkg/analysis_server/test/src/services/completion/test_all.dart b/pkg/analysis_server/test/src/services/completion/test_all.dart
index 6055c7b..19d31b3 100644
--- a/pkg/analysis_server/test/src/services/completion/test_all.dart
+++ b/pkg/analysis_server/test/src/services/completion/test_all.dart
@@ -6,10 +6,12 @@
import 'dart/test_all.dart' as dart;
import 'filtering/test_all.dart' as filtering;
+import 'yaml/test_all.dart' as yaml;
void main() {
defineReflectiveSuite(() {
dart.main();
filtering.main();
+ yaml.main();
}, name: 'completion');
}
diff --git a/pkg/analysis_server/test/src/services/completion/yaml/analysis_options_generator_test.dart b/pkg/analysis_server/test/src/services/completion/yaml/analysis_options_generator_test.dart
new file mode 100644
index 0000000..c86dc28
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/completion/yaml/analysis_options_generator_test.dart
@@ -0,0 +1,155 @@
+// 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 'package:analysis_server/src/services/completion/yaml/analysis_options_generator.dart';
+import 'package:analyzer/src/task/options.dart';
+import 'package:linter/src/rules.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'yaml_generator_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(AnalysisOptionsGeneratorTest);
+ });
+}
+
+@reflectiveTest
+class AnalysisOptionsGeneratorTest extends YamlGeneratorTest {
+ @override
+ String get fileName => 'analysis_options.yaml';
+
+ @override
+ AnalysisOptionsGenerator get generator =>
+ AnalysisOptionsGenerator(resourceProvider);
+
+ void test_analyzer() {
+ getCompletions('''
+analyzer:
+ ^
+''');
+ assertSuggestion('${AnalyzerOptions.enableExperiment}: ');
+ }
+
+ void test_empty() {
+ getCompletions('^');
+ assertSuggestion('${AnalyzerOptions.analyzer}: ');
+ assertSuggestion('${AnalyzerOptions.include}: ');
+ // TODO(brianwilkerson) Replace this with a constant.
+ assertSuggestion('linter: ');
+ }
+
+ void test_linter() {
+ getCompletions('''
+linter:
+ ^
+''');
+ assertSuggestion('rules:');
+ }
+
+ void test_linter_rules() {
+ registerLintRules();
+ getCompletions('''
+linter:
+ rules:
+ ^
+''');
+ assertSuggestion('annotate_overrides');
+ }
+
+ void test_linter_rules_listItem_first() {
+ registerLintRules();
+ getCompletions('''
+linter:
+ rules:
+ - ^
+ - annotate_overrides
+''');
+ assertSuggestion('avoid_as');
+ assertNoSuggestion('annotate_overrides');
+ }
+
+ void test_linter_rules_listItem_last() {
+ registerLintRules();
+ getCompletions('''
+linter:
+ rules:
+ - annotate_overrides
+ - ^
+''');
+ assertSuggestion('avoid_as');
+ assertNoSuggestion('annotate_overrides');
+ }
+
+ void test_linter_rules_listItem_middle() {
+ registerLintRules();
+ getCompletions('''
+linter:
+ rules:
+ - annotate_overrides
+ - ^
+ - avoid_empty_else
+''');
+ assertSuggestion('avoid_as');
+ assertNoSuggestion('annotate_overrides');
+ assertNoSuggestion('avoid_empty_else');
+ }
+
+ void test_linter_rules_listItem_nonDuplicate() {
+ registerLintRules();
+ getCompletions('''
+linter:
+ rules:
+ - annotate_overrides
+ - ^
+''');
+ assertNoSuggestion('annotate_overrides');
+ }
+
+ void test_linter_rules_listItem_only() {
+ registerLintRules();
+ getCompletions('''
+linter:
+ rules:
+ - ^
+''');
+ assertSuggestion('annotate_overrides');
+ }
+
+ void test_linter_rules_listItem_partial() {
+ registerLintRules();
+ getCompletions('''
+linter:
+ rules:
+ - ann^
+''');
+ assertSuggestion('annotate_overrides');
+ }
+
+ @failingTest
+ void test_topLevel_afterOtherKeys() {
+ // This test fails because the cursor is considered to be inside the exclude
+ // list, and we don't suggest values there.
+ getCompletions('''
+analyzer:
+ exclude:
+ - '*.g.dart'
+^
+''');
+ assertSuggestion('${AnalyzerOptions.include}: ');
+ }
+
+ @failingTest
+ void test_topLevel_afterOtherKeys_partial() {
+ // This test fails because the YAML parser can't recover from this kind of
+ // invalid input.
+ getCompletions('''
+analyzer:
+ exclude:
+ - '*.g.dart'
+li^
+''');
+ assertSuggestion('linter');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/completion/yaml/fix_data_generator_test.dart b/pkg/analysis_server/test/src/services/completion/yaml/fix_data_generator_test.dart
new file mode 100644
index 0000000..4634360
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/completion/yaml/fix_data_generator_test.dart
@@ -0,0 +1,76 @@
+// 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 'package:analysis_server/src/services/completion/yaml/fix_data_generator.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'yaml_generator_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(FixDataGeneratorTest);
+ });
+}
+
+@reflectiveTest
+class FixDataGeneratorTest extends YamlGeneratorTest {
+ @override
+ String get fileName => 'fix_data.yaml';
+
+ @override
+ FixDataGenerator get generator => FixDataGenerator(resourceProvider);
+
+ void test_empty() {
+ getCompletions('^');
+ assertSuggestion('version: ');
+ assertSuggestion('transforms:');
+ }
+
+ void test_transforms_changes_listItem_last() {
+ getCompletions('''
+transforms:
+ - changes:
+ - kind: rename
+ ^
+''');
+ assertSuggestion('newName: ');
+ assertNoSuggestion('kind: ');
+ }
+
+ void test_transforms_changes_listItem_only() {
+ getCompletions('''
+transforms:
+ - changes:
+ - ^
+''');
+ assertSuggestion('kind: ');
+ }
+
+ void test_transforms_element() {
+ getCompletions('''
+transforms:
+ - element:
+ ^
+''');
+ assertSuggestion('uris: ');
+ }
+
+ void test_transforms_listItem_last() {
+ getCompletions('''
+transforms:
+ - title: ''
+ ^
+''');
+ assertSuggestion('date: ');
+ assertNoSuggestion('title: ');
+ }
+
+ void test_transforms_listItem_only() {
+ getCompletions('''
+transforms:
+ - ^
+''');
+ assertSuggestion('title: ');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/completion/yaml/pubspec_generator_test.dart b/pkg/analysis_server/test/src/services/completion/yaml/pubspec_generator_test.dart
new file mode 100644
index 0000000..7247af0
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/completion/yaml/pubspec_generator_test.dart
@@ -0,0 +1,37 @@
+// 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 'package:analysis_server/src/services/completion/yaml/pubspec_generator.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'yaml_generator_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(PubspecGeneratorTest);
+ });
+}
+
+@reflectiveTest
+class PubspecGeneratorTest extends YamlGeneratorTest {
+ @override
+ String get fileName => 'pubspec.yaml';
+
+ @override
+ PubspecGenerator get generator => PubspecGenerator(resourceProvider);
+
+ void test_empty() {
+ getCompletions('^');
+ assertSuggestion('name: ');
+ }
+
+ void test_environment() {
+ getCompletions('''
+environment:
+ ^
+''');
+ assertSuggestion('flutter: ');
+ assertSuggestion('sdk: ');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/completion/yaml/test_all.dart b/pkg/analysis_server/test/src/services/completion/yaml/test_all.dart
new file mode 100644
index 0000000..0d82611
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/completion/yaml/test_all.dart
@@ -0,0 +1,17 @@
+// 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 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'analysis_options_generator_test.dart' as analysis_options_generator;
+import 'fix_data_generator_test.dart' as fix_data_generator;
+import 'pubspec_generator_test.dart' as pubspec_generator;
+
+void main() {
+ defineReflectiveSuite(() {
+ analysis_options_generator.main();
+ fix_data_generator.main();
+ pubspec_generator.main();
+ }, name: 'yaml');
+}
diff --git a/pkg/analysis_server/test/src/services/completion/yaml/yaml_generator_test_support.dart b/pkg/analysis_server/test/src/services/completion/yaml/yaml_generator_test_support.dart
new file mode 100644
index 0000000..7e4cc84
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/completion/yaml/yaml_generator_test_support.dart
@@ -0,0 +1,64 @@
+// 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 'package:analysis_server/src/protocol_server.dart';
+import 'package:analysis_server/src/services/completion/yaml/yaml_completion_generator.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:test/test.dart';
+
+abstract class YamlGeneratorTest with ResourceProviderMixin {
+ /// The completion results produced by [getCompletions].
+ /* late */ List<CompletionSuggestion> results;
+
+ /// Return the name of the file being tested.
+ String get fileName;
+
+ /// Return the generator to be used for this test.
+ YamlCompletionGenerator get generator;
+
+ /// Assert that there is no suggestion with the given [completion].
+ void assertNoSuggestion(String completion) {
+ for (var suggestion in results) {
+ if (suggestion.completion == completion) {
+ var buffer = StringBuffer();
+ buffer.writeln("Unexpected suggestion of '$completion' in:");
+ for (var suggestion in results) {
+ buffer.writeln(" '${suggestion.completion}'");
+ }
+ fail(buffer.toString());
+ }
+ }
+ }
+
+ /// Assert that there is a suggestion with the given [completion].
+ void assertSuggestion(String completion) {
+ for (var suggestion in results) {
+ if (suggestion.completion == completion) {
+ return;
+ }
+ }
+ var buffer = StringBuffer();
+ buffer.writeln("Missing suggestion of '$completion', found:");
+ for (var suggestion in results) {
+ buffer.writeln(" '${suggestion.completion}'");
+ }
+ fail(buffer.toString());
+ }
+
+ /// Compute the completions in the given [content]. The location of the
+ /// completion request is encoded in the content as a caret (`^`).
+ void getCompletions(String content) {
+ // Extract the completion location from the [content].
+ var completionOffset = content.indexOf('^');
+ expect(completionOffset, isNot(equals(-1)), reason: 'missing ^');
+ var nextOffset = content.indexOf('^', completionOffset + 1);
+ expect(nextOffset, equals(-1), reason: 'too many ^');
+ content = content.substring(0, completionOffset) +
+ content.substring(completionOffset + 1);
+ // Add the file to the file system.
+ var file = newFile('/home/test/$fileName', content: content);
+ // Generate completions.
+ results = generator.getSuggestions(file.path, completionOffset);
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/add_async_test.dart b/pkg/analysis_server/test/src/services/correction/fix/add_async_test.dart
index 8047f30..f4c1745 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/add_async_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/add_async_test.dart
@@ -4,7 +4,7 @@
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
-import 'package:analyzer/error/error.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -14,6 +14,7 @@
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(AddAsyncTest);
+ defineReflectiveTests(AddAsyncWithNullSafetyTest);
defineReflectiveTests(AvoidReturningNullForFutureTest);
});
}
@@ -69,7 +70,7 @@
await foo;
return 1;
}
-''', errorFilter: (AnalysisError error) {
+''', errorFilter: (error) {
return error.errorCode == CompileTimeErrorCode.UNDEFINED_IDENTIFIER_AWAIT;
});
}
@@ -84,7 +85,7 @@
void takeFutureCallback(Future callback()) {}
void doStuff() => takeFutureCallback(() async => await 1);
-''', errorFilter: (AnalysisError error) {
+''', errorFilter: (error) {
return error.errorCode == CompileTimeErrorCode.UNDEFINED_IDENTIFIER_AWAIT;
});
}
@@ -100,6 +101,42 @@
''');
}
+ Future<void> test_missingReturn_hasReturn() async {
+ await resolveTestCode('''
+Future<int> f(bool b) {
+ if (b) {
+ return 0;
+ }
+}
+''');
+ await assertNoFix(errorFilter: (error) {
+ return error.errorCode ==
+ CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION;
+ });
+ }
+
+ Future<void> test_missingReturn_notVoid() async {
+ await resolveTestCode('''
+Future<int> f() {
+ print('');
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_missingReturn_void() async {
+ await resolveTestCode('''
+Future<void> f() {
+ print('');
+}
+''');
+ await assertHasFix('''
+Future<void> f() async {
+ print('');
+}
+''');
+ }
+
Future<void> test_nullFunctionBody() async {
await resolveTestCode('''
var F = await;
@@ -121,7 +158,7 @@
await foo();
return 42;
}
-''', errorFilter: (AnalysisError error) {
+''', errorFilter: (error) {
return error.errorCode == CompileTimeErrorCode.AWAIT_IN_WRONG_CONTEXT;
});
}
@@ -179,6 +216,95 @@
}
@reflectiveTest
+class AddAsyncWithNullSafetyTest extends FixProcessorTest {
+ @override
+ List<String> get experiments => [EnableString.non_nullable];
+
+ @override
+ FixKind get kind => DartFixKind.ADD_ASYNC;
+
+ Future<void> test_missingReturn_method_hasReturn() async {
+ await resolveTestCode('''
+class C {
+ Future<int> m(bool b) {
+ if (b) {
+ return 0;
+ }
+ }
+}
+''');
+ await assertNoFix(errorFilter: (error) {
+ return error.errorCode ==
+ CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD;
+ });
+ }
+
+ Future<void> test_missingReturn_method_notVoid() async {
+ await resolveTestCode('''
+class C {
+ Future<int> m() {
+ print('');
+ }
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_missingReturn_method_void() async {
+ await resolveTestCode('''
+class C {
+ Future<void> m() {
+ print('');
+ }
+}
+''');
+ await assertHasFix('''
+class C {
+ Future<void> m() async {
+ print('');
+ }
+}
+''');
+ }
+
+ Future<void> test_missingReturn_topLevel_hasReturn() async {
+ await resolveTestCode('''
+Future<int> f(bool b) {
+ if (b) {
+ return 0;
+ }
+}
+''');
+ await assertNoFix(errorFilter: (error) {
+ return error.errorCode ==
+ CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION;
+ });
+ }
+
+ Future<void> test_missingReturn_topLevel_notVoid() async {
+ await resolveTestCode('''
+Future<int> f() {
+ print('');
+}
+''');
+ await assertNoFix();
+ }
+
+ Future<void> test_missingReturn_topLevel_void() async {
+ await resolveTestCode('''
+Future<void> f() {
+ print('');
+}
+''');
+ await assertHasFix('''
+Future<void> f() async {
+ print('');
+}
+''');
+ }
+}
+
+@reflectiveTest
class AvoidReturningNullForFutureTest extends FixProcessorLintTest {
@override
FixKind get kind => DartFixKind.ADD_ASYNC;
diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_to_set_literal_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_to_set_literal_test.dart
index f12ee52..28674cb 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/convert_to_set_literal_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/convert_to_set_literal_test.dart
@@ -83,10 +83,7 @@
''');
}
- @failingTest
Future<void> test_from_inferred() async {
- // _setWouldBeInferred does not check for inference based on the parameter
- // type.
await resolveTestCode('''
void f(Set<int> s) {}
var s = f(Set.from([]));
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index acc8ac7..4fc82c5 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -1360,17 +1360,17 @@
_errorReporter.reportErrorForNode(
HintCode.INVALID_REQUIRED_OPTIONAL_POSITIONAL_PARAM,
param,
- [param.identifier.name]);
+ [_formalParameterNameOrEmpty(param)]);
}
for (final param in nonNamedParamsWithRequired.where((p) => p.isRequired)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_REQUIRED_POSITIONAL_PARAM,
param,
- [param.identifier.name]);
+ [_formalParameterNameOrEmpty(param)]);
}
for (final param in namedParamsWithRequiredAndDefault) {
_errorReporter.reportErrorForNode(HintCode.INVALID_REQUIRED_NAMED_PARAM,
- param, [param.identifier.name]);
+ param, [_formalParameterNameOrEmpty(param)]);
}
}
@@ -1629,6 +1629,11 @@
return true;
}
+ static String _formalParameterNameOrEmpty(FormalParameter node) {
+ var identifier = node.identifier;
+ return identifier?.name ?? '';
+ }
+
static bool _hasNonVirtualAnnotation(ExecutableElement element) {
if (element == null) {
return false;
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index e0368a4..63e7b03 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -101,6 +101,10 @@
/// The file name used for analysis options files.
static const String ANALYSIS_OPTIONS_YAML_FILE = 'analysis_options.yaml';
+ /// The file name used for the files containing the data for the data-driven
+ /// fixes.
+ static const String FIX_DATA_FILE = 'fix_data.yaml';
+
/// The file name used for pubspec files.
static const String PUBSPEC_YAML_FILE = 'pubspec.yaml';
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_required_positional_param_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_required_positional_param_test.dart
index 39caa76..072131e 100644
--- a/pkg/analyzer/test/src/diagnostics/invalid_required_positional_param_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/invalid_required_positional_param_test.dart
@@ -21,7 +21,7 @@
writeTestPackageConfigWithMeta();
}
- test_requiredPositionalParameter() async {
+ test_ofFunction_first() async {
await assertErrorsInCode(r'''
import 'package:meta/meta.dart';
@@ -31,7 +31,7 @@
]);
}
- test_requiredPositionalParameter_asSecond() async {
+ test_ofFunction_second() async {
await assertErrorsInCode(r'''
import 'package:meta/meta.dart';
@@ -41,6 +41,26 @@
]);
}
+ test_ofGenericFunctionType_named() async {
+ await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+
+typedef F = void Function(@required int a);
+''', [
+ error(HintCode.INVALID_REQUIRED_POSITIONAL_PARAM, 60, 15),
+ ]);
+ }
+
+ test_ofGenericFunctionType_unnamed() async {
+ await assertErrorsInCode(r'''
+import 'package:meta/meta.dart';
+
+typedef F = void Function(@required int);
+''', [
+ error(HintCode.INVALID_REQUIRED_POSITIONAL_PARAM, 60, 13),
+ ]);
+ }
+
test_valid() async {
await assertNoErrorsInCode(r'''
m1() => null;
diff --git a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart
index f7347d2..a46f3bd 100644
--- a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart
+++ b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/element/element.dart' as analyzer;
-import 'package:analyzer/dart/element/type.dart' as analyzer;
import 'package:analyzer/diagnostic/diagnostic.dart' as analyzer;
import 'package:analyzer/error/error.dart' as analyzer;
import 'package:analyzer/exception/exception.dart' as analyzer;
@@ -11,10 +10,7 @@
import 'package:analyzer/source/line_info.dart' as analyzer;
import 'package:analyzer/src/generated/engine.dart' as analyzer;
import 'package:analyzer/src/generated/source.dart' as analyzer;
-import 'package:analyzer/src/generated/utilities_dart.dart' as analyzer;
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
-import 'package:analyzer_plugin/protocol/protocol_constants.dart' as plugin;
-import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
/// An object used to convert between objects defined by the 'analyzer' package
/// and those defined by the plugin protocol.
diff --git a/pkg/compiler/lib/src/js/js.dart b/pkg/compiler/lib/src/js/js.dart
index c2f8403..dd31e3b 100644
--- a/pkg/compiler/lib/src/js/js.dart
+++ b/pkg/compiler/lib/src/js/js.dart
@@ -18,11 +18,14 @@
String prettyPrint(Node node,
{bool enableMinification: false,
bool allowVariableMinification: true,
+ bool preferSemicolonToNewlineInMinifiedOutput: false,
Renamer renamerForNames: JavaScriptPrintingOptions.identityRenamer}) {
// TODO(johnniwinther): Do we need all the options here?
JavaScriptPrintingOptions options = new JavaScriptPrintingOptions(
shouldCompressOutput: enableMinification,
minifyLocalVariables: allowVariableMinification,
+ preferSemicolonToNewlineInMinifiedOutput:
+ preferSemicolonToNewlineInMinifiedOutput,
renamerForNames: renamerForNames);
SimpleJavaScriptPrintingContext context =
new SimpleJavaScriptPrintingContext();
diff --git a/pkg/compiler/lib/src/js/size_estimator.dart b/pkg/compiler/lib/src/js/size_estimator.dart
new file mode 100644
index 0000000..60d0354
--- /dev/null
+++ b/pkg/compiler/lib/src/js/size_estimator.dart
@@ -0,0 +1,1042 @@
+// 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.
+
+library js.size_estimator;
+
+import 'package:js_ast/js_ast.dart';
+import 'package:js_ast/src/characters.dart' as charCodes;
+import 'package:js_ast/src/precedence.dart';
+
+import '../js_backend/string_reference.dart';
+import '../js_backend/type_reference.dart';
+import '../js_emitter/metadata_collector.dart';
+
+/// [SizeEstimator] is a [NodeVisitor] designed to produce a consistent size
+/// estimate for a given JavaScript AST. [SizeEstimator] trades accuracy for
+/// stability and performance. In addition, [SizeEstimator] assumes we will emit
+/// production quality minified JavaScript.
+class SizeEstimator implements NodeVisitor {
+ int charCount = 0;
+ bool inForInit = false;
+ bool atStatementBegin = false;
+ bool pendingSemicolon = false;
+ bool pendingSpace = false;
+
+ static final String variableSizeEstimate = '#';
+ static final String nameSizeEstimate = '###';
+
+ String sizeEstimate(Node node) {
+ if (node is VariableDeclaration || node is VariableUse) {
+ // We assume all [VariableDeclaration] and [VariableUse] nodes are
+ // locals.
+ return variableSizeEstimate;
+ } else if (node is Name ||
+ node is Parameter ||
+ node is VariableDeclaration ||
+ node is VariableUse) {
+ return nameSizeEstimate;
+ } else if (node is LiteralString) {
+ assert(!node.isFinalized);
+ // We assume all non-final literal strings are minified names, and thus
+ // use the nameSizeEstimate.
+ return nameSizeEstimate;
+ } else if (node is BoundMetadataEntry) {
+ // Value is an int.
+ return '####';
+ } else if (node is TypeReference) {
+ // Type references vary in size. Some references look like:
+ // '<typeHolder>$.<type>' where <typeHolder> is a one byte local and
+ // <type> is roughly 3 bytes. However, we also have to initialize the type
+ // in the holder, some like ab:f("QQ<b7c>"), ie 16 bytes. For two
+ // occurences we will have on average 13 bytes. For a more detailed
+ // estimate, we'd have to partially finalize the results.
+ return '###_###_###_#';
+ } else if (node is StringReference) {
+ // Worst case we have to inline the string so size of string + 2 bytes for
+ // quotes.
+ return "'${node.constant}'";
+ } else {
+ throw UnsupportedError('$node type is not supported');
+ }
+ }
+
+ /// Always emit a newline, even under `enableMinification`.
+ void forceLine() {
+ out('\n'); // '\n'
+ }
+
+ void out(String s) {
+ if (s.length > 0) {
+ // We can elide a semicolon in some cases, but for simplicity we
+ // assume a semicolon is needed here.
+ if (pendingSemicolon) {
+ emit(';'); // ';'
+ }
+
+ // We can elide a pending space in some cases, but for simplicity we
+ // assume a space is needed here.
+ if (pendingSpace) {
+ emit(' '); // ' '
+ }
+ pendingSpace = false;
+ pendingSemicolon = false;
+ emit(s); // str
+ }
+ }
+
+ void outSemicolonLn() {
+ pendingSemicolon = true;
+ }
+
+ void emit(String s) {
+ charCount += s.length;
+ }
+
+ void visit(Node node) {
+ node.accept(this);
+ }
+
+ void visitCommaSeparated(List<Node> nodes, int hasRequiredType,
+ {bool newInForInit, bool newAtStatementBegin}) {
+ for (int i = 0; i < nodes.length; i++) {
+ if (i != 0) {
+ atStatementBegin = false;
+ out(','); // ','
+ }
+ visitNestedExpression(nodes[i], hasRequiredType,
+ newInForInit: newInForInit, newAtStatementBegin: newAtStatementBegin);
+ }
+ }
+
+ void visitAll(List<Node> nodes) {
+ nodes.forEach(visit);
+ }
+
+ @override
+ void visitProgram(Program program) {
+ if (program.body.isNotEmpty) {
+ visitAll(program.body);
+ }
+ }
+
+ Statement unwrapBlockIfSingleStatement(Statement body) {
+ Statement result = body;
+ while (result is Block) {
+ Block block = result;
+ if (block.statements.length != 1) break;
+ result = block.statements.single;
+ }
+ return result;
+ }
+
+ bool blockBody(Statement body, {bool needsSeparation}) {
+ if (body is Block) {
+ blockOut(body);
+ return true;
+ }
+ if (needsSeparation) {
+ out(' '); // ' '
+ }
+ visit(body);
+ return false;
+ }
+
+ void blockOutWithoutBraces(Node node) {
+ if (node is Block) {
+ Block block = node;
+ block.statements.forEach(blockOutWithoutBraces);
+ } else {
+ visit(node);
+ }
+ }
+
+ int blockOut(Block node) {
+ out('{'); // '{'
+ node.statements.forEach(blockOutWithoutBraces);
+ out('}'); // '}'
+ int closingPosition = charCount - 1;
+ return closingPosition;
+ }
+
+ @override
+ void visitBlock(Block block) {
+ blockOut(block);
+ }
+
+ @override
+ void visitExpressionStatement(ExpressionStatement node) {
+ visitNestedExpression(node.expression, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: true);
+ outSemicolonLn();
+ }
+
+ @override
+ void visitEmptyStatement(EmptyStatement node) {
+ out(';'); // ';'
+ }
+
+ void ifOut(If node) {
+ Statement then = unwrapBlockIfSingleStatement(node.then);
+ Statement elsePart = node.otherwise;
+ bool hasElse = node.hasElse;
+
+ out('if('); // 'if('
+ visitNestedExpression(node.condition, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ blockBody(then, needsSeparation: false);
+ if (hasElse) {
+ out('else'); // 'else'
+ if (elsePart is If) {
+ pendingSpace = true;
+ ifOut(elsePart);
+ } else {
+ blockBody(unwrapBlockIfSingleStatement(elsePart),
+ needsSeparation: true);
+ }
+ }
+ }
+
+ @override
+ void visitIf(If node) {
+ ifOut(node);
+ }
+
+ @override
+ void visitFor(For loop) {
+ out('for('); // 'for('
+ if (loop.init != null) {
+ visitNestedExpression(loop.init, EXPRESSION,
+ newInForInit: true, newAtStatementBegin: false);
+ }
+ out(';'); // ';'
+ if (loop.condition != null) {
+ visitNestedExpression(loop.condition, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ }
+ out(';'); // ';'
+ if (loop.update != null) {
+ visitNestedExpression(loop.update, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ }
+ out(')'); // ')'
+ blockBody(unwrapBlockIfSingleStatement(loop.body), needsSeparation: false);
+ }
+
+ @override
+ void visitForIn(ForIn loop) {
+ out('for('); // 'for('
+ visitNestedExpression(loop.leftHandSide, EXPRESSION,
+ newInForInit: true, newAtStatementBegin: false);
+ out(' in'); // ' in'
+ pendingSpace = true;
+ visitNestedExpression(loop.object, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ blockBody(unwrapBlockIfSingleStatement(loop.body), needsSeparation: false);
+ }
+
+ @override
+ void visitWhile(While loop) {
+ out('while('); // 'while('
+ visitNestedExpression(loop.condition, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ blockBody(unwrapBlockIfSingleStatement(loop.body), needsSeparation: false);
+ }
+
+ @override
+ void visitDo(Do loop) {
+ out('do'); // 'do'
+ if (blockBody(unwrapBlockIfSingleStatement(loop.body),
+ needsSeparation: true)) {}
+ out('while('); // 'while('
+ visitNestedExpression(loop.condition, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ outSemicolonLn();
+ }
+
+ @override
+ void visitContinue(Continue node) {
+ if (node.targetLabel == null) {
+ out('continue'); // 'continue'
+ } else {
+ out('continue ${node.targetLabel}'); // 'continue ${node.targetLabel}'
+ }
+ outSemicolonLn();
+ }
+
+ @override
+ void visitBreak(Break node) {
+ if (node.targetLabel == null) {
+ out('break');
+ } else {
+ out('break ${node.targetLabel}');
+ }
+ outSemicolonLn();
+ }
+
+ @override
+ void visitReturn(Return node) {
+ out('return'); // 'return'
+ if (node.value != null) {
+ pendingSpace = true;
+ visitNestedExpression(node.value, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ }
+ outSemicolonLn();
+ }
+
+ @override
+ void visitDartYield(DartYield node) {
+ if (node.hasStar) {
+ out('yield*'); // 'yield*'
+ } else {
+ out('yield'); // 'yield'
+ }
+ pendingSpace = true;
+ visitNestedExpression(node.expression, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ outSemicolonLn();
+ }
+
+ @override
+ void visitThrow(Throw node) {
+ out('throw'); // 'throw'
+ pendingSpace = true;
+ visitNestedExpression(node.expression, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ outSemicolonLn();
+ }
+
+ @override
+ void visitTry(Try node) {
+ out('try'); // 'try'
+ blockBody(node.body, needsSeparation: true);
+ if (node.catchPart != null) {
+ visit(node.catchPart);
+ }
+ if (node.finallyPart != null) {
+ out('finally'); // 'finally'
+ blockBody(node.finallyPart, needsSeparation: true);
+ }
+ }
+
+ @override
+ void visitCatch(Catch node) {
+ out('catch('); // 'catch('
+ visitNestedExpression(node.declaration, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ blockBody(node.body, needsSeparation: false);
+ }
+
+ @override
+ void visitSwitch(Switch node) {
+ out('switch('); // 'switch('
+ visitNestedExpression(node.key, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out('){'); // '){
+ visitAll(node.cases);
+ out('}'); // '}'
+ }
+
+ @override
+ void visitCase(Case node) {
+ out('case'); // 'case'
+ pendingSpace = true;
+ visitNestedExpression(node.expression, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(':'); // ':'
+ if (!node.body.statements.isEmpty) {
+ blockOutWithoutBraces(node.body);
+ }
+ }
+
+ @override
+ void visitDefault(Default node) {
+ out('default:'); // 'default:'
+ if (!node.body.statements.isEmpty) {
+ blockOutWithoutBraces(node.body);
+ }
+ }
+
+ @override
+ void visitLabeledStatement(LabeledStatement node) {
+ Statement body = unwrapBlockIfSingleStatement(node.body);
+ // `label: break label;`
+ // Does not work on IE. The statement is a nop, so replace it by an empty
+ // statement.
+ // See:
+ // https://connect.microsoft.com/IE/feedback/details/891889/parser-bugs
+ if (body is Break && body.targetLabel == node.label) {
+ visit(new EmptyStatement());
+ return;
+ }
+ out('${node.label}:');
+ blockBody(body, needsSeparation: false);
+ }
+
+ int functionOut(Fun fun, Node name, VarCollector vars) {
+ out('function'); // 'function'
+ if (name != null) {
+ out(' '); // ' '
+ // Name must be a [Decl]. Therefore only test for primary expressions.
+ visitNestedExpression(name, PRIMARY,
+ newInForInit: false, newAtStatementBegin: false);
+ }
+ out('('); // '('
+ if (fun.params != null) {
+ visitCommaSeparated(fun.params, PRIMARY,
+ newInForInit: false, newAtStatementBegin: false);
+ }
+ out(')'); // ')'
+ switch (fun.asyncModifier) {
+ case AsyncModifier.sync:
+ break;
+ case AsyncModifier.async:
+ out(' async'); // ' async'
+ break;
+ case AsyncModifier.syncStar:
+ out(' sync*'); // ' sync*'
+ break;
+ case AsyncModifier.asyncStar:
+ out(' async*'); // ' async*'
+ break;
+ }
+ int closingPosition = blockOut(fun.body);
+ return closingPosition;
+ }
+
+ @override
+ visitFunctionDeclaration(FunctionDeclaration declaration) {
+ VarCollector vars = new VarCollector();
+ vars.visitFunctionDeclaration(declaration);
+ functionOut(declaration.function, declaration.name, vars);
+ }
+
+ visitNestedExpression(Expression node, int requiredPrecedence,
+ {bool newInForInit, bool newAtStatementBegin}) {
+ bool needsParentheses = !node.isFinalized ||
+ // a - (b + c).
+ (requiredPrecedence != EXPRESSION &&
+ node.precedenceLevel < requiredPrecedence) ||
+ // for (a = (x in o); ... ; ... ) { ... }
+ (newInForInit && node is Binary && node.op == "in") ||
+ // (function() { ... })().
+ // ({a: 2, b: 3}.toString()).
+ (newAtStatementBegin &&
+ (node is NamedFunction ||
+ node is Fun ||
+ node is ObjectInitializer));
+ if (needsParentheses) {
+ inForInit = false;
+ atStatementBegin = false;
+ out('('); // '('
+ visit(node);
+ out(')'); // ')'
+ } else {
+ inForInit = newInForInit;
+ atStatementBegin = newAtStatementBegin;
+ visit(node);
+ }
+ }
+
+ @override
+ visitVariableDeclarationList(VariableDeclarationList list) {
+ out('var '); // 'var '
+ List<Node> nodes = list.declarations;
+ if (inForInit) {
+ visitCommaSeparated(nodes, ASSIGNMENT,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ } else {
+ for (int i = 0; i < nodes.length; i++) {
+ Node node = nodes[i];
+ if (i > 0) {
+ atStatementBegin = false;
+ out(','); // ','
+ }
+ visitNestedExpression(node, ASSIGNMENT,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ }
+ }
+ }
+
+ void _outputIncDec(String op, Expression variable, [Expression alias]) {
+ // We can eliminate the space preceding the inc/dec in some cases,
+ // but for estimation purposes we assume the worst case.
+ if (op == '+') {
+ out(' ++');
+ } else {
+ out(' --');
+ }
+ visitNestedExpression(variable, UNARY,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ }
+
+ @override
+ visitAssignment(Assignment assignment) {
+ /// To print assignments like `a = a + 1` and `a = a + b` compactly as
+ /// `++a` and `a += b` in the face of [DeferredExpression]s we detect the
+ /// pattern of the undeferred assignment.
+ String op = assignment.op;
+ Node leftHandSide = assignment.leftHandSide;
+ Node rightHandSide = assignment.value;
+ if ((op == '+' || op == '-') &&
+ leftHandSide is VariableUse &&
+ rightHandSide is LiteralNumber &&
+ rightHandSide.value == "1") {
+ // Output 'a += 1' as '++a' and 'a -= 1' as '--a'.
+ _outputIncDec(op, assignment.leftHandSide);
+ return;
+ }
+ if (!assignment.isCompound &&
+ leftHandSide is VariableUse &&
+ rightHandSide is Binary) {
+ Node rLeft = rightHandSide.left;
+ Node rRight = rightHandSide.right;
+ String op = rightHandSide.op;
+ if (op == '+' ||
+ op == '-' ||
+ op == '/' ||
+ op == '*' ||
+ op == '%' ||
+ op == '^' ||
+ op == '&' ||
+ op == '|') {
+ if (rLeft is VariableUse && rLeft.name == leftHandSide.name) {
+ // Output 'a = a + 1' as '++a' and 'a = a - 1' as '--a'.
+ if ((op == '+' || op == '-') &&
+ rRight is LiteralNumber &&
+ rRight.value == "1") {
+ _outputIncDec(op, assignment.leftHandSide, rightHandSide.left);
+ return;
+ }
+ // Output 'a = a + b' as 'a += b'.
+ visitNestedExpression(assignment.leftHandSide, CALL,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+ assert(op.length == 1);
+ out('$op='); // '$op='
+ visitNestedExpression(rRight, ASSIGNMENT,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ return;
+ }
+ }
+ }
+ visitNestedExpression(assignment.leftHandSide, CALL,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+ if (assignment.value != null) {
+ if (op != null) out(op);
+ out('='); // '='
+ visitNestedExpression(assignment.value, ASSIGNMENT,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ }
+ }
+
+ @override
+ visitVariableInitialization(VariableInitialization initialization) {
+ visitAssignment(initialization);
+ }
+
+ @override
+ visitConditional(Conditional cond) {
+ visitNestedExpression(cond.condition, LOGICAL_OR,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+ out('?'); // '?'
+ // The then part is allowed to have an 'in'.
+ visitNestedExpression(cond.then, ASSIGNMENT,
+ newInForInit: false, newAtStatementBegin: false);
+ out(':'); // ':'
+ visitNestedExpression(cond.otherwise, ASSIGNMENT,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ }
+
+ @override
+ visitNew(New node) {
+ out('new'); // 'new'
+ visitNestedExpression(node.target, LEFT_HAND_SIDE,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ out('('); // '('
+ visitCommaSeparated(node.arguments, ASSIGNMENT,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ }
+
+ @override
+ visitCall(Call call) {
+ visitNestedExpression(call.target, CALL,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+ out('('); // '('
+ visitCommaSeparated(call.arguments, ASSIGNMENT,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ }
+
+ @override
+ void visitBinary(Binary binary) {
+ Expression left = binary.left;
+ Expression right = binary.right;
+ String op = binary.op;
+ int leftPrecedenceRequirement;
+ int rightPrecedenceRequirement;
+ switch (op) {
+ case ',':
+ // x, (y, z) <=> (x, y), z.
+ leftPrecedenceRequirement = EXPRESSION;
+ rightPrecedenceRequirement = EXPRESSION;
+ break;
+ case "||":
+ leftPrecedenceRequirement = LOGICAL_OR;
+ // x || (y || z) <=> (x || y) || z.
+ rightPrecedenceRequirement = LOGICAL_OR;
+ break;
+ case "&&":
+ leftPrecedenceRequirement = LOGICAL_AND;
+ // x && (y && z) <=> (x && y) && z.
+ rightPrecedenceRequirement = LOGICAL_AND;
+ break;
+ case "|":
+ leftPrecedenceRequirement = BIT_OR;
+ // x | (y | z) <=> (x | y) | z.
+ rightPrecedenceRequirement = BIT_OR;
+ break;
+ case "^":
+ leftPrecedenceRequirement = BIT_XOR;
+ // x ^ (y ^ z) <=> (x ^ y) ^ z.
+ rightPrecedenceRequirement = BIT_XOR;
+ break;
+ case "&":
+ leftPrecedenceRequirement = BIT_AND;
+ // x & (y & z) <=> (x & y) & z.
+ rightPrecedenceRequirement = BIT_AND;
+ break;
+ case "==":
+ case "!=":
+ case "===":
+ case "!==":
+ leftPrecedenceRequirement = EQUALITY;
+ rightPrecedenceRequirement = RELATIONAL;
+ break;
+ case "<":
+ case ">":
+ case "<=":
+ case ">=":
+ case "instanceof":
+ case "in":
+ leftPrecedenceRequirement = RELATIONAL;
+ rightPrecedenceRequirement = SHIFT;
+ break;
+ case ">>":
+ case "<<":
+ case ">>>":
+ leftPrecedenceRequirement = SHIFT;
+ rightPrecedenceRequirement = ADDITIVE;
+ break;
+ case "+":
+ case "-":
+ leftPrecedenceRequirement = ADDITIVE;
+ // We cannot remove parenthesis for "+" because
+ // x + (y + z) <!=> (x + y) + z:
+ // Example:
+ // "a" + (1 + 2) => "a3";
+ // ("a" + 1) + 2 => "a12";
+ rightPrecedenceRequirement = MULTIPLICATIVE;
+ break;
+ case "*":
+ case "/":
+ case "%":
+ leftPrecedenceRequirement = MULTIPLICATIVE;
+ // We cannot remove parenthesis for "*" because of precision issues.
+ rightPrecedenceRequirement = UNARY;
+ break;
+ default:
+ throw UnsupportedError("Forgot operator: $op");
+ }
+
+ visitNestedExpression(left, leftPrecedenceRequirement,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+
+ if (op == "in" || op == "instanceof") {
+ // There are cases where the space is not required but without further
+ // analysis we cannot know.
+ out(' $op '); // ' $op '
+ } else {
+ out(op); // '$op'
+ }
+ visitNestedExpression(right, rightPrecedenceRequirement,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ }
+
+ @override
+ void visitPrefix(Prefix unary) {
+ String op = unary.op;
+ switch (op) {
+ case "delete":
+ case "void":
+ case "typeof":
+ case "+":
+ case "++":
+ case "-":
+ case "--":
+ // We may be able to eliminate the space in some cases, but for
+ // estimation we assume the worst case.
+ out('$op '); // '$op '
+ break;
+ default:
+ out('$op'); // '$op'
+ }
+ visitNestedExpression(unary.argument, UNARY,
+ newInForInit: inForInit, newAtStatementBegin: false);
+ }
+
+ @override
+ void visitPostfix(Postfix postfix) {
+ visitNestedExpression(postfix.argument, CALL,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+ out(postfix.op); // '${postfix.op}'
+ }
+
+ @override
+ void visitVariableUse(VariableUse ref) {
+ // For simplicity and stability we use a constant name size estimate.
+ // In production this is:
+ // '${localNamer.getName(ref.name)'
+ out(sizeEstimate(ref));
+ }
+
+ @override
+ void visitThis(This node) {
+ out('this'); // 'this'
+ }
+
+ @override
+ void visitVariableDeclaration(VariableDeclaration decl) {
+ // '${localNamer.getName(decl.name)'
+ out(sizeEstimate(decl));
+ }
+
+ @override
+ void visitParameter(Parameter param) {
+ // For simplicity and stability we use a constant name size estimate.
+ // In production this is:
+ // '${localNamer.getName(param.name)'
+ out(sizeEstimate(param));
+ }
+
+ bool isDigit(int charCode) {
+ return charCodes.$0 <= charCode && charCode <= charCodes.$9;
+ }
+
+ bool isValidJavaScriptId(String field) {
+ if (field.length < 3) return false;
+ // Ignore the leading and trailing string-delimiter.
+ for (int i = 1; i < field.length - 1; i++) {
+ // TODO(floitsch): allow more characters.
+ int charCode = field.codeUnitAt(i);
+ if (!(charCodes.$a <= charCode && charCode <= charCodes.$z ||
+ charCodes.$A <= charCode && charCode <= charCodes.$Z ||
+ charCode == charCodes.$$ ||
+ charCode == charCodes.$_ ||
+ i != 1 && isDigit(charCode))) {
+ return false;
+ }
+ }
+ // TODO(floitsch): normally we should also check that the field is not a
+ // reserved word. We don't generate fields with reserved word names except
+ // for 'super'.
+ if (field == '"super"') return false;
+ if (field == '"catch"') return false;
+ return true;
+ }
+
+ @override
+ void visitAccess(PropertyAccess access) {
+ visitNestedExpression(access.receiver, CALL,
+ newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+ Node selector = access.selector;
+ if (selector is LiteralString) {
+ String fieldWithQuotes = selector.value;
+ if (isValidJavaScriptId(fieldWithQuotes)) {
+ if (access.receiver is LiteralNumber) {
+ // We can eliminate the space in some cases, but for simplicity we
+ // always assume it is necessary.
+ out(' '); // ' '
+ }
+
+ // '.${fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)}'
+ out('.${fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)}');
+ return;
+ }
+ } else if (selector is Name) {
+ Node receiver = access.receiver;
+ if (receiver is LiteralNumber) {
+ // We can eliminate the space in some cases, but for simplicity we
+ // always assume it is necessary.
+ out(' '); // ' '
+ }
+ out('.'); // '.'
+ selector.accept(this);
+ return;
+ }
+ out('['); // '['
+ visitNestedExpression(access.selector, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(']'); // ']
+ }
+
+ @override
+ void visitNamedFunction(NamedFunction namedFunction) {
+ VarCollector vars = new VarCollector();
+ vars.visitNamedFunction(namedFunction);
+ functionOut(namedFunction.function, namedFunction.name, vars);
+ }
+
+ @override
+ void visitFun(Fun fun) {
+ VarCollector vars = new VarCollector();
+ vars.visitFun(fun);
+ functionOut(fun, null, vars);
+ }
+
+ @override
+ visitDeferredExpression(DeferredExpression node) {
+ if (node.isFinalized) {
+ // Continue printing with the expression value.
+ assert(node.precedenceLevel == node.value.precedenceLevel);
+ node.value.accept(this);
+ } else {
+ out(sizeEstimate(node));
+ }
+ }
+
+ outputNumberWithRequiredWhitespace(String number) {
+ int charCode = number.codeUnitAt(0);
+ if (charCode == charCodes.$MINUS) {
+ // We can eliminate the space in some cases, but for simplicity we
+ // always assume it is necessary.
+ out(' ');
+ }
+ out(number); // '${number}'
+ }
+
+ @override
+ visitDeferredNumber(DeferredNumber node) {
+ if (node.isFinalized) {
+ outputNumberWithRequiredWhitespace("${node.value}");
+ } else {
+ out(sizeEstimate(node));
+ }
+ }
+
+ @override
+ visitDeferredString(DeferredString node) {
+ if (node.isFinalized) {
+ out(node.value);
+ } else {
+ out(sizeEstimate(node));
+ }
+ }
+
+ @override
+ visitLiteralBool(LiteralBool node) {
+ out(node.value ? '!0' : '!1');
+ }
+
+ @override
+ void visitLiteralString(LiteralString node) {
+ if (node.isFinalized) {
+ out(node.value); // '${node.value}'
+ } else {
+ out(sizeEstimate(node));
+ }
+ }
+
+ @override
+ visitStringConcatenation(StringConcatenation node) {
+ node.visitChildren(this);
+ }
+
+ @override
+ visitName(Name node) {
+ // For simplicity and stability we use a constant name size estimate.
+ // In production this is:
+ // '${options.renamerForNames(node)}'
+ out(sizeEstimate(node));
+ }
+
+ @override
+ visitParentheses(Parentheses node) {
+ out('('); // '('
+ visitNestedExpression(node.enclosed, EXPRESSION,
+ newInForInit: false, newAtStatementBegin: false);
+ out(')'); // ')'
+ }
+
+ @override
+ visitLiteralNumber(LiteralNumber node) {
+ outputNumberWithRequiredWhitespace(node.value);
+ }
+
+ @override
+ void visitLiteralNull(LiteralNull node) {
+ out('null'); // 'null'
+ }
+
+ @override
+ void visitArrayInitializer(ArrayInitializer node) {
+ out('['); // '['
+ List<Expression> elements = node.elements;
+ for (int i = 0; i < elements.length; i++) {
+ Expression element = elements[i];
+ if (element is ArrayHole) {
+ // Note that array holes must have a trailing "," even if they are
+ // in last position. Otherwise `[,]` (having length 1) would become
+ // equal to `[]` (the empty array)
+ // and [1,,] (array with 1 and a hole) would become [1,] = [1].
+ out(','); // ','
+ continue;
+ }
+ visitNestedExpression(element, ASSIGNMENT,
+ newInForInit: false, newAtStatementBegin: false);
+ // We can skip the trailing "," for the last element (since it's not
+ // an array hole).
+ if (i != elements.length - 1) out(','); // ','
+ }
+ out(']'); // ']'
+ }
+
+ @override
+ void visitArrayHole(ArrayHole node) {
+ throw UnsupportedError("Unreachable");
+ }
+
+ @override
+ void visitObjectInitializer(ObjectInitializer node) {
+ // Print all the properties on one line until we see a function-valued
+ // property. Ideally, we would use a proper pretty-printer to make the
+ // decision based on layout.
+ bool exitOneLinerMode(Expression value) {
+ return value is Fun ||
+ value is ArrayInitializer && value.elements.any((e) => e is Fun);
+ }
+
+ bool isOneLiner = true;
+ List<Property> properties = node.properties;
+ out('{'); // '{'
+ for (int i = 0; i < properties.length; i++) {
+ Node value = properties[i].value;
+ if (isOneLiner && exitOneLinerMode(value)) isOneLiner = false;
+ if (i != 0) {
+ out(','); // ','
+ }
+ if (!isOneLiner) {
+ forceLine();
+ }
+ visit(properties[i]);
+ }
+ out('}'); // '}'
+ }
+
+ @override
+ void visitProperty(Property node) {
+ Node name = node.name;
+ if (name is LiteralString) {
+ String text = name.value;
+ if (isValidJavaScriptId(text)) {
+ // '${text.substring(1, text.length - 1)}
+ out('${text.substring(1, text.length - 1)}');
+ } else {
+ out(text); // '$text'
+ }
+ } else if (name is Name) {
+ node.name.accept(this);
+ } else if (name is DeferredExpression) {
+ out(sizeEstimate(name));
+ } else {
+ assert(name is LiteralNumber);
+ LiteralNumber nameNumber = node.name;
+ out(nameNumber.value); // '${nameNumber.value}'
+ }
+ out(':'); // ':'
+ visitNestedExpression(node.value, ASSIGNMENT,
+ newInForInit: false, newAtStatementBegin: false);
+ }
+
+ @override
+ void visitRegExpLiteral(RegExpLiteral node) {
+ out(node.pattern); // '${node.pattern}'
+ }
+
+ @override
+ void visitLiteralExpression(LiteralExpression node) {
+ String template = node.template;
+ List<Expression> inputs = node.inputs;
+
+ List<String> parts = template.split('#');
+ int inputsLength = inputs == null ? 0 : inputs.length;
+ if (parts.length != inputsLength + 1) {
+ throw UnsupportedError('Wrong number of arguments for JS: $template');
+ }
+ // Code that uses JS must take care of operator precedences, and
+ // put parenthesis if needed.
+ out(parts[0]); // '${parts[0]}'
+ for (int i = 0; i < inputsLength; i++) {
+ visit(inputs[i]);
+ out(parts[i + 1]); // '${parts[i + 1]}'
+ }
+ }
+
+ @override
+ void visitLiteralStatement(LiteralStatement node) {
+ out(node.code); // '${node.code}'
+ }
+
+ void visitInterpolatedNode(InterpolatedNode node) {
+ throw UnsupportedError('InterpolatedStatements are not supported');
+ }
+
+ @override
+ void visitInterpolatedExpression(InterpolatedExpression node) =>
+ visitInterpolatedNode(node);
+
+ @override
+ void visitInterpolatedLiteral(InterpolatedLiteral node) =>
+ visitInterpolatedNode(node);
+
+ @override
+ void visitInterpolatedParameter(InterpolatedParameter node) =>
+ visitInterpolatedNode(node);
+
+ @override
+ void visitInterpolatedSelector(InterpolatedSelector node) =>
+ visitInterpolatedNode(node);
+
+ @override
+ void visitInterpolatedStatement(InterpolatedStatement node) {
+ throw UnsupportedError('InterpolatedStatements are not supported');
+ }
+
+ @override
+ void visitInterpolatedDeclaration(InterpolatedDeclaration node) {
+ visitInterpolatedNode(node);
+ }
+
+ @override
+ void visitComment(Comment node) {
+ // We assume output is compressed and thus do not output comments.
+ }
+
+ @override
+ void visitAwait(Await node) {
+ out('await '); // 'await '
+ visit(node.expression);
+ }
+}
+
+int EstimateSize(Node node) {
+ var estimator = SizeEstimator();
+ estimator.visit(node);
+ return estimator.charCount;
+}
diff --git a/pkg/compiler/lib/src/js_backend/namer_names.dart b/pkg/compiler/lib/src/js_backend/namer_names.dart
index dd8c765..ce57087 100644
--- a/pkg/compiler/lib/src/js_backend/namer_names.dart
+++ b/pkg/compiler/lib/src/js_backend/namer_names.dart
@@ -83,6 +83,9 @@
int get hashCode => base.hashCode * 13 + prefix.hashCode;
@override
+ bool get isFinalized => prefix.isFinalized && base.isFinalized;
+
+ @override
int compareTo(jsAst.Name other) {
_NamerName otherNamerName;
if (other is ModularName) {
@@ -209,6 +212,7 @@
TokenName(this._scope, this.key);
+ @override
bool get isFinalized => _name != null;
@override
@@ -276,6 +280,9 @@
}
@override
+ bool get isFinalized => _target.isFinalized;
+
+ @override
bool operator ==(other) => _target == other;
@override
diff --git a/pkg/compiler/lib/src/js_backend/string_reference.dart b/pkg/compiler/lib/src/js_backend/string_reference.dart
index 95fa795..b8c41aa 100644
--- a/pkg/compiler/lib/src/js_backend/string_reference.dart
+++ b/pkg/compiler/lib/src/js_backend/string_reference.dart
@@ -115,16 +115,19 @@
}
set value(js.Expression value) {
- assert(_value == null && value != null);
+ assert(!isFinalized && value != null);
_value = value;
}
@override
js.Expression get value {
- assert(_value != null, 'StringReference is unassigned');
+ assert(isFinalized, 'StringReference is unassigned');
return _value;
}
+ @override
+ bool get isFinalized => _value != null;
+
// Precedence will be CALL or LEFT_HAND_SIDE depending on what expression the
// reference is resolved to.
@override
@@ -139,7 +142,7 @@
}
@override
- Iterable<js.Node> get containedNodes => _value == null ? const [] : [_value];
+ Iterable<js.Node> get containedNodes => isFinalized ? [_value] : const [];
}
/// A [StringReferenceResource] is a deferred JavaScript expression determined
@@ -159,13 +162,13 @@
StringReferenceResource._(this._value, this.sourceInformation);
set value(js.Expression value) {
- assert(_value == null && value != null);
+ assert(!isFinalized && value != null);
_value = value;
}
@override
js.Expression get value {
- assert(_value != null, 'StringReferenceResource is unassigned');
+ assert(isFinalized, 'StringReferenceResource is unassigned');
return _value;
}
@@ -173,6 +176,9 @@
int get precedenceLevel => value.precedenceLevel;
@override
+ bool get isFinalized => _value != null;
+
+ @override
StringReferenceResource withSourceInformation(
js.JavaScriptNodeSourceInformation newSourceInformation) {
if (newSourceInformation == sourceInformation) return this;
@@ -181,7 +187,7 @@
}
@override
- Iterable<js.Node> get containedNodes => _value == null ? const [] : [_value];
+ Iterable<js.Node> get containedNodes => isFinalized ? [_value] : const [];
@override
void visitChildren<T>(js.NodeVisitor<T> visitor) {
diff --git a/pkg/compiler/lib/src/js_backend/type_reference.dart b/pkg/compiler/lib/src/js_backend/type_reference.dart
index de1a5f3..f33591d 100644
--- a/pkg/compiler/lib/src/js_backend/type_reference.dart
+++ b/pkg/compiler/lib/src/js_backend/type_reference.dart
@@ -132,16 +132,19 @@
}
set value(js.Expression value) {
- assert(_value == null && value != null);
+ assert(!isFinalized && value != null);
_value = value;
}
@override
js.Expression get value {
- assert(_value != null, 'TypeReference is unassigned');
+ assert(isFinalized, 'TypeReference is unassigned');
return _value;
}
+ @override
+ bool get isFinalized => _value != null;
+
// Precedence will be CALL or LEFT_HAND_SIDE depending on what expression the
// reference is resolved to.
@override
@@ -156,7 +159,7 @@
}
@override
- Iterable<js.Node> get containedNodes => _value == null ? const [] : [_value];
+ Iterable<js.Node> get containedNodes => isFinalized ? [_value] : const [];
}
/// A [TypeReferenceResource] is a deferred JavaScript expression determined by
@@ -176,17 +179,20 @@
TypeReferenceResource._(this._value, this.sourceInformation);
set value(js.Expression value) {
- assert(_value == null && value != null);
+ assert(!isFinalized && value != null);
_value = value;
}
@override
js.Expression get value {
- assert(_value != null, 'TypeReferenceResource is unassigned');
+ assert(isFinalized, 'TypeReferenceResource is unassigned');
return _value;
}
@override
+ bool get isFinalized => _value != null;
+
+ @override
int get precedenceLevel => value.precedenceLevel;
@override
@@ -198,7 +204,7 @@
}
@override
- Iterable<js.Node> get containedNodes => _value == null ? const [] : [_value];
+ Iterable<js.Node> get containedNodes => isFinalized ? [_value] : const [];
@override
void visitChildren<T>(js.NodeVisitor<T> visitor) {
diff --git a/pkg/compiler/lib/src/js_emitter/metadata_collector.dart b/pkg/compiler/lib/src/js_emitter/metadata_collector.dart
index 8066ff1..6fac2f9 100644
--- a/pkg/compiler/lib/src/js_emitter/metadata_collector.dart
+++ b/pkg/compiler/lib/src/js_emitter/metadata_collector.dart
@@ -41,15 +41,16 @@
void markSeen(jsAst.TokenCounter visitor);
}
-class _BoundMetadataEntry extends _MetadataEntry {
+class BoundMetadataEntry extends _MetadataEntry {
int _value = -1;
@override
int _rc = 0;
@override
final jsAst.Expression entry;
- _BoundMetadataEntry(this.entry);
+ BoundMetadataEntry(this.entry);
+ @override
bool get isFinalized => _value != -1;
finalize(int value) {
@@ -75,7 +76,7 @@
int compareTo(covariant _MetadataEntry other) => other._rc - this._rc;
@override
- String toString() => '_BoundMetadataEntry($hashCode,rc=$_rc,_value=$_value)';
+ String toString() => 'BoundMetadataEntry($hashCode,rc=$_rc,_value=$_value)';
}
class _MetadataList extends jsAst.DeferredExpression {
@@ -114,8 +115,8 @@
}
/// A map used to canonicalize the entries of metadata.
- Map<OutputUnit, Map<String, _BoundMetadataEntry>> _metadataMap =
- <OutputUnit, Map<String, _BoundMetadataEntry>>{};
+ Map<OutputUnit, Map<String, BoundMetadataEntry>> _metadataMap =
+ <OutputUnit, Map<String, BoundMetadataEntry>>{};
/// A map with a token for a lists of JS expressions, one token for each
/// output unit. Once finalized, the entries represent types including
@@ -128,8 +129,8 @@
}
/// A map used to canonicalize the entries of types.
- Map<OutputUnit, Map<DartType, _BoundMetadataEntry>> _typesMap =
- <OutputUnit, Map<DartType, _BoundMetadataEntry>>{};
+ Map<OutputUnit, Map<DartType, BoundMetadataEntry>> _typesMap =
+ <OutputUnit, Map<DartType, BoundMetadataEntry>>{};
MetadataCollector(this._options, this.reporter, this._emitter,
this._rtiRecipeEncoder, this._elementEnvironment);
@@ -165,9 +166,9 @@
String printed = jsAst.prettyPrint(node,
enableMinification: _options.enableMinification,
renamerForNames: nameToKey);
- _metadataMap[outputUnit] ??= new Map<String, _BoundMetadataEntry>();
+ _metadataMap[outputUnit] ??= new Map<String, BoundMetadataEntry>();
return _metadataMap[outputUnit].putIfAbsent(printed, () {
- return new _BoundMetadataEntry(node);
+ return new BoundMetadataEntry(node);
});
}
@@ -177,36 +178,36 @@
}
jsAst.Expression addTypeInOutputUnit(DartType type, OutputUnit outputUnit) {
- _typesMap[outputUnit] ??= new Map<DartType, _BoundMetadataEntry>();
+ _typesMap[outputUnit] ??= new Map<DartType, BoundMetadataEntry>();
return _typesMap[outputUnit].putIfAbsent(type, () {
- return new _BoundMetadataEntry(_computeTypeRepresentationNewRti(type));
+ return new BoundMetadataEntry(_computeTypeRepresentationNewRti(type));
});
}
@override
void finalizeTokens() {
- void countTokensInTypes(Iterable<_BoundMetadataEntry> entries) {
+ void countTokensInTypes(Iterable<BoundMetadataEntry> entries) {
jsAst.TokenCounter counter = new jsAst.TokenCounter();
entries
- .where((_BoundMetadataEntry e) => e._rc > 0)
- .map((_BoundMetadataEntry e) => e.entry)
+ .where((BoundMetadataEntry e) => e._rc > 0)
+ .map((BoundMetadataEntry e) => e.entry)
.forEach(counter.countTokens);
}
- jsAst.ArrayInitializer finalizeMap(Map<dynamic, _BoundMetadataEntry> map) {
- bool isUsed(_BoundMetadataEntry entry) => entry.isUsed;
- List<_BoundMetadataEntry> entries = map.values.where(isUsed).toList();
+ jsAst.ArrayInitializer finalizeMap(Map<dynamic, BoundMetadataEntry> map) {
+ bool isUsed(BoundMetadataEntry entry) => entry.isUsed;
+ List<BoundMetadataEntry> entries = map.values.where(isUsed).toList();
entries.sort();
// TODO(herhut): Bucket entries by index length and use a stable
// distribution within buckets.
int count = 0;
- for (_BoundMetadataEntry entry in entries) {
+ for (BoundMetadataEntry entry in entries) {
entry.finalize(count++);
}
List<jsAst.Node> values =
- entries.map((_BoundMetadataEntry e) => e.entry).toList();
+ entries.map((BoundMetadataEntry e) => e.entry).toList();
return new jsAst.ArrayInitializer(values);
}
diff --git a/pkg/compiler/test/js/debug_size_estimator.dart b/pkg/compiler/test/js/debug_size_estimator.dart
new file mode 100644
index 0000000..86b528c
--- /dev/null
+++ b/pkg/compiler/test/js/debug_size_estimator.dart
@@ -0,0 +1,18 @@
+// 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 'package:compiler/src/js/size_estimator.dart';
+
+/// This class is supposed to be identical to [SizeEstimator] with the sole
+/// difference that it builds up strings to help debug estimates.
+class DebugSizeEstimator extends SizeEstimator {
+ StringBuffer resultBuffer = StringBuffer();
+ String get resultString => resultBuffer.toString();
+
+ @override
+ void emit(String s) {
+ resultBuffer.write(s);
+ super.emit(s);
+ }
+}
diff --git a/pkg/compiler/test/js/js_size_estimator_test.dart b/pkg/compiler/test/js/js_size_estimator_test.dart
new file mode 100644
index 0000000..cb5ce76
--- /dev/null
+++ b/pkg/compiler/test/js/js_size_estimator_test.dart
@@ -0,0 +1,125 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.7
+
+import 'dart:convert' as json;
+import 'dart:io';
+
+import 'package:expect/expect.dart';
+import 'package:compiler/src/js/js.dart';
+import 'package:compiler/src/js/size_estimator.dart';
+import 'debug_size_estimator.dart';
+
+const String expressionsKey = 'expressions';
+const String statementsKey = 'statements';
+const String originalKey = 'original';
+const String expectedKey = 'expected';
+const String minifiedKey = 'minified';
+
+DebugSizeEstimator debugSizeEstimator(Node node) {
+ DebugSizeEstimator debugSizeEstimator = DebugSizeEstimator();
+ debugSizeEstimator.visit(node);
+
+ // Always verify the actual results from the [SizeEstimator].
+ // This is the actual test, though DebugSizeEstimator is pretty trivial.
+ int actualEstimate = EstimateSize(node);
+ Expect.equals(actualEstimate, debugSizeEstimator.charCount);
+ return debugSizeEstimator;
+}
+
+abstract class TestSuite {
+ String get key;
+ Node parse(String testCase);
+
+ String generateExpected(Node node) {
+ return debugSizeEstimator(node).resultString;
+ }
+
+ String generateMinified(Node node) {
+ return prettyPrint(node,
+ enableMinification: true,
+ preferSemicolonToNewlineInMinifiedOutput: true);
+ }
+
+ Map<String, String> goldenTestCase(goldenTestCaseJson) {
+ String original = goldenTestCaseJson[originalKey];
+ Node node = parse(original);
+ return {
+ originalKey: original,
+ expectedKey: generateExpected(node),
+ minifiedKey: generateMinified(node),
+ };
+ }
+
+ List<Map<String, String>> regenerateGoldens(currentGoldensJson) {
+ List<Map<String, String>> newGoldens = [];
+ for (var testCaseJson in currentGoldensJson) {
+ newGoldens.add(goldenTestCase(testCaseJson));
+ }
+ return newGoldens;
+ }
+
+ void verifyGoldens(goldensJson) {
+ for (var goldenTestCase in goldensJson) {
+ test(goldenTestCase[originalKey], goldenTestCase[expectedKey]);
+ }
+ }
+
+ void test(String original, String expected) {
+ var debugResults = debugSizeEstimator(parse(original));
+ Expect.equals(expected, debugResults.resultString);
+ Expect.equals(expected.length, debugResults.charCount);
+ }
+}
+
+class ExpressionTestSuite extends TestSuite {
+ @override
+ String get key => expressionsKey;
+
+ @override
+ Node parse(String expression) => js(expression);
+}
+
+class StatementTestSuite extends TestSuite {
+ @override
+ String get key => statementsKey;
+
+ @override
+ Node parse(String statement) => js.statement(statement);
+}
+
+List<TestSuite> testSuites = [
+ ExpressionTestSuite(),
+ StatementTestSuite(),
+];
+
+void generateGoldens(currentGoldens,
+ Map<String, List<Map<String, String>>> newGoldens, List<TestSuite> suites) {
+ for (var suite in suites) {
+ newGoldens[suite.key] = suite.regenerateGoldens(currentGoldens[suite.key]);
+ }
+}
+
+void testGoldens(currentGoldens, List<TestSuite> suites) {
+ for (var suite in suites) {
+ suite.verifyGoldens(currentGoldens[suite.key]);
+ }
+}
+
+void main(List<String> args) {
+ var goldenFile = 'pkg/compiler/test/js/size_estimator_expectations.json';
+ bool generate = args.contains('-g');
+ var currentGoldens = json.jsonDecode(File(goldenFile).readAsStringSync());
+
+ if (generate) {
+ Map<String, List<Map<String, String>>> newGoldens = {};
+ generateGoldens(currentGoldens, newGoldens, testSuites);
+
+ File(goldenFile).writeAsStringSync(
+ json.JsonEncoder.withIndent(' ').convert(newGoldens));
+ } else {
+ testGoldens(currentGoldens, testSuites);
+ }
+}
diff --git a/pkg/compiler/test/js/size_estimator_expectations.json b/pkg/compiler/test/js/size_estimator_expectations.json
new file mode 100644
index 0000000..57b911f
--- /dev/null
+++ b/pkg/compiler/test/js/size_estimator_expectations.json
@@ -0,0 +1,196 @@
+{
+ "expressions": [
+ {
+ "original": "1",
+ "expected": "1",
+ "minified": "1"
+ },
+ {
+ "original": "123456789",
+ "expected": "123456789",
+ "minified": "123456789"
+ },
+ {
+ "original": "true",
+ "expected": "!0",
+ "minified": "true"
+ },
+ {
+ "original": "false",
+ "expected": "!1",
+ "minified": "false"
+ },
+ {
+ "original": "a || b",
+ "expected": "#||#",
+ "minified": "a||b"
+ },
+ {
+ "original": "(a || b) && c",
+ "expected": "(#||#)&&#",
+ "minified": "(a||b)&&c"
+ },
+ {
+ "original": "a == true ? b : c",
+ "expected": "#==!0?#:#",
+ "minified": "a==true?b:c"
+ },
+ {
+ "original": "x = a + b * c",
+ "expected": "#=#+#*#",
+ "minified": "x=a+b*c"
+ },
+ {
+ "original": "a + (b == c) + d",
+ "expected": "#+(#==#)+#",
+ "minified": "a+(b==c)+d"
+ },
+ {
+ "original": "foo(bar)",
+ "expected": "#(#)",
+ "minified": "foo(bar)"
+ },
+ {
+ "original": "foo.bar(baz)",
+ "expected": "#.bar(#)",
+ "minified": "foo.bar(baz)"
+ },
+ {
+ "original": "foo({meaning: 42})",
+ "expected": "#({meaning:42})",
+ "minified": "foo({meaning:42})"
+ },
+ {
+ "original": "x = !x",
+ "expected": "#=!#",
+ "minified": "x=!x"
+ },
+ {
+ "original": "delete foo.bar",
+ "expected": "delete #.bar",
+ "minified": "delete foo.bar"
+ },
+ {
+ "original": "x in y",
+ "expected": "# in #",
+ "minified": "x in y"
+ },
+ {
+ "original": "x instanceof y",
+ "expected": "# instanceof #",
+ "minified": "x instanceof y"
+ },
+ {
+ "original": "x &= ~mask",
+ "expected": "#&=~#",
+ "minified": "x&=~mask"
+ },
+ {
+ "original": "await x++",
+ "expected": "await #++",
+ "minified": "await x++"
+ },
+ {
+ "original": "foo[x[bar]]",
+ "expected": "#[#[#]]",
+ "minified": "foo[x[bar]]"
+ },
+ {
+ "original": "x << 5",
+ "expected": "#<<5",
+ "minified": "x<<5"
+ },
+ {
+ "original": "x = ['a', 'b', 'c']",
+ "expected": "#=['a','b','c']",
+ "minified": "x=['a','b','c']"
+ },
+ {
+ "original": "a = {'b': 1, 'c': 2}",
+ "expected": "#={b:1,c:2}",
+ "minified": "a={b:1,c:2}"
+ },
+ {
+ "original": "foo([1, 2, 3])",
+ "expected": "#([1,2,3])",
+ "minified": "foo([1,2,3])"
+ },
+ {
+ "original": "a = b = c",
+ "expected": "#=#=#",
+ "minified": "a=b=c"
+ },
+ {
+ "original": "var a = \"\"",
+ "expected": "var #=\"\"",
+ "minified": "var a=\"\""
+ }
+ ],
+ "statements": [
+ {
+ "original": "{ 1; 2; 3; }",
+ "expected": "{1;2;3;}",
+ "minified": "{1;2;3}"
+ },
+ {
+ "original": "function foo() { /a/; 1; 2; }",
+ "expected": "function #(){/a/;1;2;}",
+ "minified": "function foo(){/a/;1;2}"
+ },
+ {
+ "original": "function foo() { return function bar() { return null; } }",
+ "expected": "function #(){return function #(){return null;};}",
+ "minified": "function foo(){return function bar(){return null}}"
+ },
+ {
+ "original": "if (x == true) { bar(); }",
+ "expected": "if(#==!0)#()",
+ "minified": "if(x==true)bar()"
+ },
+ {
+ "original": "if (x > 4 && y < 5) { 1; 2; }",
+ "expected": "if(#>4&&#<5){1;2;}",
+ "minified": "if(x>4&&y<5){1;2}"
+ },
+ {
+ "original": "if (x == true) { return true; } else if (y < 3 || z > 5) { return l != null ? 'a' : 4; } else { foo(); return; }",
+ "expected": "if(#==!0)return !0;else if(#<3||#>5)return #!=null?'a':4;else{#();return;}",
+ "minified": "if(x==true)return true;else if(y<3||z>5)return l!=null?'a':4;else{foo();return}"
+ },
+ {
+ "original": "for (var a = 0; a < 10; a++) { foo(a); }",
+ "expected": "for(var #=0;#<10;#++)#(#)",
+ "minified": "for(var a=0;a<10;a++)foo(a)"
+ },
+ {
+ "original": "for (var b in c) { var e = 1; foo(e); }",
+ "expected": "for(var # in #){var #=1;#(#);}",
+ "minified": "for(var b in c){var e=1;foo(e)}"
+ },
+ {
+ "original": "while (x != null) { foo(); }",
+ "expected": "while(#!=null)#()",
+ "minified": "while(x!=null)foo()"
+ },
+ {
+ "original": "do { print(1); do while (true) ; while (false); } while (a != b);",
+ "expected": "do{#(1);do while(!0);while(!1);}while(#!=#)",
+ "minified": "do{print(1);do while(true);while(false)}while(a!=b)"
+ },
+ {
+ "original": "switch (foo) { case 'a': case 'b': bar(); break; case 'c': 1; break; default: boo(); }",
+ "expected": "switch(#){case 'a':case 'b':#();break;case 'c':1;break;default:#();}",
+ "minified": "switch(foo){case'a':case'b':bar();break;case'c':1;break;default:boo()}"
+ },
+ {
+ "original": "foo.prototype.Goo = function(a) { return a.bar(); }",
+ "expected": "#.prototype.Goo=function(#){return #.bar();}",
+ "minified": "foo.prototype.Goo=function(a){return a.bar()}"
+ },
+ {
+ "original": "try { null = 4; } catch (e) { print(e); }",
+ "expected": "try{null=4;}catch(#){#(#);}",
+ "minified": "try{null=4}catch(e){print(e)}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 9571c2a..5d79c5f 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4428,6 +4428,33 @@
js_ast.Expression _emitMethodCall(Expression receiver, Member target,
Arguments arguments, InvocationExpression node) {
var name = node.name.text;
+
+ /// Returns `true` when [node] represents an invocation of `List.add()` that
+ /// can be optimized.
+ ///
+ /// The optimized add operation can skip checks for a growable or modifiable
+ /// list and the element type is known to be invariant so it can skip the
+ /// type check.
+ bool isNativeListInvariantAdd(InvocationExpression node) {
+ if (node is MethodInvocation &&
+ node.isInvariant &&
+ node.name.name == 'add') {
+ // The call to add is marked as invariant, so the type check on the
+ // parameter to add is not needed.
+ var receiver = node.receiver;
+ if (receiver is VariableGet && receiver.variable.isFinal) {
+ // The receiver is a final variable, so it only contains the
+ // initializer value.
+ if (receiver.variable.initializer is ListLiteral) {
+ // The initializer is a list literal, so we know the list can be
+ // grown, modified, and is represented by a JavaScript Array.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
if (isOperatorMethodName(name) && arguments.named.isEmpty) {
var argLength = arguments.positional.length;
if (argLength == 0) {
@@ -4441,6 +4468,10 @@
var jsReceiver = _visitExpression(receiver);
var args = _emitArgumentList(arguments, target: target);
+ if (isNativeListInvariantAdd(node)) {
+ return js.call('#.push(#)', [jsReceiver, args]);
+ }
+
var isCallingDynamicField = target is Member &&
target.hasGetter &&
_isDynamicOrFunction(target.getterType);
diff --git a/pkg/js_ast/lib/src/nodes.dart b/pkg/js_ast/lib/src/nodes.dart
index 82cdc4f..7a9298a 100644
--- a/pkg/js_ast/lib/src/nodes.dart
+++ b/pkg/js_ast/lib/src/nodes.dart
@@ -444,6 +444,8 @@
bool get isCommaOperator => false;
+ bool get isFinalized => true;
+
Statement toStatement() {
throw new UnsupportedError('toStatement');
}
@@ -1005,6 +1007,9 @@
LiteralStringFromName(this.name) : super(null);
+ @override
+ bool get isFinalized => name.isFinalized;
+
String get value => '"${name.name}"';
void visitChildren<T>(NodeVisitor<T> visitor) {
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index f3875f3..b76ee89 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -145,6 +145,12 @@
/// being visited, or null.
Element _currentClassOrExtension;
+ /// If an extension declaration is being visited, the decorated type of the
+ /// type appearing in the `on` clause (this is the type of `this` inside the
+ /// extension declaration). Null if an extension declaration is not being
+ /// visited.
+ DecoratedType _currentExtendedType;
+
/// The [DecoratedType] of the innermost list or set literal being visited, or
/// `null` if the visitor is not inside any list or set.
///
@@ -792,9 +798,12 @@
}
DecoratedType visitExtensionDeclaration(ExtensionDeclaration node) {
- visitClassOrMixinOrExtensionDeclaration(node);
_dispatch(node.typeParameters);
_dispatch(node.extendedType);
+ _currentExtendedType =
+ _variables.decoratedTypeAnnotation(source, node.extendedType);
+ visitClassOrMixinOrExtensionDeclaration(node);
+ _currentExtendedType = null;
return null;
}
@@ -3148,22 +3157,8 @@
.toList());
} else {
assert(_currentClassOrExtension is ExtensionElement);
- final type = (_currentClassOrExtension as ExtensionElement).extendedType;
-
- if (type is InterfaceType) {
- var index = 0;
- return DecoratedType(type, NullabilityNode.forInferredType(target),
- typeArguments: type.typeArguments
- .map((t) => DecoratedType(
- t,
- NullabilityNode.forInferredType(
- target.typeArgument(index++))))
- .toList());
- } else if (type is TypeParameterType) {
- return DecoratedType(type, NullabilityNode.forInferredType(target));
- } else {
- _unimplemented(node, 'extension of $type (${type.runtimeType}');
- }
+ assert(_currentExtendedType != null);
+ return _currentExtendedType;
}
}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 1eb3737..d3075e1 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -2217,6 +2217,28 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_extension_on_generic_type() async {
+ var content = '''
+class C<T> {
+ final T value;
+ C(this.value);
+}
+extension E<T> on Future<C<T/*?*/>> {
+ Future<T> get asyncValue async => (await this).value;
+}
+''';
+ var expected = '''
+class C<T> {
+ final T value;
+ C(this.value);
+}
+extension E<T> on Future<C<T?>> {
+ Future<T?> get asyncValue async => (await this).value;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_extension_on_type_param_implementation() async {
var content = '''
abstract class C {
@@ -6408,11 +6430,11 @@
await _checkSingleFileChanges(content, expected);
}
- @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39387')
Future<void> test_this_inside_extension() async {
var content = '''
class C<T> {
T field;
+ C(this.field);
}
extension on C<int> {
f() {
@@ -6421,14 +6443,14 @@
}
extension on C<List<int>> {
f() {
- this.field = null;
+ this.field = [null];
}
}
''';
var expected = '''
-
class C<T> {
T field;
+ C(this.field);
}
extension on C<int?> {
f() {
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index 001a94b..e8f81c6 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -2929,7 +2929,6 @@
// metadata was visited.
}
- @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39387')
Future<void> test_extension_on_class_with_generic_type_arguments() async {
await analyze('''
class C<T> {}
@@ -2942,7 +2941,6 @@
// adding assertion(s).
}
- @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/39387')
Future<void> test_extension_on_function_type() async {
await analyze('''
extension CurryFunction<R, S, T> on R Function(S, T) {
diff --git a/pkg/test_runner/lib/src/test_configurations.dart b/pkg/test_runner/lib/src/test_configurations.dart
index 4d3925a..3179fec 100644
--- a/pkg/test_runner/lib/src/test_configurations.dart
+++ b/pkg/test_runner/lib/src/test_configurations.dart
@@ -149,6 +149,12 @@
// vm tests contain both cc tests (added here) and dart tests (added
// in [TEST_SUITE_DIRECTORIES]).
testSuites.add(VMTestSuite(configuration));
+ } else if (key == 'ffi_unit') {
+ // 'ffi_unit' contains cc non-DartVM unit tests.
+ //
+ // This is a separate suite from 'ffi', because we want to run the
+ // 'ffi' suite on many architectures, but 'ffi_unit' only on one.
+ testSuites.add(FfiTestSuite(configuration));
} else if (configuration.compiler == Compiler.dart2analyzer) {
if (key == 'analyze_library') {
testSuites.add(AnalyzeLibraryTestSuite(configuration));
diff --git a/pkg/test_runner/lib/src/test_suite.dart b/pkg/test_runner/lib/src/test_suite.dart
index e788744..53af2c7 100644
--- a/pkg/test_runner/lib/src/test_suite.dart
+++ b/pkg/test_runner/lib/src/test_suite.dart
@@ -364,6 +364,128 @@
VMUnitTest(this.name, this.expectation);
}
+/// A specialized [TestSuite] that runs tests written in C to unit test
+/// the standalone (non-DartVM) C/C++ code.
+///
+/// The tests are compiled into an executable for all [targetAbis] by the
+/// build step.
+/// An executable lists its tests when run with the --list command line flag.
+/// Individual tests are run by specifying them on the command line.
+class FfiTestSuite extends TestSuite {
+ Map<String, String> runnerPaths;
+ final String dartDir;
+
+ static const targetAbis = [
+ "arm64_android",
+ "arm64_ios",
+ "arm64_linux",
+ "arm64_macos",
+ "arm_android",
+ "arm_ios",
+ "arm_linux",
+ "ia32_android",
+ "ia32_linux",
+ "ia32_win",
+ "x64_ios",
+ "x64_linux",
+ "x64_macos",
+ "x64_win",
+ ];
+
+ FfiTestSuite(TestConfiguration configuration)
+ : dartDir = Repository.dir.toNativePath(),
+ super(configuration, "ffi_unit", []) {
+ final binarySuffix = Platform.operatingSystem == 'windows' ? '.exe' : '';
+
+ // For running the tests we use multiple binaries, one for each target ABI.
+ runnerPaths = Map.fromIterables(
+ targetAbis,
+ targetAbis.map((String config) =>
+ '$buildDir/run_ffi_unit_tests_$config$binarySuffix'));
+ }
+
+ void findTestCases(TestCaseEvent onTest, Map testCache) {
+ final statusFiles =
+ statusFilePaths.map((statusFile) => "$dartDir/$statusFile").toList();
+ final expectations = ExpectationSet.read(statusFiles, configuration);
+
+ runnerPaths.forEach((runnerName, runnerPath) {
+ try {
+ for (final test in _listTests(runnerName, runnerPath)) {
+ _addTest(expectations, test, onTest);
+ }
+ } catch (error, s) {
+ print(
+ "Fatal error occurred while parsing tests from $runnerName: $error");
+ print(s);
+ exit(1);
+ }
+ });
+ }
+
+ void _addTest(
+ ExpectationSet testExpectations, FfiUnitTest test, TestCaseEvent onTest) {
+ final fullName = '${test.runnerName}/${test.name}';
+ var expectations = testExpectations.expectations(fullName);
+
+ // Get the expectation from the test itself.
+ final testExpectation = Expectation.find(test.expectation);
+
+ // Update the legacy status-file based expectations to include
+ // [testExpectation].
+ if (testExpectation != Expectation.pass) {
+ expectations = {...expectations, testExpectation};
+ expectations.remove(Expectation.pass);
+ }
+
+ // Update the new workflow based expectations to include [testExpectation].
+ final testFile = TestFile.vmUnitTest(
+ hasSyntaxError: false,
+ hasCompileError: testExpectation == Expectation.compileTimeError,
+ hasRuntimeError: testExpectation == Expectation.runtimeError,
+ hasStaticWarning: false,
+ hasCrash: testExpectation == Expectation.crash);
+
+ final args = [
+ // This test has no VM, but pipe through vmOptions as test options.
+ // Passing `--vm-options=--update` will update all test expectations.
+ ...configuration.vmOptions,
+ test.name,
+ ];
+ final command = ProcessCommand(
+ 'run_ffi_unit_test', test.runnerPath, args, environmentOverrides);
+
+ _addTestCase(testFile, fullName, [command], expectations, onTest);
+ }
+
+ Iterable<FfiUnitTest> _listTests(String runnerName, String runnerPath) {
+ final result = Process.runSync(runnerPath, ["--list"]);
+ if (result.exitCode != 0) {
+ throw "Failed to list tests: '$runnerPath --list'. "
+ "Process exited with ${result.exitCode}";
+ }
+
+ return (result.stdout as String)
+ .split('\n')
+ .map((line) => line.trim())
+ .where((name) => name.isNotEmpty)
+ .map((String line) {
+ final parts = line.split(' ');
+ assert(parts.length == 2);
+ return FfiUnitTest(runnerName, runnerPath, parts[0], parts[1]);
+ });
+ }
+}
+
+class FfiUnitTest {
+ final String runnerName;
+ final String runnerPath;
+ final String name;
+ final String expectation;
+
+ FfiUnitTest(this.runnerName, this.runnerPath, this.name, this.expectation);
+}
+
/// A standard [TestSuite] implementation that searches for tests in a
/// directory, and creates [TestCase]s that compile and/or run them.
class StandardTestSuite extends TestSuite {
diff --git a/pkg/test_runner/tool/update_static_error_tests.dart b/pkg/test_runner/tool/update_static_error_tests.dart
index 5364fc2..7432ff2 100644
--- a/pkg/test_runner/tool/update_static_error_tests.dart
+++ b/pkg/test_runner/tool/update_static_error_tests.dart
@@ -301,7 +301,7 @@
return errors;
}
-/// Find the most recently-built [binary] in any of the build directories.
+/// Find the most recently-built binary [name] in any of the build directories.
String _findBinary(String name, String windowsExtension) {
var binary = Platform.isWindows ? "$name.$windowsExtension" : name;
diff --git a/pkg/vm_service/test/get_stack_test.dart b/pkg/vm_service/test/get_stack_test.dart
index 6e61379..0f65938 100644
--- a/pkg/vm_service/test/get_stack_test.dart
+++ b/pkg/vm_service/test/get_stack_test.dart
@@ -74,17 +74,17 @@
]);
expectFrames(result.asyncCausalFrames, [
- [equals('AsyncCausal'), endsWith(' func10')],
- [equals('AsyncCausal'), endsWith(' func9')],
- [equals('AsyncCausal'), endsWith(' func8')],
- [equals('AsyncCausal'), endsWith(' func7')],
- [equals('AsyncCausal'), endsWith(' func6')],
- [equals('AsyncCausal'), endsWith(' func5')],
- [equals('AsyncCausal'), endsWith(' func4')],
- [equals('AsyncCausal'), endsWith(' func3')],
- [equals('AsyncCausal'), endsWith(' func2')],
- [equals('AsyncCausal'), endsWith(' func1')],
- [equals('AsyncCausal'), endsWith(' testMain')],
+ [equals('Regular'), endsWith(' func10')],
+ [equals('Regular'), endsWith(' func9')],
+ [equals('Regular'), endsWith(' func8')],
+ [equals('Regular'), endsWith(' func7')],
+ [equals('Regular'), endsWith(' func6')],
+ [equals('Regular'), endsWith(' func5')],
+ [equals('Regular'), endsWith(' func4')],
+ [equals('Regular'), endsWith(' func3')],
+ [equals('Regular'), endsWith(' func2')],
+ [equals('Regular'), endsWith(' func1')],
+ [equals('Regular'), endsWith(' testMain')],
]);
expectFrames(result.awaiterFrames, [
@@ -126,7 +126,7 @@
]);
expectFrames(result.asyncCausalFrames, [
- [equals('AsyncCausal'), endsWith(' func10')],
+ [equals('Regular'), endsWith(' func10')],
[equals('AsyncSuspensionMarker'), isNull],
[equals('AsyncCausal'), endsWith(' func9')],
[equals('AsyncSuspensionMarker'), isNull],
diff --git a/runtime/bin/ffi_unit_test/BUILD.gn b/runtime/bin/ffi_unit_test/BUILD.gn
new file mode 100644
index 0000000..d7c93ba
--- /dev/null
+++ b/runtime/bin/ffi_unit_test/BUILD.gn
@@ -0,0 +1,187 @@
+# 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("../../platform/platform_sources.gni")
+import("../../vm/compiler/compiler_sources.gni")
+import("../../vm/vm_sources.gni")
+
+template("build_run_ffi_unit_tests") {
+ extra_configs = []
+ if (defined(invoker.extra_configs)) {
+ extra_configs += invoker.extra_configs
+ }
+
+ executable(target_name) {
+ configs += [] + extra_configs
+
+ defines = [
+ "TESTING",
+ "FFI_UNIT_TESTS",
+ ]
+
+ include_dirs = [
+ "../..",
+ "//third_party",
+ ]
+
+ constants = rebase_path(constants_sources, ".", "../../vm")
+ ffi_tests = rebase_path(ffi_sources_tests, ".", "../../vm/compiler")
+ platform = rebase_path(platform_sources, ".", "../../platform")
+
+ sources = [ "run_ffi_unit_tests.cc" ] + constants + ffi_tests + platform
+ }
+}
+
+config("define_target_arch_arm") {
+ defines = [ "TARGET_ARCH_ARM" ]
+}
+
+config("define_target_arch_arm64") {
+ defines = [ "TARGET_ARCH_ARM64" ]
+}
+
+config("define_target_arch_ia32") {
+ defines = [ "TARGET_ARCH_IA32" ]
+}
+
+config("define_target_arch_x64") {
+ defines = [ "TARGET_ARCH_X64" ]
+}
+
+config("define_target_os_android") {
+ defines = [ "TARGET_OS_ANDROID" ]
+}
+
+config("define_target_os_ios") {
+ defines = [ "TARGET_OS_IOS" ]
+}
+
+config("define_target_os_linux") {
+ defines = [ "TARGET_OS_LINUX" ]
+}
+
+config("define_target_os_macos") {
+ defines = [ "TARGET_OS_MACOS" ]
+}
+
+config("define_target_os_windows") {
+ defines = [ "TARGET_OS_WINDOWS" ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_arm_android") {
+ extra_configs = [
+ ":define_target_arch_arm",
+ ":define_target_os_android",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_arm_ios") {
+ extra_configs = [
+ ":define_target_arch_arm",
+ ":define_target_os_ios",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_arm_linux") {
+ extra_configs = [
+ ":define_target_arch_arm",
+ ":define_target_os_linux",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_arm64_android") {
+ extra_configs = [
+ ":define_target_arch_arm64",
+ ":define_target_os_android",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_arm64_ios") {
+ extra_configs = [
+ ":define_target_arch_arm64",
+ ":define_target_os_ios",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_arm64_macos") {
+ extra_configs = [
+ ":define_target_arch_arm64",
+ ":define_target_os_macos",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_arm64_linux") {
+ extra_configs = [
+ ":define_target_arch_arm64",
+ ":define_target_os_linux",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_ia32_android") {
+ extra_configs = [
+ ":define_target_arch_ia32",
+ ":define_target_os_android",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_ia32_linux") {
+ extra_configs = [
+ ":define_target_arch_ia32",
+ ":define_target_os_linux",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_ia32_win") {
+ extra_configs = [
+ ":define_target_arch_ia32",
+ ":define_target_os_windows",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_x64_ios") {
+ extra_configs = [
+ ":define_target_arch_x64",
+ ":define_target_os_ios",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_x64_linux") {
+ extra_configs = [
+ ":define_target_arch_x64",
+ ":define_target_os_linux",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_x64_macos") {
+ extra_configs = [
+ ":define_target_arch_x64",
+ ":define_target_os_macos",
+ ]
+}
+
+build_run_ffi_unit_tests("run_ffi_unit_tests_x64_win") {
+ extra_configs = [
+ ":define_target_arch_x64",
+ ":define_target_os_windows",
+ ]
+}
+
+group("run_ffi_unit_tests") {
+ deps = [
+ ":run_ffi_unit_tests_arm64_android",
+ ":run_ffi_unit_tests_arm64_ios", # No other test coverage.
+ ":run_ffi_unit_tests_arm64_linux",
+ ":run_ffi_unit_tests_arm64_macos",
+ ":run_ffi_unit_tests_arm_android", # SoftFP
+ ":run_ffi_unit_tests_arm_ios", # No other test coverage.
+ ":run_ffi_unit_tests_arm_linux", # HardFP
+ ":run_ffi_unit_tests_ia32_android", # Emulator, no other test coverage.
+ ":run_ffi_unit_tests_ia32_linux",
+ ":run_ffi_unit_tests_ia32_win",
+ ":run_ffi_unit_tests_x64_ios", # Simulator, no other test coverage.
+ ":run_ffi_unit_tests_x64_linux",
+ ":run_ffi_unit_tests_x64_macos",
+ ":run_ffi_unit_tests_x64_win",
+ ]
+}
diff --git a/runtime/bin/ffi_unit_test/run_ffi_unit_tests.cc b/runtime/bin/ffi_unit_test/run_ffi_unit_tests.cc
new file mode 100644
index 0000000..8fe8784
--- /dev/null
+++ b/runtime/bin/ffi_unit_test/run_ffi_unit_tests.cc
@@ -0,0 +1,115 @@
+// 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.
+
+// A slimmed down version of bin/run_vm_tests.cc that only runs C++ non-DartVM
+// unit tests.
+//
+// By slimming it down to non-VM, we can run with the defines for all target
+// architectures and operating systems.
+
+#include "vm/compiler/ffi/unit_test.h"
+
+#include "platform/assert.h"
+#include "platform/syslog.h"
+
+namespace dart {
+namespace compiler {
+namespace ffi {
+
+static const char* const kNone = "No Test";
+static const char* const kList = "List all Tests";
+static const char* const kAll = "Run all Tests";
+static const char* run_filter = kNone;
+
+static const char* kCommandAll = "--all";
+static const char* kCommandList = "--list";
+static const char* kCommandUpdate = "--update";
+
+static int run_matches = 0;
+
+TestCaseBase* TestCaseBase::first_ = nullptr;
+TestCaseBase* TestCaseBase::tail_ = nullptr;
+bool TestCaseBase::update_expectations = false;
+
+TestCaseBase::TestCaseBase(const char* name, const char* expectation)
+ : next_(nullptr), name_(name), expectation_(expectation) {
+ ASSERT(strlen(expectation) > 0);
+ if (first_ == nullptr) {
+ first_ = this;
+ } else {
+ tail_->next_ = this;
+ }
+ tail_ = this;
+}
+
+void TestCaseBase::RunAll() {
+ TestCaseBase* test = first_;
+ while (test != nullptr) {
+ test->RunTest();
+ test = test->next_;
+ }
+}
+
+void TestCaseBase::RunTest() {
+ if (run_filter == kList) {
+ Syslog::Print("%s %s\n", this->name(), this->expectation());
+ run_matches++;
+ } else if (run_filter == kAll) {
+ this->Run();
+ run_matches++;
+ } else if (strcmp(run_filter, this->name()) == 0) {
+ this->Run();
+ run_matches++;
+ }
+}
+
+void RawTestCase::Run() {
+ Syslog::Print("Running test: %s\n", name());
+ (*run_)();
+ Syslog::Print("Done: %s\n", name());
+}
+
+static int Main(int argc, const char** argv) {
+ if (argc == 2 && strcmp(argv[1], kCommandList) == 0) {
+ run_filter = kList;
+ // List all tests and benchmarks and exit.
+ TestCaseBase::RunAll();
+ fflush(stdout);
+ return 0;
+ }
+ if (argc > 1 && strcmp(argv[1], kCommandUpdate) == 0) {
+ TestCaseBase::update_expectations = true;
+ }
+ if (strcmp(argv[argc - 1], kCommandAll) == 0) {
+ // Run all tests.
+ run_filter = kAll;
+ } else if (argc > 1) {
+ // Run only test with specific name.
+ run_filter = argv[argc - 1];
+ }
+
+ TestCaseBase::RunAll();
+
+ // Print a warning message if no tests or benchmarks were matched.
+ if (run_matches == 0) {
+ Syslog::PrintErr("No tests matched: %s\n", run_filter);
+ return 1;
+ }
+ if (Expect::failed()) {
+ Syslog::PrintErr(
+ "Some tests failed. Run the following command to update "
+ "expectations.\ntools/test.py --vm-options=--update ffi_unit");
+ return 255;
+ }
+
+ return 0;
+}
+
+} // namespace ffi
+} // namespace compiler
+} // namespace dart
+
+int main(int argc, const char** argv) {
+ return dart::compiler::ffi::Main(argc, argv);
+}
diff --git a/runtime/platform/globals.h b/runtime/platform/globals.h
index ab51780..79b6a6b 100644
--- a/runtime/platform/globals.h
+++ b/runtime/platform/globals.h
@@ -339,17 +339,17 @@
// Verify that host and target architectures match, we cannot
// have a 64 bit Dart VM generating 32 bit code or vice-versa.
#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64)
-#if !defined(ARCH_IS_64_BIT)
+#if !defined(ARCH_IS_64_BIT) && !defined(FFI_UNIT_TESTS)
#error Mismatched Host/Target architectures.
-#endif // !defined(ARCH_IS_64_BIT)
+#endif // !defined(ARCH_IS_64_BIT) && !defined(FFI_UNIT_TESTS)
#elif defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_ARM)
#if defined(HOST_ARCH_X64) && defined(TARGET_ARCH_ARM)
// This is simarm_x64, which is the only case where host/target architecture
-// mismatch is allowed.
+// mismatch is allowed. Unless, we're running FFI unit tests.
#define IS_SIMARM_X64 1
-#elif !defined(ARCH_IS_32_BIT)
+#elif !defined(ARCH_IS_32_BIT) && !defined(FFI_UNIT_TESTS)
#error Mismatched Host/Target architectures.
-#endif // !defined(ARCH_IS_32_BIT)
+#endif // !defined(ARCH_IS_32_BIT) && !defined(FFI_UNIT_TESTS)
#endif // defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_ARM)
// Determine whether we will be using the simulator.
diff --git a/runtime/tools/embedder_layering_check.py b/runtime/tools/embedder_layering_check.py
index d49b705..8e33bf0 100644
--- a/runtime/tools/embedder_layering_check.py
+++ b/runtime/tools/embedder_layering_check.py
@@ -20,7 +20,9 @@
# Tests that don't match the simple case of *_test.cc.
EXTRA_TEST_FILES = [
- 'runtime/bin/run_vm_tests.cc', 'runtime/vm/libfuzzer/dart_libfuzzer.cc'
+ 'runtime/bin/run_vm_tests.cc',
+ 'runtime/bin/ffi_unit_test/run_ffi_unit_tests.cc',
+ 'runtime/vm/libfuzzer/dart_libfuzzer.cc'
]
diff --git a/runtime/vm/compilation_trace.cc b/runtime/vm/compilation_trace.cc
index 0db337d..5a86366 100644
--- a/runtime/vm/compilation_trace.cc
+++ b/runtime/vm/compilation_trace.cc
@@ -744,6 +744,8 @@
fields_ = cls_.fields();
}
+ SafepointWriteRwLocker ml(thread_,
+ thread_->isolate_group()->program_lock());
for (intptr_t i = 0; i < num_fields; i++) {
field_name_ = ReadString();
intptr_t guarded_cid = cid_map_[ReadInt()];
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index dd1d2fa..386dd86 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -204,3 +204,10 @@
"assembler/disassembler_arm64.cc",
"assembler/disassembler_x86.cc",
]
+
+ffi_sources_tests = [
+ "ffi/native_calling_convention_test.cc",
+ "ffi/native_location_test.cc",
+ "ffi/native_type_test.cc",
+ "ffi/unit_test_custom_zone.cc",
+]
diff --git a/runtime/vm/compiler/ffi/native_calling_convention.cc b/runtime/vm/compiler/ffi/native_calling_convention.cc
index fe3881c..5b21cf2 100644
--- a/runtime/vm/compiler/ffi/native_calling_convention.cc
+++ b/runtime/vm/compiler/ffi/native_calling_convention.cc
@@ -6,9 +6,12 @@
#include "vm/compiler/ffi/native_location.h"
#include "vm/compiler/ffi/native_type.h"
-#include "vm/cpu.h"
#include "vm/zone_text_buffer.h"
+#if !defined(FFI_UNIT_TESTS)
+#include "vm/cpu.h"
+#endif
+
namespace dart {
namespace compiler {
@@ -17,6 +20,7 @@
const intptr_t kNoFpuRegister = -1;
+#if !defined(FFI_UNIT_TESTS)
// In Soft FP, floats and doubles get passed in integer registers.
static bool SoftFpAbi() {
#if defined(TARGET_ARCH_ARM)
@@ -25,6 +29,15 @@
return false;
#endif
}
+#else // !defined(FFI_UNIT_TESTS)
+static bool SoftFpAbi() {
+#if defined(TARGET_ARCH_ARM) && defined(TARGET_OS_ANDROID)
+ return true;
+#else
+ return false;
+#endif
+}
+#endif // !defined(FFI_UNIT_TESTS)
// In Soft FP, floats are treated as 4 byte ints, and doubles as 8 byte ints.
static const NativeType& ConvertIfSoftFp(Zone* zone, const NativeType& rep) {
@@ -293,9 +306,11 @@
return textBuffer.buffer();
}
+#if !defined(FFI_UNIT_TESTS)
const char* NativeCallingConvention::ToCString(bool multi_line) const {
return ToCString(Thread::Current()->zone(), multi_line);
}
+#endif
} // namespace ffi
diff --git a/runtime/vm/compiler/ffi/native_calling_convention.h b/runtime/vm/compiler/ffi/native_calling_convention.h
index bb10887..9faf620 100644
--- a/runtime/vm/compiler/ffi/native_calling_convention.h
+++ b/runtime/vm/compiler/ffi/native_calling_convention.h
@@ -42,7 +42,9 @@
void PrintTo(BaseTextBuffer* f, bool multi_line = false) const;
void PrintToMultiLine(BaseTextBuffer* f) const;
const char* ToCString(Zone* zone, bool multi_line = false) const;
+#if !defined(FFI_UNIT_TESTS)
const char* ToCString(bool multi_line = false) const;
+#endif
private:
NativeCallingConvention(const NativeLocations& argument_locations,
diff --git a/runtime/vm/compiler/ffi/native_calling_convention_test.cc b/runtime/vm/compiler/ffi/native_calling_convention_test.cc
new file mode 100644
index 0000000..23ed777
--- /dev/null
+++ b/runtime/vm/compiler/ffi/native_calling_convention_test.cc
@@ -0,0 +1,138 @@
+// 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.
+
+#include "vm/compiler/ffi/unit_test.h"
+
+#include "platform/syslog.h"
+#include "vm/compiler/ffi/native_calling_convention.h"
+
+namespace dart {
+namespace compiler {
+namespace ffi {
+
+#if defined(TARGET_ARCH_ARM)
+const char* kArch = "arm";
+#elif defined(TARGET_ARCH_ARM64)
+const char* kArch = "arm64";
+#elif defined(TARGET_ARCH_IA32)
+const char* kArch = "ia32";
+#elif defined(TARGET_ARCH_X64)
+const char* kArch = "x64";
+#endif
+
+#if defined(TARGET_OS_ANDROID)
+const char* kOs = "android";
+#elif defined(TARGET_OS_IOS)
+const char* kOs = "ios";
+#elif defined(TARGET_OS_LINUX)
+const char* kOs = "linux";
+#elif defined(TARGET_OS_MACOS)
+const char* kOs = "macos";
+#elif defined(TARGET_OS_WINDOWS)
+const char* kOs = "win";
+#endif
+
+void WriteToFile(char* path, const char* contents) {
+ FILE* file;
+ file = fopen(path, "w");
+ if (file != nullptr) {
+ fprintf(file, "%s", contents);
+ } else {
+ Syslog::Print("Error %d \n", errno);
+ }
+ fclose(file);
+}
+
+void ReadFromFile(char* path, char** buffer_pointer) {
+ FILE* file = fopen(path, "rb");
+ if (file == nullptr) {
+ Syslog::Print("Error %d \n", errno);
+ return;
+ }
+
+ fseek(file, 0, SEEK_END);
+ size_t size = ftell(file);
+ rewind(file);
+
+ char* buffer = reinterpret_cast<char*>(malloc(sizeof(char) * (size + 1)));
+
+ fread(buffer, 1, size, file);
+ buffer[size] = 0;
+
+ fclose(file);
+ *buffer_pointer = buffer;
+}
+
+void RunSignatureTest(dart::Zone* zone,
+ const char* name,
+ const NativeTypes& argument_types,
+ const NativeType& return_type) {
+ const auto& native_signature =
+ *new (zone) NativeFunctionType(argument_types, return_type);
+
+ const auto& native_calling_convention =
+ NativeCallingConvention::FromSignature(zone, native_signature);
+
+ const char* test_result =
+ native_calling_convention.ToCString(zone, /*multi_line=*/true);
+
+ const int kFilePathLength = 100;
+ char expectation_file_path[kFilePathLength];
+ Utils::SNPrint(expectation_file_path, kFilePathLength,
+ "runtime/vm/compiler/ffi/unit_tests/%s/%s_%s.expect", name,
+ kArch, kOs);
+
+ if (TestCaseBase::update_expectations) {
+ Syslog::Print("Updating %s\n", expectation_file_path);
+ WriteToFile(expectation_file_path, test_result);
+ }
+
+ char* expectation_file_contents = nullptr;
+ ReadFromFile(expectation_file_path, &expectation_file_contents);
+ EXPECT_NOTNULL(expectation_file_contents);
+ if (expectation_file_contents != nullptr) {
+ EXPECT_STREQ(expectation_file_contents, test_result);
+ free(expectation_file_contents);
+ }
+}
+
+UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_int8x10) {
+ const auto& int8type = *new (Z) NativePrimitiveType(kInt8);
+
+ auto& arguments = *new (Z) NativeTypes(Z, 10);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+ arguments.Add(&int8type);
+
+ RunSignatureTest(Z, "int8x10", arguments, int8type);
+}
+
+UNIT_TEST_CASE_WITH_ZONE(NativeCallingConvention_floatx10) {
+ const auto& floatType = *new (Z) NativePrimitiveType(kFloat);
+
+ auto& arguments = *new (Z) NativeTypes(Z, 10);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+ arguments.Add(&floatType);
+
+ RunSignatureTest(Z, "floatx10", arguments, floatType);
+}
+
+} // namespace ffi
+} // namespace compiler
+} // namespace dart
diff --git a/runtime/vm/compiler/ffi/native_location.cc b/runtime/vm/compiler/ffi/native_location.cc
index dce51c1..12439aa 100644
--- a/runtime/vm/compiler/ffi/native_location.cc
+++ b/runtime/vm/compiler/ffi/native_location.cc
@@ -12,6 +12,7 @@
namespace ffi {
+#if !defined(FFI_UNIT_TESTS)
bool NativeLocation::LocationCanBeExpressed(Location loc, Representation rep) {
switch (loc.kind()) {
case Location::Kind::kRegister:
@@ -74,6 +75,7 @@
const Location loc = pair_loc.AsPairLocation()->At(index);
return FromLocation(zone, loc, rep);
}
+#endif
const NativeRegistersLocation& NativeLocation::AsRegisters() const {
ASSERT(IsRegisters());
@@ -90,6 +92,7 @@
return static_cast<const NativeStackLocation&>(*this);
}
+#if !defined(FFI_UNIT_TESTS)
Location NativeRegistersLocation::AsLocation() const {
ASSERT(IsExpressibleAsLocation());
switch (num_regs()) {
@@ -126,6 +129,7 @@
}
UNREACHABLE();
}
+#endif
NativeRegistersLocation& NativeRegistersLocation::Split(Zone* zone,
intptr_t index) const {
@@ -206,10 +210,12 @@
return other_stack.offset_in_bytes_ == offset_in_bytes_;
}
+#if !defined(FFI_UNIT_TESTS)
compiler::Address NativeLocationToStackSlotAddress(
const NativeStackLocation& loc) {
return compiler::Address(loc.base_register(), loc.offset_in_bytes());
}
+#endif
static void PrintRepresentations(BaseTextBuffer* f, const NativeLocation& loc) {
f->AddString(" ");
@@ -271,9 +277,11 @@
return textBuffer.buffer();
}
+#if !defined(FFI_UNIT_TESTS)
const char* NativeLocation::ToCString() const {
return ToCString(Thread::Current()->zone());
}
+#endif
intptr_t SizeFromFpuRegisterKind(enum FpuRegisterKind kind) {
switch (kind) {
diff --git a/runtime/vm/compiler/ffi/native_location.h b/runtime/vm/compiler/ffi/native_location.h
index c3cce14..3437e5c 100644
--- a/runtime/vm/compiler/ffi/native_location.h
+++ b/runtime/vm/compiler/ffi/native_location.h
@@ -10,17 +10,24 @@
#endif // defined(DART_PRECOMPILED_RUNTIME)
#include "platform/assert.h"
-#include "vm/compiler/backend/locations.h"
#include "vm/compiler/ffi/native_type.h"
#include "vm/constants.h"
#include "vm/growable_array.h"
+#if !defined(FFI_UNIT_TESTS)
+#include "vm/compiler/backend/locations.h"
+#endif
+
namespace dart {
class BaseTextBuffer;
namespace compiler {
+namespace target {
+extern const int kWordSize;
+}
+
namespace ffi {
class NativeRegistersLocation;
@@ -55,6 +62,7 @@
// inequality cannot be used to determine disjointness.
class NativeLocation : public ZoneAllocated {
public:
+#if !defined(FFI_UNIT_TESTS)
static bool LocationCanBeExpressed(Location loc, Representation rep);
static NativeLocation& FromLocation(Zone* zone,
Location loc,
@@ -63,6 +71,7 @@
Location loc,
Representation rep,
intptr_t index);
+#endif
// The type of the data at this location.
const NativeType& payload_type() const { return payload_type_; }
@@ -91,14 +100,18 @@
virtual bool IsStack() const { return false; }
virtual bool IsExpressibleAsLocation() const { return false; }
+#if !defined(FFI_UNIT_TESTS)
virtual Location AsLocation() const {
ASSERT(IsExpressibleAsLocation());
UNREACHABLE();
}
+#endif
virtual void PrintTo(BaseTextBuffer* f) const;
const char* ToCString(Zone* zone) const;
+#if !defined(FFI_UNIT_TESTS)
const char* ToCString() const;
+#endif
const NativeRegistersLocation& AsRegisters() const;
const NativeFpuRegistersLocation& AsFpuRegisters() const;
@@ -171,7 +184,9 @@
virtual bool IsExpressibleAsLocation() const {
return num_regs() == 1 || num_regs() == 2;
}
+#if !defined(FFI_UNIT_TESTS)
virtual Location AsLocation() const;
+#endif
intptr_t num_regs() const { return regs_->length(); }
Register reg_at(intptr_t index) const { return regs_->At(index); }
@@ -238,10 +253,12 @@
virtual bool IsExpressibleAsLocation() const {
return fpu_reg_kind_ == kQuadFpuReg;
}
+#if !defined(FFI_UNIT_TESTS)
virtual Location AsLocation() const {
ASSERT(IsExpressibleAsLocation());
return Location::FpuRegisterLocation(fpu_reg());
}
+#endif
FpuRegisterKind fpu_reg_kind() const { return fpu_reg_kind_; }
FpuRegister fpu_reg() const {
ASSERT(fpu_reg_kind_ == kQuadFpuReg);
@@ -299,6 +316,8 @@
size % compiler::target::kWordSize == 0 &&
(size_slots == 1 || size_slots == 2);
}
+
+#if !defined(FFI_UNIT_TESTS)
virtual Location AsLocation() const;
// ConstantInstr expects DoubleStackSlot for doubles, even on 64-bit systems.
@@ -308,6 +327,7 @@
ASSERT(compiler::target::kWordSize == 8);
return Location::DoubleStackSlot(offset_in_words(), base_register_);
}
+#endif
virtual NativeStackLocation& Split(Zone* zone, intptr_t index) const;
@@ -334,9 +354,11 @@
DISALLOW_COPY_AND_ASSIGN(NativeStackLocation);
};
+#if !defined(FFI_UNIT_TESTS)
// Return a memory operand for stack slot locations.
compiler::Address NativeLocationToStackSlotAddress(
const NativeStackLocation& loc);
+#endif
} // namespace ffi
diff --git a/runtime/vm/compiler/ffi/native_location_test.cc b/runtime/vm/compiler/ffi/native_location_test.cc
new file mode 100644
index 0000000..ea1fb27
--- /dev/null
+++ b/runtime/vm/compiler/ffi/native_location_test.cc
@@ -0,0 +1,40 @@
+// 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.
+
+#include "vm/compiler/ffi/unit_test.h"
+
+#include "vm/compiler/ffi/native_location.h"
+
+namespace dart {
+namespace compiler {
+namespace ffi {
+
+UNIT_TEST_CASE_WITH_ZONE(NativeStackLocation) {
+ const auto& native_type = *new (Z) NativePrimitiveType(kInt8);
+
+ const int kUnalignedStackLocation = 17;
+
+ const auto& native_location = *new (Z) NativeStackLocation(
+ native_type, native_type, SPREG, kUnalignedStackLocation);
+
+ EXPECT_EQ(kUnalignedStackLocation + native_type.SizeInBytes(),
+ native_location.StackTopInBytes());
+}
+
+UNIT_TEST_CASE_WITH_ZONE(NativeStackLocation_Split) {
+ const auto& native_type = *new (Z) NativePrimitiveType(kInt64);
+
+ const auto& native_location =
+ *new (Z) NativeStackLocation(native_type, native_type, SPREG, 0);
+
+ const auto& half_0 = native_location.Split(Z, 0);
+ const auto& half_1 = native_location.Split(Z, 1);
+
+ EXPECT_EQ(0, half_0.offset_in_bytes());
+ EXPECT_EQ(4, half_1.offset_in_bytes());
+}
+
+} // namespace ffi
+} // namespace compiler
+} // namespace dart
diff --git a/runtime/vm/compiler/ffi/native_type.cc b/runtime/vm/compiler/ffi/native_type.cc
index 7c1faae..db06400 100644
--- a/runtime/vm/compiler/ffi/native_type.cc
+++ b/runtime/vm/compiler/ffi/native_type.cc
@@ -10,9 +10,9 @@
#include "vm/constants.h"
#include "vm/zone_text_buffer.h"
-#if !defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
#include "vm/compiler/backend/locations.h"
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
+#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
namespace dart {
@@ -127,7 +127,7 @@
}
}
-#if !defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
bool NativePrimitiveType::IsExpressibleAsRepresentation() const {
switch (representation_) {
case kInt8:
@@ -170,7 +170,7 @@
UNREACHABLE();
}
}
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
+#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
bool NativePrimitiveType::Equals(const NativeType& other) const {
if (!other.IsPrimitive()) {
@@ -249,12 +249,14 @@
return *new (zone) NativePrimitiveType(fundamental_rep);
}
+#if !defined(FFI_UNIT_TESTS)
NativeType& NativeType::FromAbstractType(Zone* zone, const AbstractType& type) {
// TODO(36730): Support composites.
return NativeType::FromTypedDataClassId(zone, type.type_class_id());
}
+#endif
-#if !defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
static PrimitiveType fundamental_rep(Representation rep) {
switch (rep) {
case kUnboxedDouble:
@@ -277,7 +279,7 @@
Representation rep) {
return *new (zone) NativePrimitiveType(fundamental_rep(rep));
}
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
+#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
const char* NativeType::ToCString(Zone* zone) const {
ZoneTextBuffer textBuffer(zone);
@@ -285,9 +287,11 @@
return textBuffer.buffer();
}
+#if !defined(FFI_UNIT_TESTS)
const char* NativeType::ToCString() const {
return ToCString(Thread::Current()->zone());
}
+#endif
static const char* PrimitiveTypeToCString(PrimitiveType rep) {
switch (rep) {
@@ -334,9 +338,11 @@
return textBuffer.buffer();
}
+#if !defined(FFI_UNIT_TESTS)
const char* NativeFunctionType::ToCString() const {
return ToCString(Thread::Current()->zone());
}
+#endif
void NativeFunctionType::PrintTo(BaseTextBuffer* f) const {
f->AddString("(");
diff --git a/runtime/vm/compiler/ffi/native_type.h b/runtime/vm/compiler/ffi/native_type.h
index d2ca9c1..3c11341 100644
--- a/runtime/vm/compiler/ffi/native_type.h
+++ b/runtime/vm/compiler/ffi/native_type.h
@@ -9,11 +9,13 @@
#include "platform/globals.h"
#include "vm/allocation.h"
#include "vm/growable_array.h"
-#include "vm/object.h"
-#if !defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
#include "vm/compiler/backend/locations.h"
#endif
+#if !defined(FFI_UNIT_TESTS)
+#include "vm/object.h"
+#endif
namespace dart {
@@ -51,13 +53,15 @@
// TODO(36730): Add composites.
class NativeType : public ZoneAllocated {
public:
+#if !defined(FFI_UNIT_TESTS)
static NativeType& FromAbstractType(Zone* zone, const AbstractType& type);
+#endif
static NativeType& FromTypedDataClassId(Zone* zone, classid_t class_id);
-#if !defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
static NativePrimitiveType& FromUnboxedRepresentation(Zone* zone,
Representation rep);
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
+#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
virtual bool IsPrimitive() const { return false; }
const NativePrimitiveType& AsPrimitive() const;
@@ -79,7 +83,7 @@
// The alignment in bytes of this representation as member of a composite.
virtual intptr_t AlignmentInBytesField() const = 0;
-#if !defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
// NativeTypes which are available as unboxed Representations.
virtual bool IsExpressibleAsRepresentation() const { return false; }
@@ -91,7 +95,7 @@
const auto& widened = WidenTo4Bytes(zone_);
return widened.AsRepresentation();
}
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
+#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
virtual bool Equals(const NativeType& other) const { UNREACHABLE(); }
@@ -104,7 +108,9 @@
virtual void PrintTo(BaseTextBuffer* f) const;
const char* ToCString(Zone* zone) const;
+#if !defined(FFI_UNIT_TESTS)
const char* ToCString() const;
+#endif
virtual ~NativeType() {}
@@ -152,10 +158,10 @@
virtual intptr_t AlignmentInBytesStack() const;
virtual intptr_t AlignmentInBytesField() const;
-#if !defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
virtual bool IsExpressibleAsRepresentation() const;
virtual Representation AsRepresentation() const;
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
+#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
virtual bool Equals(const NativeType& other) const;
virtual NativePrimitiveType& Split(Zone* zone, intptr_t part) const;
@@ -181,8 +187,9 @@
void PrintTo(BaseTextBuffer* f) const;
const char* ToCString(Zone* zone) const;
-
+#if !defined(FFI_UNIT_TESTS)
const char* ToCString() const;
+#endif
private:
const NativeTypes& argument_types_;
diff --git a/runtime/vm/compiler/ffi/native_type_test.cc b/runtime/vm/compiler/ffi/native_type_test.cc
new file mode 100644
index 0000000..abf275d
--- /dev/null
+++ b/runtime/vm/compiler/ffi/native_type_test.cc
@@ -0,0 +1,25 @@
+// 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.
+
+#include "vm/compiler/ffi/unit_test.h"
+
+#include "vm/compiler/ffi/native_type.h"
+
+namespace dart {
+namespace compiler {
+namespace ffi {
+
+UNIT_TEST_CASE_WITH_ZONE(NativeType) {
+ const auto& native_type = *new (Z) NativePrimitiveType(kInt8);
+
+ EXPECT_EQ(1, native_type.SizeInBytes());
+ EXPECT(native_type.IsInt());
+ EXPECT(native_type.IsPrimitive());
+
+ EXPECT_STREQ("int8", native_type.ToCString(Z));
+}
+
+} // namespace ffi
+} // namespace compiler
+} // namespace dart
diff --git a/runtime/vm/compiler/ffi/unit_test.h b/runtime/vm/compiler/ffi/unit_test.h
new file mode 100644
index 0000000..44c4f0d
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_test.h
@@ -0,0 +1,85 @@
+// 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.
+
+// A slimmed down version of runtime/vm/unit_test.h that only runs C++
+// non-DartVM unit tests.
+
+#ifndef RUNTIME_VM_COMPILER_FFI_UNIT_TEST_H_
+#define RUNTIME_VM_COMPILER_FFI_UNIT_TEST_H_
+
+// Don't use the DartVM zone, so include this first.
+#include "vm/compiler/ffi/unit_test_custom_zone.h"
+
+#include "platform/globals.h"
+
+// The UNIT_TEST_CASE macro is used for tests.
+#define UNIT_TEST_CASE_WITH_EXPECTATION(name, expectation) \
+ void Dart_Test##name(); \
+ static const dart::compiler::ffi::RawTestCase kRegister##name( \
+ Dart_Test##name, #name, expectation); \
+ void Dart_Test##name()
+
+#define UNIT_TEST_CASE(name) UNIT_TEST_CASE_WITH_EXPECTATION(name, "Pass")
+
+// The UNIT_TEST_CASE_WITH_ZONE macro is used for tests that need a custom
+// dart::Zone.
+#define UNIT_TEST_CASE_WITH_ZONE_WITH_EXPECTATION(name, expectation) \
+ static void Dart_TestHelper##name(dart::Zone* Z); \
+ UNIT_TEST_CASE_WITH_EXPECTATION(name, expectation) { \
+ dart::Zone zone; \
+ Dart_TestHelper##name(&zone); \
+ } \
+ static void Dart_TestHelper##name(dart::Zone* Z)
+
+#define UNIT_TEST_CASE_WITH_ZONE(name) \
+ UNIT_TEST_CASE_WITH_ZONE_WITH_EXPECTATION(name, "Pass")
+
+namespace dart {
+namespace compiler {
+namespace ffi {
+
+class TestCaseBase {
+ public:
+ explicit TestCaseBase(const char* name, const char* expectation);
+ virtual ~TestCaseBase() {}
+
+ const char* name() const { return name_; }
+ const char* expectation() const { return expectation_; }
+
+ virtual void Run() = 0;
+ void RunTest();
+
+ static void RunAll();
+ static void RunAllRaw();
+
+ static bool update_expectations;
+
+ private:
+ static TestCaseBase* first_;
+ static TestCaseBase* tail_;
+
+ TestCaseBase* next_;
+ const char* name_;
+ const char* expectation_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCaseBase);
+};
+
+class RawTestCase : TestCaseBase {
+ public:
+ typedef void(RunEntry)();
+
+ RawTestCase(RunEntry* run, const char* name, const char* expectation)
+ : TestCaseBase(name, expectation), run_(run) {}
+ virtual void Run();
+
+ private:
+ RunEntry* const run_;
+};
+
+} // namespace ffi
+} // namespace compiler
+} // namespace dart
+
+#endif // RUNTIME_VM_COMPILER_FFI_UNIT_TEST_H_
diff --git a/runtime/vm/compiler/ffi/unit_test_custom_zone.cc b/runtime/vm/compiler/ffi/unit_test_custom_zone.cc
new file mode 100644
index 0000000..90f01cf
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_test_custom_zone.cc
@@ -0,0 +1,45 @@
+// 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.
+
+#include "vm/compiler/ffi/unit_test_custom_zone.h"
+
+#include "vm/compiler/runtime_api.h"
+
+// Directly compile cc files into the custom zone, so that we do not get linker
+// errors from object files compiled against the DartVM Zone.
+#include "vm/compiler/ffi/native_calling_convention.cc" // NOLINT
+#include "vm/compiler/ffi/native_location.cc" // NOLINT
+#include "vm/compiler/ffi/native_type.cc" // NOLINT
+#include "vm/zone_text_buffer.cc" // NOLINT
+
+namespace dart {
+
+void* ZoneAllocated::operator new(uintptr_t size, dart::Zone* zone) {
+ return reinterpret_cast<void*>(zone->AllocUnsafe(size));
+}
+
+Zone::~Zone() {
+ while (buffers_.size() > 0) {
+ free(buffers_.back());
+ buffers_.pop_back();
+ }
+}
+
+void* Zone::AllocUnsafe(intptr_t size) {
+ void* memory = malloc(size);
+ buffers_.push_back(memory);
+ return memory;
+}
+
+DART_EXPORT void Dart_PrepareToAbort() {
+ fprintf(stderr, "Dart_PrepareToAbort() not implemented!\n");
+ exit(1);
+}
+
+DART_EXPORT void Dart_DumpNativeStackTrace(void* context) {
+ fprintf(stderr, "Dart_DumpNativeStackTrace() not implemented!\n");
+ exit(1);
+}
+
+} // namespace dart
diff --git a/runtime/vm/compiler/ffi/unit_test_custom_zone.h b/runtime/vm/compiler/ffi/unit_test_custom_zone.h
new file mode 100644
index 0000000..ac305a1
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_test_custom_zone.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef RUNTIME_VM_COMPILER_FFI_UNIT_TEST_CUSTOM_ZONE_H_
+#define RUNTIME_VM_COMPILER_FFI_UNIT_TEST_CUSTOM_ZONE_H_
+
+#include <vector>
+
+// We use a custom zone here which doesn't depend on VM internals (e.g. handles,
+// thread, ...)
+#if defined(RUNTIME_VM_ZONE_H_)
+#error "We want our own zone implementation"
+#endif
+#define RUNTIME_VM_ZONE_H_
+
+namespace dart {
+
+class Zone {
+ public:
+ Zone() {}
+ ~Zone();
+
+ template <class ElementType>
+ inline ElementType* Alloc(intptr_t length) {
+ return static_cast<ElementType*>(AllocUnsafe(sizeof(ElementType) * length));
+ }
+
+ template <class ElementType>
+ inline ElementType* Realloc(ElementType* old_array,
+ intptr_t old_length,
+ intptr_t new_length) {
+ void* memory = AllocUnsafe(sizeof(ElementType) * new_length);
+ memmove(memory, old_array, sizeof(ElementType) * old_length);
+ return static_cast<ElementType*>(memory);
+ }
+
+ template <class ElementType>
+ void Free(ElementType* old_array, intptr_t len) {}
+
+ void* AllocUnsafe(intptr_t size);
+
+ private:
+ Zone(const Zone&) = delete;
+ void operator=(const Zone&) = delete;
+ std::vector<void*> buffers_;
+};
+
+} // namespace dart
+
+#endif // RUNTIME_VM_COMPILER_FFI_UNIT_TEST_CUSTOM_ZONE_H_
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_android.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_android.expect
new file mode 100644
index 0000000..8f237e3
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_android.expect
@@ -0,0 +1,12 @@
+v0 float
+v1 float
+v2 float
+v3 float
+v4 float
+v5 float
+v6 float
+v7 float
+S+0 float
+S+8 float
+=>
+v0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_ios.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_ios.expect
new file mode 100644
index 0000000..8f237e3
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_ios.expect
@@ -0,0 +1,12 @@
+v0 float
+v1 float
+v2 float
+v3 float
+v4 float
+v5 float
+v6 float
+v7 float
+S+0 float
+S+8 float
+=>
+v0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_linux.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_linux.expect
new file mode 100644
index 0000000..8f237e3
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_linux.expect
@@ -0,0 +1,12 @@
+v0 float
+v1 float
+v2 float
+v3 float
+v4 float
+v5 float
+v6 float
+v7 float
+S+0 float
+S+8 float
+=>
+v0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_macos.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_macos.expect
new file mode 100644
index 0000000..8f237e3
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm64_macos.expect
@@ -0,0 +1,12 @@
+v0 float
+v1 float
+v2 float
+v3 float
+v4 float
+v5 float
+v6 float
+v7 float
+S+0 float
+S+8 float
+=>
+v0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_android.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_android.expect
new file mode 100644
index 0000000..f563dbd
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_android.expect
@@ -0,0 +1,12 @@
+r0 int32[float]
+r1 int32[float]
+r2 int32[float]
+r3 int32[float]
+S+0 float
+S+4 float
+S+8 float
+S+12 float
+S+16 float
+S+20 float
+=>
+r0 int32[float]
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_ios.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_ios.expect
new file mode 100644
index 0000000..9ff44a7
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_ios.expect
@@ -0,0 +1,12 @@
+s0 float
+s1 float
+s2 float
+s3 float
+s4 float
+s5 float
+s6 float
+s7 float
+s8 float
+s9 float
+=>
+q0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_linux.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_linux.expect
new file mode 100644
index 0000000..9ff44a7
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/arm_linux.expect
@@ -0,0 +1,12 @@
+s0 float
+s1 float
+s2 float
+s3 float
+s4 float
+s5 float
+s6 float
+s7 float
+s8 float
+s9 float
+=>
+q0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_android.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_android.expect
new file mode 100644
index 0000000..c4390e7
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_android.expect
@@ -0,0 +1,12 @@
+S+0 float
+S+4 float
+S+8 float
+S+12 float
+S+16 float
+S+20 float
+S+24 float
+S+28 float
+S+32 float
+S+36 float
+=>
+xmm0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_linux.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_linux.expect
new file mode 100644
index 0000000..c4390e7
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_linux.expect
@@ -0,0 +1,12 @@
+S+0 float
+S+4 float
+S+8 float
+S+12 float
+S+16 float
+S+20 float
+S+24 float
+S+28 float
+S+32 float
+S+36 float
+=>
+xmm0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_win.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_win.expect
new file mode 100644
index 0000000..c4390e7
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/ia32_win.expect
@@ -0,0 +1,12 @@
+S+0 float
+S+4 float
+S+8 float
+S+12 float
+S+16 float
+S+20 float
+S+24 float
+S+28 float
+S+32 float
+S+36 float
+=>
+xmm0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_ios.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_ios.expect
new file mode 100644
index 0000000..b650130
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_ios.expect
@@ -0,0 +1,12 @@
+xmm0 float
+xmm1 float
+xmm2 float
+xmm3 float
+xmm4 float
+xmm5 float
+xmm6 float
+xmm7 float
+S+0 float
+S+8 float
+=>
+xmm0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_linux.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_linux.expect
new file mode 100644
index 0000000..b650130
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_linux.expect
@@ -0,0 +1,12 @@
+xmm0 float
+xmm1 float
+xmm2 float
+xmm3 float
+xmm4 float
+xmm5 float
+xmm6 float
+xmm7 float
+S+0 float
+S+8 float
+=>
+xmm0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_macos.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_macos.expect
new file mode 100644
index 0000000..b650130
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_macos.expect
@@ -0,0 +1,12 @@
+xmm0 float
+xmm1 float
+xmm2 float
+xmm3 float
+xmm4 float
+xmm5 float
+xmm6 float
+xmm7 float
+S+0 float
+S+8 float
+=>
+xmm0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_win.expect b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_win.expect
new file mode 100644
index 0000000..1244587
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/floatx10/x64_win.expect
@@ -0,0 +1,12 @@
+xmm0 float
+xmm1 float
+xmm2 float
+xmm3 float
+S+0 float
+S+8 float
+S+16 float
+S+24 float
+S+32 float
+S+40 float
+=>
+xmm0 float
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_android.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_android.expect
new file mode 100644
index 0000000..04f72e5f
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_android.expect
@@ -0,0 +1,12 @@
+r0 int8
+r1 int8
+r2 int8
+r3 int8
+r4 int8
+r5 int8
+r6 int8
+r7 int8
+S+0 int8
+S+8 int8
+=>
+r0 int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_ios.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_ios.expect
new file mode 100644
index 0000000..04f72e5f
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_ios.expect
@@ -0,0 +1,12 @@
+r0 int8
+r1 int8
+r2 int8
+r3 int8
+r4 int8
+r5 int8
+r6 int8
+r7 int8
+S+0 int8
+S+8 int8
+=>
+r0 int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_linux.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_linux.expect
new file mode 100644
index 0000000..04f72e5f
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_linux.expect
@@ -0,0 +1,12 @@
+r0 int8
+r1 int8
+r2 int8
+r3 int8
+r4 int8
+r5 int8
+r6 int8
+r7 int8
+S+0 int8
+S+8 int8
+=>
+r0 int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_macos.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_macos.expect
new file mode 100644
index 0000000..04f72e5f
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_macos.expect
@@ -0,0 +1,12 @@
+r0 int8
+r1 int8
+r2 int8
+r3 int8
+r4 int8
+r5 int8
+r6 int8
+r7 int8
+S+0 int8
+S+8 int8
+=>
+r0 int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_android.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_android.expect
new file mode 100644
index 0000000..9307d2f
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_android.expect
@@ -0,0 +1,12 @@
+r0 int32[int8]
+r1 int32[int8]
+r2 int32[int8]
+r3 int32[int8]
+S+0 int32[int8]
+S+4 int32[int8]
+S+8 int32[int8]
+S+12 int32[int8]
+S+16 int32[int8]
+S+20 int32[int8]
+=>
+r0 int32[int8]
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_ios.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_ios.expect
new file mode 100644
index 0000000..9307d2f
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_ios.expect
@@ -0,0 +1,12 @@
+r0 int32[int8]
+r1 int32[int8]
+r2 int32[int8]
+r3 int32[int8]
+S+0 int32[int8]
+S+4 int32[int8]
+S+8 int32[int8]
+S+12 int32[int8]
+S+16 int32[int8]
+S+20 int32[int8]
+=>
+r0 int32[int8]
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_linux.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_linux.expect
new file mode 100644
index 0000000..9307d2f
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/arm_linux.expect
@@ -0,0 +1,12 @@
+r0 int32[int8]
+r1 int32[int8]
+r2 int32[int8]
+r3 int32[int8]
+S+0 int32[int8]
+S+4 int32[int8]
+S+8 int32[int8]
+S+12 int32[int8]
+S+16 int32[int8]
+S+20 int32[int8]
+=>
+r0 int32[int8]
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_android.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_android.expect
new file mode 100644
index 0000000..bf5f6c4
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_android.expect
@@ -0,0 +1,12 @@
+S+0 int32[int8]
+S+4 int32[int8]
+S+8 int32[int8]
+S+12 int32[int8]
+S+16 int32[int8]
+S+20 int32[int8]
+S+24 int32[int8]
+S+28 int32[int8]
+S+32 int32[int8]
+S+36 int32[int8]
+=>
+eax int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_linux.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_linux.expect
new file mode 100644
index 0000000..bf5f6c4
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_linux.expect
@@ -0,0 +1,12 @@
+S+0 int32[int8]
+S+4 int32[int8]
+S+8 int32[int8]
+S+12 int32[int8]
+S+16 int32[int8]
+S+20 int32[int8]
+S+24 int32[int8]
+S+28 int32[int8]
+S+32 int32[int8]
+S+36 int32[int8]
+=>
+eax int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_win.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_win.expect
new file mode 100644
index 0000000..bf5f6c4
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_win.expect
@@ -0,0 +1,12 @@
+S+0 int32[int8]
+S+4 int32[int8]
+S+8 int32[int8]
+S+12 int32[int8]
+S+16 int32[int8]
+S+20 int32[int8]
+S+24 int32[int8]
+S+28 int32[int8]
+S+32 int32[int8]
+S+36 int32[int8]
+=>
+eax int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_ios.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_ios.expect
new file mode 100644
index 0000000..0bcad4b
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_ios.expect
@@ -0,0 +1,12 @@
+rdi int32[int8]
+rsi int32[int8]
+rdx int32[int8]
+rcx int32[int8]
+r8 int32[int8]
+r9 int32[int8]
+S+0 int32[int8]
+S+8 int32[int8]
+S+16 int32[int8]
+S+24 int32[int8]
+=>
+rax int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_linux.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_linux.expect
new file mode 100644
index 0000000..0bcad4b
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_linux.expect
@@ -0,0 +1,12 @@
+rdi int32[int8]
+rsi int32[int8]
+rdx int32[int8]
+rcx int32[int8]
+r8 int32[int8]
+r9 int32[int8]
+S+0 int32[int8]
+S+8 int32[int8]
+S+16 int32[int8]
+S+24 int32[int8]
+=>
+rax int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_macos.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_macos.expect
new file mode 100644
index 0000000..0bcad4b
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_macos.expect
@@ -0,0 +1,12 @@
+rdi int32[int8]
+rsi int32[int8]
+rdx int32[int8]
+rcx int32[int8]
+r8 int32[int8]
+r9 int32[int8]
+S+0 int32[int8]
+S+8 int32[int8]
+S+16 int32[int8]
+S+24 int32[int8]
+=>
+rax int8
diff --git a/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_win.expect b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_win.expect
new file mode 100644
index 0000000..94704e5
--- /dev/null
+++ b/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_win.expect
@@ -0,0 +1,12 @@
+rcx int8
+rdx int8
+r8 int8
+r9 int8
+S+0 int8
+S+8 int8
+S+16 int8
+S+24 int8
+S+32 int8
+S+40 int8
+=>
+rax int8
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index cd282c3..90d3903 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -620,10 +620,13 @@
CheckIfBackgroundCompilerIsBeingStopped(optimized());
}
- // Grab read program_lock outside of potential safepoint, that lock
+ // Grab write program_lock outside of potential safepoint, that lock
// can't be waited for inside the safepoint.
- SafepointReadRwLocker ml(thread(),
- thread()->isolate_group()->program_lock());
+ // Initially read lock was added to guard direct_subclasses field
+ // access.
+ // Read lock was upgraded to write lock to guard dependent code updates.
+ SafepointWriteRwLocker ml(thread(),
+ thread()->isolate_group()->program_lock());
// We have to ensure no mutators are running, because:
//
// a) We allocate an instructions object, which might cause us to
diff --git a/runtime/vm/compiler/jit/jit_call_specializer.cc b/runtime/vm/compiler/jit/jit_call_specializer.cc
index 6ce72c4..05ce2af 100644
--- a/runtime/vm/compiler/jit/jit_call_specializer.cc
+++ b/runtime/vm/compiler/jit/jit_call_specializer.cc
@@ -205,6 +205,9 @@
}
ASSERT(field.IsOriginal());
field.set_is_unboxing_candidate(false);
+ Thread* thread = Thread::Current();
+ SafepointWriteRwLocker ml(thread,
+ thread->isolate_group()->program_lock());
field.DeoptimizeDependentCode();
} else {
flow_graph()->parsed_function().AddToGuardedFields(&field);
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index 7611669..ce2d164 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -288,8 +288,10 @@
static constexpr int kWordSize = 1 << kWordSizeLog2;
static_assert(kWordSize == sizeof(word), "kWordSize should match sizeof(word)");
// Our compiler code currently assumes this, so formally check it.
+#if !defined(FFI_UNIT_TESTS)
static_assert(dart::kWordSize >= kWordSize,
"Host word size smaller than target word size");
+#endif
static constexpr word kBitsPerWordLog2 = kWordSizeLog2 + kBitsPerByteLog2;
static constexpr word kBitsPerWord = 1 << kBitsPerWordLog2;
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 21319ca..898f10e 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -1400,7 +1400,7 @@
DISALLOW_IMPLICIT_CONSTRUCTORS(Instr);
};
-const uword kBreakInstructionFiller = 0xD4200000D4200000L; // brk #0; brk #0
+const uint64_t kBreakInstructionFiller = 0xD4200000D4200000L; // brk #0; brk #0
} // namespace dart
diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h
index ae421fd..80e37ab 100644
--- a/runtime/vm/constants_x64.h
+++ b/runtime/vm/constants_x64.h
@@ -473,7 +473,7 @@
// becomes important to us.
const int MAX_NOP_SIZE = 8;
-const uword kBreakInstructionFiller = 0xCCCCCCCCCCCCCCCCL;
+const uint64_t kBreakInstructionFiller = 0xCCCCCCCCCCCCCCCCL;
} // namespace dart
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index 36394b0..ceb0e2e 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -1919,8 +1919,11 @@
DebuggerStackTrace* Debugger::CollectAsyncLazyStackTrace() {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
+ Isolate* isolate = thread->isolate();
Code& code = Code::Handle(zone);
+ Code& inlined_code = Code::Handle(zone);
+ Array& deopt_frame = Array::Handle(zone);
Smi& offset = Smi::Handle(zone);
Function& function = Function::Handle(zone);
@@ -1932,8 +1935,16 @@
const auto& pc_offset_array = GrowableObjectArray::ZoneHandle(
zone, GrowableObjectArray::New(kDefaultStackAllocation));
bool has_async = false;
+
+ std::function<void(StackFrame*)> on_sync_frame = [&](StackFrame* frame) {
+ code = frame->LookupDartCode();
+ AppendCodeFrames(thread, isolate, zone, stack_trace, frame, &code,
+ &inlined_code, &deopt_frame);
+ };
+
StackTraceUtils::CollectFramesLazy(thread, code_array, pc_offset_array,
- /*skip_frames=*/0, &has_async);
+ /*skip_frames=*/0, &on_sync_frame,
+ &has_async);
// If the entire stack is sync, return no trace.
if (!has_async) {
@@ -1941,11 +1952,19 @@
}
const intptr_t length = code_array.Length();
- for (intptr_t i = stack_trace->Length(); i < length; ++i) {
+ bool async_frames = false;
+ for (intptr_t i = 0; i < length; ++i) {
code ^= code_array.At(i);
if (code.raw() == StubCode::AsynchronousGapMarker().raw()) {
stack_trace->AddMarker(ActivationFrame::kAsyncSuspensionMarker);
+ // Once we reach a gap, the rest is async.
+ async_frames = true;
+ continue;
+ }
+
+ // Skip the sync frames since they've been added (and un-inlined) above.
+ if (!async_frames) {
continue;
}
diff --git a/runtime/vm/heap/weak_code.cc b/runtime/vm/heap/weak_code.cc
index c1a3d53..97f832a 100644
--- a/runtime/vm/heap/weak_code.cc
+++ b/runtime/vm/heap/weak_code.cc
@@ -69,6 +69,9 @@
}
UpdateArrayTo(Object::null_array());
+
+ // TODO(dartbug.com/36097): This has to walk all mutator threads when
+ // disabling code.
// Disable all code on stack.
Code& code = Code::Handle();
{
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index 4df27eb..b126b1c 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -1242,6 +1242,8 @@
Class& cls = Class::Handle();
Array& fields = Array::Handle();
Field& field = Field::Handle();
+ Thread* thread = Thread::Current();
+ SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
for (intptr_t cls_idx = bottom; cls_idx < top; cls_idx++) {
if (!class_table->HasValidClassAt(cls_idx)) {
// Skip.
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 3395587..aeacffe 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -3889,7 +3889,15 @@
}
void Class::DisableCHAOptimizedCode(const Class& subclass) {
- ASSERT(Thread::Current()->IsMutatorThread());
+ Thread* thread = Thread::Current();
+ ASSERT(thread->IsMutatorThread());
+ // TODO(dartbug.com/36097): The program_lock acquisition has to move up the
+ // call chain to ClassFinalizer::AllocateFinalizeClass() so that:
+ // - no two threads allocate-finalize a class at the same time(we should
+ // use the logic similar to what is used in EnsureIsAllocateFinalized()).
+ // - code is deoptimized before we violate optimization assumptions
+ // potentially done concurrently (AddDirectSubclass/AddDirectImplementor).
+ SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
CHACodeArray a(*this);
if (FLAG_trace_deoptimization && a.HasCodes()) {
if (subclass.IsNull()) {
@@ -3928,7 +3936,15 @@
#endif
}
+ArrayPtr Class::dependent_code() const {
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
+ return raw_ptr()->dependent_code_;
+}
+
void Class::set_dependent_code(const Array& array) const {
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
StorePointer(&raw_ptr()->dependent_code_, array.raw());
}
@@ -10405,11 +10421,15 @@
}
ArrayPtr Field::dependent_code() const {
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
return raw_ptr()->dependent_code_;
}
void Field::set_dependent_code(const Array& array) const {
ASSERT(IsOriginal());
+ DEBUG_ASSERT(
+ IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
StorePointer(&raw_ptr()->dependent_code_, array.raw());
}
@@ -11058,6 +11078,8 @@
THR_Print(" => %s\n", GuardedPropertiesAsCString());
}
+ Thread* const thread = Thread::Current();
+ SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
DeoptimizeDependentCode();
}
}
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 11fed21..098db27 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -1541,7 +1541,7 @@
// Return the list of code objects that were compiled using CHA of this class.
// These code objects will be invalidated if new subclasses of this class
// are finalized.
- ArrayPtr dependent_code() const { return raw_ptr()->dependent_code_; }
+ ArrayPtr dependent_code() const;
void set_dependent_code(const Array& array) const;
bool TraceAllocation(Isolate* isolate) const;
diff --git a/runtime/vm/object_reload.cc b/runtime/vm/object_reload.cc
index bea8d14..447dbc2 100644
--- a/runtime/vm/object_reload.cc
+++ b/runtime/vm/object_reload.cc
@@ -468,6 +468,8 @@
}
}
+ Thread* thread = Thread::Current();
+ SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
const Array& field_list = Array::Handle(fields());
Field& field = Field::Handle();
for (intptr_t i = 0; i < field_list.Length(); i++) {
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 0b4ceb7..f2c86a2 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -2574,15 +2574,20 @@
if (Compiler::CanOptimizeFunction(thread, function)) {
if (FLAG_background_compilation) {
- Field& field = Field::Handle(zone, isolate->GetDeoptimizingBoxedField());
- while (!field.IsNull()) {
- if (FLAG_trace_optimization || FLAG_trace_field_guards) {
- THR_Print("Lazy disabling unboxing of %s\n", field.ToCString());
+ {
+ SafepointWriteRwLocker ml(thread,
+ thread->isolate_group()->program_lock());
+ Field& field =
+ Field::Handle(zone, isolate->GetDeoptimizingBoxedField());
+ while (!field.IsNull()) {
+ if (FLAG_trace_optimization || FLAG_trace_field_guards) {
+ THR_Print("Lazy disabling unboxing of %s\n", field.ToCString());
+ }
+ field.set_is_unboxing_candidate(false);
+ field.DeoptimizeDependentCode();
+ // Get next field.
+ field = isolate->GetDeoptimizingBoxedField();
}
- field.set_is_unboxing_candidate(false);
- field.DeoptimizeDependentCode();
- // Get next field.
- field = isolate->GetDeoptimizingBoxedField();
}
if (!BackgroundCompiler::IsDisabled(isolate,
/* optimizing_compiler = */ true) &&
diff --git a/runtime/vm/stack_trace.cc b/runtime/vm/stack_trace.cc
index 572c222..5b146ff 100644
--- a/runtime/vm/stack_trace.cc
+++ b/runtime/vm/stack_trace.cc
@@ -287,6 +287,7 @@
const GrowableObjectArray& code_array,
const GrowableObjectArray& pc_offset_array,
int skip_frames,
+ std::function<void(StackFrame*)>* on_sync_frames,
bool* has_async) {
if (has_async != nullptr) {
*has_async = false;
@@ -326,6 +327,9 @@
ASSERT(pc_offset > 0 && pc_offset <= code.Size());
offset = Smi::New(pc_offset);
pc_offset_array.Add(offset);
+ if (on_sync_frames != nullptr) {
+ (*on_sync_frames)(frame);
+ }
// Either continue the loop (sync-async case) or find all await'ers and
// return.
diff --git a/runtime/vm/stack_trace.h b/runtime/vm/stack_trace.h
index e3fd332..660746a 100644
--- a/runtime/vm/stack_trace.h
+++ b/runtime/vm/stack_trace.h
@@ -80,11 +80,15 @@
/// closure = closure.context[Context::kAsyncCompleterVarIndex]._future
/// ._resultOrListeners.callback;
/// }
+ ///
+ /// If [on_sync_frames] is non-nullptr, it will be called for every
+ /// synchronous frame which is collected.
static void CollectFramesLazy(
Thread* thread,
const GrowableObjectArray& code_array,
const GrowableObjectArray& pc_offset_array,
int skip_frames,
+ std::function<void(StackFrame*)>* on_sync_frames = nullptr,
bool* has_async = nullptr);
/// Counts the number of stack frames.
diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni
index fda79fb..665d37b 100644
--- a/runtime/vm/vm_sources.gni
+++ b/runtime/vm/vm_sources.gni
@@ -451,3 +451,15 @@
"virtual_memory_test.cc",
"zone_test.cc",
]
+
+constants_sources = [
+ "constants_arm.cc",
+ "constants_arm.h",
+ "constants_arm64.cc",
+ "constants_arm64.h",
+ "constants_base.h",
+ "constants_ia32.cc",
+ "constants_ia32.h",
+ "constants_x64.cc",
+ "constants_x64.h",
+]
diff --git a/sdk/lib/html/html_common/conversions_dart2js.dart b/sdk/lib/html/html_common/conversions_dart2js.dart
index c1fb0be..f0ed6d2 100644
--- a/sdk/lib/html/html_common/conversions_dart2js.dart
+++ b/sdk/lib/html/html_common/conversions_dart2js.dart
@@ -13,7 +13,29 @@
return dict;
}
-/// Converts a flat Dart map into a JavaScript object with properties.
+/// Converts values that occur within a Dart map for map conversion.
+///
+/// This includes other maps, lists, or values that don't need a conversion e.g.
+/// bool, String.
+_convertDartToNative_Value(Object? value) {
+ if (value == null) return value;
+ if (value is String || value is num || value is bool) return value;
+ if (value is Map) return convertDartToNative_Dictionary(value);
+ if (value is List) {
+ var array = JS('var', '[]');
+ value.forEach((element) {
+ JS('void', '#.push(#)', array, _convertDartToNative_Value(element));
+ });
+ value = array;
+ }
+ return value;
+}
+
+/// Converts a potentially nested Dart map to a JavaScript object with
+/// properties.
+///
+/// This method requires that the values within the map are either maps
+/// themselves, lists, or do not need a conversion.
convertDartToNative_Dictionary(Map? dict, [void postCreate(Object? f)?]) {
if (dict == null) return null;
var object = JS('var', '{}');
@@ -21,7 +43,7 @@
postCreate(object);
}
dict.forEach((key, value) {
- JS('void', '#[#] = #', object, key, value);
+ JS('void', '#[#] = #', object, key, _convertDartToNative_Value(value));
});
return object;
}
diff --git a/tools/VERSION b/tools/VERSION
index f08a7cd..6296136 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 49
+PRERELEASE 50
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index 8d4be38..582dd82 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -418,6 +418,9 @@
"flutter-frontend": {
"__comment__": "This configuration is only used for a custom test runner. If it conflicts with a new configuration you are adding, you can make this configuration more specific by adding options."
},
+ "vm-ffi-unit-test": {
+ "__comment__": "This configuration is only used for a custom test runner. If it conflicts with a new configuration you are adding, you can make this configuration more specific by adding options."
+ },
"unittest-asserts-no-sdk-(linux|mac|win)": {
"options": {
"compiler": "dartk",
@@ -1102,10 +1105,26 @@
"vm-precomp-ffi-qemu-linux-release-arm"
],
"meta": {
- "description": "This configuration is used for running FFI tests on qemu."
+ "description": "This configuration is used for running FFI tests on qemu and FFI unit tests."
},
"steps": [
{
+ "name": "build run_ffi_unit_tests",
+ "script": "tools/build.py",
+ "arguments": [
+ "--arch=x64",
+ "--mode=debug",
+ "run_ffi_unit_tests"
+ ]
+ },
+ {
+ "name": "ffi unit tests",
+ "arguments": [
+ "-nvm-ffi-unit-test",
+ "ffi_unit"
+ ]
+ },
+ {
"name": "build dart",
"script": "tools/build.py",
"arguments": [
@@ -2804,7 +2823,7 @@
"analyzer-analysis-server-linux"
],
"meta": {
- "description": "Analyze analyzer related packages."
+ "description": "Analyze repository packages."
},
"steps": [
{
@@ -2816,9 +2835,9 @@
},
{
"name": "analyze runtime/tools/dartfuzz",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
"runtime/tools/dartfuzz"
]
},
@@ -2827,6 +2846,7 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/analysis_server"
]
},
@@ -2835,6 +2855,7 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/analysis_server_client"
]
},
@@ -2843,6 +2864,7 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/analyzer"
]
},
@@ -2851,6 +2873,7 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/analyzer_cli"
]
},
@@ -2859,14 +2882,15 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/analyzer_plugin"
]
},
{
"name": "analyze pkg/async_helper",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
"pkg/async_helper"
]
},
@@ -2905,26 +2929,27 @@
},
{
"name": "analyze pkg/dart_internal",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/dart_internal"
]
},
{
"name": "analyze pkg/dev_compiler",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
- "--fatal-lints",
+ "analyze",
+ "--fatal-infos",
"pkg/dev_compiler"
]
},
{
"name": "analyze pkg/expect",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
"pkg/expect"
]
},
@@ -2962,33 +2987,36 @@
},
{
"name": "analyze pkg/meta",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/meta"
]
},
{
"name": "analyze pkg/native_stack_traces",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
"pkg/native_stack_traces"
]
},
{
"name": "analyze pkg/smith",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/smith"
]
},
{
"name": "analyze pkg/sourcemap_testing",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/sourcemap_testing"
]
},
@@ -2997,6 +3025,7 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/status_file"
]
},
@@ -3005,6 +3034,7 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/telemetry"
]
},
@@ -3018,25 +3048,28 @@
},
{
"name": "analyze pkg/testing",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/testing"
]
},
{
"name": "analyze pkg/vm",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/vm"
]
},
{
"name": "analyze pkg/vm_service",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/vm_service"
]
},
@@ -3045,14 +3078,16 @@
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
"analyze",
+ "--fatal-infos",
"pkg/vm_snapshot_analysis"
]
},
{
"name": "analyze pkg/dds",
- "script": "out/ReleaseX64/dart-sdk/bin/dartanalyzer",
+ "script": "out/ReleaseX64/dart-sdk/bin/dart",
"arguments": [
- "--fatal-warnings",
+ "analyze",
+ "--fatal-infos",
"pkg/dds"
]
},