Version 2.13.0-104.0.dev
Merge commit 'dcc466919f312729598bd755cd0443efb39c6db7' into 'dev'
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index 57e7ef4..8c5161c 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -109,7 +109,7 @@
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
- 1.32.4
+ 1.32.5
</h1>
<p>
This document contains a specification of the API provided by the
@@ -236,6 +236,13 @@
ignoring the item or treating it with some default/fallback handling.
</p>
<h3>Changelog</h3>
+<h4>1.32.5</h4>
+<ul>
+ <li>Added optional <tt>replacementOffset</tt> and <tt>replacementLength</tt> on
+ <tt>CompletionSuggestion</tt> to support different per-completion text replacement
+ ranges (for example when inserting names for arguments, a different range may be
+ supplied than for completions that are not name labels).</li>
+</ul>
<h4>1.32.4</h4>
<ul>
<li>Added <tt>ElementKind.TYPE_ALIAS</tt> and <tt>HighlightRegionType.TYPE_ALIAS</tt>
@@ -3483,6 +3490,30 @@
is only defined if the displayed text should be different than the
completion. Otherwise it is omitted.
</p>
+ </dd><dt class="field"><b>replacementOffset: int<span style="color:#999999"> (optional)</span></b></dt><dd>
+
+ <p>
+ The offset of the start of the text to be
+ replaced. If supplied, this should be used in
+ preference to the offset provided on the
+ containing completion results.
+
+ This value may be provided independently of
+ replacementLength (for example if only one
+ differs from the completion result value).
+ </p>
+ </dd><dt class="field"><b>replacementLength: int<span style="color:#999999"> (optional)</span></b></dt><dd>
+
+ <p>
+ The length of the text to be replaced.
+ If supplied, this should be used in preference
+ to the offset provided on the
+ containing completion results.
+
+ This value may be provided independently of
+ replacementOffset (for example if only one
+ differs from the completion result value).
+ </p>
</dd><dt class="field"><b>selectionOffset: int</b></dt><dd>
<p>
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index 9b566ba..6772e9d 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -6,7 +6,7 @@
// To regenerate the file, use the script
// "pkg/analysis_server/tool/spec/generate_files".
-const String PROTOCOL_VERSION = '1.32.4';
+const String PROTOCOL_VERSION = '1.32.5';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index 4268054..e565b85 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -272,29 +272,42 @@
return cancelled();
}
- final results = serverSuggestions
- .map(
- (item) => toCompletionItem(
- completionCapabilities,
- clientSupportedCompletionKinds,
- unit.lineInfo,
- item,
- completionRequest.replacementOffset,
- insertLength,
- completionRequest.replacementLength,
- // TODO(dantup): Including commit characters in every completion
- // increases the payload size. The LSP spec is ambigious
- // about how this should be handled (and VS Code requires it) but
- // this should be removed (or made conditional based on a capability)
- // depending on how the spec is updated.
- // https://github.com/microsoft/vscode-languageserver-node/issues/673
- includeCommitCharacters:
- server.clientConfiguration.previewCommitCharacters,
- completeFunctionCalls:
- server.clientConfiguration.completeFunctionCalls,
- ),
- )
- .toList();
+ final results = serverSuggestions.map(
+ (item) {
+ var itemReplacementOffset =
+ item.replacementOffset ?? completionRequest.replacementOffset;
+ var itemReplacementLength =
+ item.replacementLength ?? completionRequest.replacementLength;
+ var itemInsertLength = insertLength;
+
+ // Recompute the insert length if it may be affected by the above.
+ if (item.replacementOffset != null ||
+ item.replacementLength != null) {
+ itemInsertLength = _computeInsertLength(
+ offset, itemReplacementOffset, itemInsertLength);
+ }
+
+ return toCompletionItem(
+ completionCapabilities,
+ clientSupportedCompletionKinds,
+ unit.lineInfo,
+ item,
+ itemReplacementOffset,
+ itemInsertLength,
+ itemReplacementLength,
+ // TODO(dantup): Including commit characters in every completion
+ // increases the payload size. The LSP spec is ambigious
+ // about how this should be handled (and VS Code requires it) but
+ // this should be removed (or made conditional based on a capability)
+ // depending on how the spec is updated.
+ // https://github.com/microsoft/vscode-languageserver-node/issues/673
+ includeCommitCharacters:
+ server.clientConfiguration.previewCommitCharacters,
+ completeFunctionCalls:
+ server.clientConfiguration.completeFunctionCalls,
+ );
+ },
+ ).toList();
// Now compute items in suggestion sets.
var includedSuggestionSets = <IncludedSuggestionSet>[];
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index 7125eaa..1b8e024 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -284,6 +284,7 @@
);
final insertText = insertTextInfo.first;
final insertTextFormat = insertTextInfo.last;
+ final isMultilineCompletion = insertText.contains('\n');
final supportsDeprecatedFlag =
completionCapabilities?.completionItem?.deprecatedSupport == true;
@@ -291,6 +292,10 @@
completionCapabilities?.completionItem?.tagSupport?.valueSet ?? const [];
final supportsDeprecatedTag =
supportedTags.contains(lsp.CompletionItemTag.Deprecated);
+ final supportsAsIsInsertMode = completionCapabilities
+ ?.completionItem?.insertTextModeSupport?.valueSet
+ ?.contains(InsertTextMode.asIs) ==
+ true;
final completionKind = declarationKindToCompletionItemKind(
supportedCompletionItemKinds, declaration.kind);
@@ -333,6 +338,9 @@
insertTextFormat: insertTextFormat != lsp.InsertTextFormat.PlainText
? insertTextFormat
: null, // Defaults to PlainText if not supplied
+ insertTextMode: supportsAsIsInsertMode && isMultilineCompletion
+ ? InsertTextMode.asIs
+ : null,
// data, used for completionItem/resolve.
data: lsp.DartCompletionItemResolutionInfo(
file: file,
@@ -868,6 +876,10 @@
completionCapabilities?.completionItem?.snippetSupport == true;
final supportsInsertReplace =
completionCapabilities?.completionItem?.insertReplaceSupport == true;
+ final supportsAsIsInsertMode = completionCapabilities
+ ?.completionItem?.insertTextModeSupport?.valueSet
+ ?.contains(InsertTextMode.asIs) ==
+ true;
final completionKind = suggestion.element != null
? elementKindToCompletionItemKind(
@@ -889,6 +901,7 @@
);
final insertText = insertTextInfo.first;
final insertTextFormat = insertTextInfo.last;
+ final isMultilineCompletion = insertText.contains('\n');
// Because we potentially send thousands of these items, we should minimise
// the generated JSON as much as possible - for example using nulls in place
@@ -923,6 +936,9 @@
insertTextFormat: insertTextFormat != lsp.InsertTextFormat.PlainText
? insertTextFormat
: null, // Defaults to PlainText if not supplied
+ insertTextMode: supportsAsIsInsertMode && isMultilineCompletion
+ ? InsertTextMode.asIs
+ : null,
textEdit: supportsInsertReplace && insertLength != replacementLength
? Either2<TextEdit, InsertReplaceEdit>.t2(
InsertReplaceEdit(
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
index 328f319..271b7fb 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
@@ -45,31 +45,40 @@
}
void _addDefaultParamSuggestions(Iterable<ParameterElement> parameters,
- [bool appendComma = false]) {
+ {bool appendComma = false, int replacementLength}) {
var appendColon = !_isInNamedExpression();
var namedArgs = _namedArgs();
for (var parameter in parameters) {
if (parameter.isNamed) {
_addNamedParameterSuggestion(
- namedArgs, parameter, appendColon, appendComma);
+ namedArgs, parameter, appendColon, appendComma,
+ replacementLength: replacementLength);
}
}
}
void _addNamedParameterSuggestion(List<String> namedArgs,
- ParameterElement parameter, bool appendColon, bool appendComma) {
+ ParameterElement parameter, bool appendColon, bool appendComma,
+ {int replacementLength}) {
var name = parameter.name;
+ // Check whether anything after the caret is being replaced. If so, we will
+ // suppress inserting colons/commas. We check only replacing _after_ the
+ // caret as some replacements (before) will still want colons, for example:
+ // foo(mySt^'bar');
+ var replacementEnd = request.replacementRange.offset +
+ (replacementLength ?? request.replacementRange.length);
var willReplace =
request.completionPreference == CompletionPreference.replace &&
- request.replacementRange.length > 0;
+ replacementEnd > request.offset;
if (name != null && name.isNotEmpty && !namedArgs.contains(name)) {
builder.suggestNamedArgument(parameter,
// If there's a replacement length and the preference is to replace,
// we should not include colons/commas.
appendColon: appendColon && !willReplace,
- appendComma: appendComma && !willReplace);
+ appendComma: appendComma && !willReplace,
+ replacementLength: replacementLength);
}
}
@@ -80,20 +89,40 @@
var requiredParam =
parameters.where((ParameterElement p) => p.isRequiredPositional);
var requiredCount = requiredParam.length;
+
+ // When inserted named args, if there is a replacement starting at the caret
+ // it will be an identifier that should not be replaced if the completion
+ // preference is to insert. In this case, override the replacement length
+ // to 0.
+
// TODO(jwren): _isAppendingToArgList can be split into two cases (with and
// without preceded), then _isAppendingToArgList,
// _isInsertingToArgListWithNoSynthetic and
// _isInsertingToArgListWithSynthetic could be formatted into a single
// method which returns some enum with 5+ cases.
- if (_isEditingNamedArgLabel() || _isAppendingToArgList()) {
+ if (_isEditingNamedArgLabel() ||
+ _isAppendingToArgList() ||
+ _isAddingLabelToPositional()) {
if (requiredCount == 0 || requiredCount < _argCount()) {
+ // If there's a replacement range that starts at the caret, it will be
+ // for an identifier that is not the named label and therefore it should
+ // not be replaced.
+ var replacementLength =
+ request.offset == request.target.entity.offset &&
+ request.replacementRange.length != 0
+ ? 0
+ : null;
+
var addTrailingComma = !_isFollowedByAComma() && _isInFlutterCreation();
- _addDefaultParamSuggestions(parameters, addTrailingComma);
+ _addDefaultParamSuggestions(parameters,
+ appendComma: addTrailingComma,
+ replacementLength: replacementLength);
}
} else if (_isInsertingToArgListWithNoSynthetic()) {
- _addDefaultParamSuggestions(parameters, true);
+ _addDefaultParamSuggestions(parameters, appendComma: true);
} else if (_isInsertingToArgListWithSynthetic()) {
- _addDefaultParamSuggestions(parameters, !_isFollowedByAComma());
+ _addDefaultParamSuggestions(parameters,
+ appendComma: !_isFollowedByAComma());
} else {
var argument = request.target.containingNode;
if (argument is NamedExpression) {
@@ -126,6 +155,37 @@
}
}
+ /// Return `true` if the caret is preceeding an arg where a name could be added
+ /// (turning a positional arg into a named arg).
+ bool _isAddingLabelToPositional() {
+ if (argumentList != null) {
+ var entity = request.target.entity;
+ if (entity is! NamedExpression) {
+ // Caret is in front of a value
+ // f(one: 1, ^2);
+ if (request.offset <= entity.offset) {
+ return true;
+ }
+
+ // Caret is in the between two values that are not seperated by a comma.
+ // f(one: 1, tw^'foo');
+ // must be at least two and the target not last.
+ var args = argumentList.arguments;
+ if (args.length >= 2 && entity != args.last) {
+ var index = args.indexOf(entity);
+ if (index != -1) {
+ var next = args[index + 1];
+ // Check the two tokens are adjacent without any comma.
+ if (entity.end == next.offset) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
/// Return `true` if the completion target is at the end of the list of
/// arguments.
bool _isAppendingToArgList() {
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index 3164f0c..529a4d7 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -654,7 +654,9 @@
/// [appendComma] is `true` then a comma will be included at the end of the
/// completion text.
void suggestNamedArgument(ParameterElement parameter,
- {@required bool appendColon, @required bool appendComma}) {
+ {@required bool appendColon,
+ @required bool appendComma,
+ int replacementLength}) {
var name = parameter.name;
var type = parameter.type?.getDisplayString(
withNullability: request.libraryElement.isNonNullableByDefault);
@@ -705,7 +707,8 @@
false,
false,
parameterName: name,
- parameterType: type);
+ parameterType: type,
+ replacementLength: replacementLength);
if (parameter is FieldFormalParameterElement) {
_setDocumentation(suggestion, parameter);
suggestion.element = convertElement(parameter);
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart b/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart
index 009e950..1cf53bb 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart
@@ -14,6 +14,9 @@
FixKind get fixKind => DartFixKind.REMOVE_UNNECESSARY_CAST;
@override
+ FixKind get multiFixKind => DartFixKind.REMOVE_UNNECESSARY_CAST_MULTI;
+
+ @override
Future<void> compute(ChangeBuilder builder) async {
if (coveredNode is! AsExpression) {
return;
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/replace_boolean_with_bool.dart b/pkg/analysis_server/lib/src/services/correction/dart/replace_boolean_with_bool.dart
index 0e8935f..0105fed 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/replace_boolean_with_bool.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/replace_boolean_with_bool.dart
@@ -13,6 +13,9 @@
FixKind get fixKind => DartFixKind.REPLACE_BOOLEAN_WITH_BOOL;
@override
+ FixKind get multiFixKind => DartFixKind.REPLACE_BOOLEAN_WITH_BOOL_MULTI;
+
+ @override
Future<void> compute(ChangeBuilder builder) async {
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleReplacement(range.error(diagnostic), 'bool');
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 6e59e10..270e10d 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -323,6 +323,24 @@
],
),
],
+ CompileTimeErrorCode.UNDEFINED_CLASS_BOOLEAN: [
+ FixInfo(
+ canBeAppliedToFile: true,
+ canBeBulkApplied: false,
+ generators: [
+ ReplaceBooleanWithBool.newInstance,
+ ],
+ ),
+ ],
+ HintCode.UNNECESSARY_CAST: [
+ FixInfo(
+ canBeAppliedToFile: true,
+ canBeBulkApplied: false,
+ generators: [
+ RemoveUnnecessaryCast.newInstance,
+ ],
+ ),
+ ],
HintCode.UNUSED_IMPORT: [
FixInfo(
canBeAppliedToFile: true,
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index 7e2f972..bfb2c73 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.dart
@@ -52,6 +52,74 @@
expect(suggestions, hasLength(2));
}
+ Future<void>
+ test_ArgumentList_function_named_fromPositionalNumeric_withoutSpace() async {
+ addTestFile('void f(int a, {int b = 0}) {}'
+ 'void g() { f(2, ^3); }');
+ await getSuggestions();
+ assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
+ expect(suggestions, hasLength(1));
+ // Ensure we don't try to replace the following arg.
+ expect(replacementOffset, equals(completionOffset));
+ expect(replacementLength, equals(0));
+ }
+
+ Future<void>
+ test_ArgumentList_function_named_fromPositionalNumeric_withSpace() async {
+ addTestFile('void f(int a, {int b = 0}) {}'
+ 'void g() { f(2, ^ 3); }');
+ await getSuggestions();
+ assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
+ expect(suggestions, hasLength(1));
+ // Ensure we don't try to replace the following arg.
+ expect(replacementOffset, equals(completionOffset));
+ expect(replacementLength, equals(0));
+ }
+
+ Future<void>
+ test_ArgumentList_function_named_fromPositionalVariable_withoutSpace() async {
+ addTestFile('void f(int a, {int b = 0}) {}'
+ 'var foo = 1;'
+ 'void g() { f(2, ^foo); }');
+ await getSuggestions();
+ expect(suggestions, hasLength(1));
+
+ // The named arg "b: " should not replace anything.
+ assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ',
+ replacementOffset: null, replacementLength: 0);
+ }
+
+ Future<void>
+ test_ArgumentList_function_named_fromPositionalVariable_withSpace() async {
+ addTestFile('void f(int a, {int b = 0}) {}'
+ 'var foo = 1;'
+ 'void g() { f(2, ^ foo); }');
+ await getSuggestions();
+ assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'b: ');
+ expect(suggestions, hasLength(1));
+ // Ensure we don't try to replace the following arg.
+ expect(replacementOffset, equals(completionOffset));
+ expect(replacementLength, equals(0));
+ }
+
+ Future<void> test_ArgumentList_function_named_partiallyTyped() async {
+ addTestFile('''
+ class C {
+ void m(String firstString, {String secondString}) {}
+
+ void n() {
+ m('a', se^'b');
+ }
+ }
+ ''');
+ await getSuggestions();
+ assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'secondString: ');
+ expect(suggestions, hasLength(1));
+ // Ensure we replace the correct section.
+ expect(replacementOffset, equals(completionOffset - 2));
+ expect(replacementLength, equals(2));
+ }
+
Future<void> test_ArgumentList_imported_function_named_param() async {
addTestFile('main() { int.parse("16", ^);}');
await getSuggestions();
diff --git a/pkg/analysis_server/test/domain_completion_util.dart b/pkg/analysis_server/test/domain_completion_util.dart
index 17cbdd8..6d93dcf 100644
--- a/pkg/analysis_server/test/domain_completion_util.dart
+++ b/pkg/analysis_server/test/domain_completion_util.dart
@@ -43,6 +43,8 @@
{bool isDeprecated = false,
bool isPotential = false,
int selectionOffset,
+ int replacementOffset,
+ int replacementLength,
ElementKind elementKind}) {
CompletionSuggestion cs;
suggestions.forEach((s) {
@@ -70,6 +72,8 @@
expect(cs.kind, equals(kind));
expect(cs.selectionOffset, selectionOffset ?? completion.length);
expect(cs.selectionLength, equals(0));
+ expect(cs.replacementOffset, equals(replacementOffset));
+ expect(cs.replacementLength, equals(replacementLength));
expect(cs.isDeprecated, equals(isDeprecated));
expect(cs.isPotential, equals(isPotential));
}
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index c323422..49e185e 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -250,6 +250,8 @@
/// "relevance": int
/// "completion": String
/// "displayText": optional String
+/// "replacementOffset": optional int
+/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@@ -279,6 +281,8 @@
'isPotential': isBool
}, optionalFields: {
'displayText': isString,
+ 'replacementOffset': isInt,
+ 'replacementLength': isInt,
'docSummary': isString,
'docComplete': isString,
'declaringType': isString,
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 0c13828..4ad9170 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -569,6 +569,51 @@
expect(inserted, contains('a.abcdefghijdef\n'));
}
+ Future<void> test_insertTextMode_multiline() async {
+ final content = '''
+ import 'package:flutter/material.dart';
+
+ class _MyWidgetState extends State<MyWidget> {
+ @override
+ Widget build(BuildContext context) {
+ [[setSt^]]
+ return Container();
+ }
+ }
+ ''';
+
+ await initialize(
+ textDocumentCapabilities: withCompletionItemInsertTextModeSupport(
+ emptyTextDocumentClientCapabilities));
+ await openFile(mainFileUri, withoutMarkers(content));
+ final res = await getCompletion(mainFileUri, positionFromMarker(content));
+ final item = res.singleWhere((c) => c.label.startsWith('setState'));
+
+ // Multiline completions should always set insertTextMode.asIs.
+ expect(item.insertText, contains('\n'));
+ expect(item.insertTextMode, equals(InsertTextMode.asIs));
+ }
+
+ Future<void> test_insertTextMode_singleLine() async {
+ final content = '''
+ main() {
+ ^
+ }
+ ''';
+
+ await initialize(
+ textDocumentCapabilities: withCompletionItemInsertTextModeSupport(
+ emptyTextDocumentClientCapabilities));
+ await openFile(mainFileUri, withoutMarkers(content));
+ final res = await getCompletion(mainFileUri, positionFromMarker(content));
+ final item = res.singleWhere((c) => c.label.startsWith('main'));
+
+ // Single line completions should never set insertTextMode.asIs to
+ // avoid bloating payload size where it wouldn't matter.
+ expect(item.insertText, isNot(contains('\n')));
+ expect(item.insertTextMode, isNull);
+ }
+
Future<void> test_insideString() async {
final content = '''
var a = "This is ^a test"
@@ -663,17 +708,20 @@
String expectedInsert,
}) async {
final content = '''
-class A { const A({int argOne, int argTwo}); }
+class A { const A({int argOne, int argTwo, String argThree}); }
+final varOne = '';
$code
main() { }
''';
final expectedReplaced = '''
-class A { const A({int argOne, int argTwo}); }
+class A { const A({int argOne, int argTwo, String argThree}); }
+final varOne = '';
$expectedReplace
main() { }
''';
final expectedInserted = '''
-class A { const A({int argOne, int argTwo}); }
+class A { const A({int argOne, int argTwo, String argThree}); }
+final varOne = '';
$expectedInsert
main() { }
''';
@@ -698,7 +746,23 @@
expectedInsert: '@A(argTwoargOne: 1)',
);
- // Inside the identifier also should be expected to replace.
+ // When adding a name to an existing value, it should always insert.
+ await check(
+ '@A(^1)',
+ 'argOne: ',
+ expectedReplace: '@A(argOne: 1)',
+ expectedInsert: '@A(argOne: 1)',
+ );
+
+ // When adding a name to an existing variable, it should always insert.
+ await check(
+ '@A(argOne: 1, ^varOne)',
+ 'argTwo: ',
+ expectedReplace: '@A(argOne: 1, argTwo: varOne)',
+ expectedInsert: '@A(argOne: 1, argTwo: varOne)',
+ );
+
+ // // Inside the identifier also should be expected to replace.
await check(
'@A(arg^One: 1)',
'argTwo',
@@ -715,6 +779,15 @@
expectedReplace: '@A(argTwo: ^, argOne: 1)',
expectedInsert: '@A(argTwo: ^, argOne: 1)',
);
+
+ // Partially typed names in front of values (that aren't considered part of
+ // the same identifier) should also suggest name labels.
+ await check(
+ '''@A(argOne: 1, argTh^'Foo')''',
+ 'argThree: ',
+ expectedReplace: '''@A(argOne: 1, argThree: 'Foo')''',
+ expectedInsert: '''@A(argOne: 1, argThree: 'Foo')''',
+ );
}
Future<void> test_namedArg_offsetBeforeCompletionTarget() async {
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 5ad6556..2cf21e5 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -283,6 +283,22 @@
});
}
+ TextDocumentClientCapabilities withCompletionItemInsertTextModeSupport(
+ TextDocumentClientCapabilities source,
+ ) {
+ return extendTextDocumentCapabilities(source, {
+ 'completion': {
+ 'completionItem': {
+ 'insertTextModeSupport': {
+ 'valueSet': [InsertTextMode.adjustIndentation, InsertTextMode.asIs]
+ .map((k) => k.toJson())
+ .toList()
+ }
+ }
+ }
+ });
+ }
+
TextDocumentClientCapabilities withCompletionItemKinds(
TextDocumentClientCapabilities source,
List<CompletionItemKind> kinds,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart
index 14c84b6..57bfd6c 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart
@@ -11,35 +11,16 @@
void main() {
defineReflectiveSuite(() {
+ defineReflectiveTests(RemoveUnnecessaryCastMultiTest);
defineReflectiveTests(RemoveUnnecessaryCastTest);
});
}
@reflectiveTest
-class RemoveUnnecessaryCastTest extends FixProcessorTest {
+class RemoveUnnecessaryCastMultiTest extends FixProcessorTest {
@override
- FixKind get kind => DartFixKind.REMOVE_UNNECESSARY_CAST;
+ FixKind get kind => DartFixKind.REMOVE_UNNECESSARY_CAST_MULTI;
- Future<void> test_assignment() async {
- await resolveTestCode('''
-main(Object p) {
- if (p is String) {
- String v = ((p as String));
- print(v);
- }
-}
-''');
- await assertHasFix('''
-main(Object p) {
- if (p is String) {
- String v = p;
- print(v);
- }
-}
-''');
- }
-
- @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/45026')
Future<void> test_assignment_all() async {
await resolveTestCode('''
main(Object p, Object q) {
@@ -67,3 +48,28 @@
''');
}
}
+
+@reflectiveTest
+class RemoveUnnecessaryCastTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.REMOVE_UNNECESSARY_CAST;
+
+ Future<void> test_assignment() async {
+ await resolveTestCode('''
+main(Object p) {
+ if (p is String) {
+ String v = ((p as String));
+ print(v);
+ }
+}
+''');
+ await assertHasFix('''
+main(Object p) {
+ if (p is String) {
+ String v = p;
+ print(v);
+ }
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_boolean_with_bool_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_boolean_with_bool_test.dart
index 0fa2fd0..2d35370 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/replace_boolean_with_bool_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_boolean_with_bool_test.dart
@@ -11,16 +11,16 @@
void main() {
defineReflectiveSuite(() {
+ defineReflectiveTests(ReplaceBooleanWithBoolMultiTest);
defineReflectiveTests(ReplaceBooleanWithBoolTest);
});
}
@reflectiveTest
-class ReplaceBooleanWithBoolTest extends FixProcessorTest {
+class ReplaceBooleanWithBoolMultiTest extends FixProcessorTest {
@override
- FixKind get kind => DartFixKind.REPLACE_BOOLEAN_WITH_BOOL;
+ FixKind get kind => DartFixKind.REPLACE_BOOLEAN_WITH_BOOL_MULTI;
- @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/45026')
Future<void> test_all() async {
await resolveTestCode('''
main() {
@@ -35,6 +35,12 @@
}
''');
}
+}
+
+@reflectiveTest
+class ReplaceBooleanWithBoolTest extends FixProcessorTest {
+ @override
+ FixKind get kind => DartFixKind.REPLACE_BOOLEAN_WITH_BOOL;
Future<void> test_single() async {
await resolveTestCode('''
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/CompletionSuggestion.java b/pkg/analysis_server/tool/spec/generated/java/types/CompletionSuggestion.java
index 28e0a81..c9ffeeb 100644
--- a/pkg/analysis_server/tool/spec/generated/java/types/CompletionSuggestion.java
+++ b/pkg/analysis_server/tool/spec/generated/java/types/CompletionSuggestion.java
@@ -60,6 +60,21 @@
private final String displayText;
/**
+ * The offset of the start of the text to be replaced. If supplied, this should be used in
+ * preference to the offset provided on the containing completion results. This value may be
+ * provided independently of replacementLength (for example if only one differs from the completion
+ * result value).
+ */
+ private final Integer replacementOffset;
+
+ /**
+ * The length of the text to be replaced. If supplied, this should be used in preference to the
+ * offset provided on the containing completion results. This value may be provided independently
+ * of replacementOffset (for example if only one differs from the completion result value).
+ */
+ private final Integer replacementLength;
+
+ /**
* The offset, relative to the beginning of the completion, of where the selection should be placed
* after insertion.
*/
@@ -163,11 +178,13 @@
/**
* Constructor for {@link CompletionSuggestion}.
*/
- public CompletionSuggestion(String kind, int relevance, String completion, String displayText, int selectionOffset, int selectionLength, boolean isDeprecated, boolean isPotential, String docSummary, String docComplete, String declaringType, String defaultArgumentListString, int[] defaultArgumentListTextRanges, Element element, String returnType, List<String> parameterNames, List<String> parameterTypes, Integer requiredParameterCount, Boolean hasNamedParameters, String parameterName, String parameterType) {
+ public CompletionSuggestion(String kind, int relevance, String completion, String displayText, Integer replacementOffset, Integer replacementLength, int selectionOffset, int selectionLength, boolean isDeprecated, boolean isPotential, String docSummary, String docComplete, String declaringType, String defaultArgumentListString, int[] defaultArgumentListTextRanges, Element element, String returnType, List<String> parameterNames, List<String> parameterTypes, Integer requiredParameterCount, Boolean hasNamedParameters, String parameterName, String parameterType) {
this.kind = kind;
this.relevance = relevance;
this.completion = completion;
this.displayText = displayText;
+ this.replacementOffset = replacementOffset;
+ this.replacementLength = replacementLength;
this.selectionOffset = selectionOffset;
this.selectionLength = selectionLength;
this.isDeprecated = isDeprecated;
@@ -196,6 +213,8 @@
other.relevance == relevance &&
ObjectUtilities.equals(other.completion, completion) &&
ObjectUtilities.equals(other.displayText, displayText) &&
+ ObjectUtilities.equals(other.replacementOffset, replacementOffset) &&
+ ObjectUtilities.equals(other.replacementLength, replacementLength) &&
other.selectionOffset == selectionOffset &&
other.selectionLength == selectionLength &&
other.isDeprecated == isDeprecated &&
@@ -222,6 +241,8 @@
int relevance = jsonObject.get("relevance").getAsInt();
String completion = jsonObject.get("completion").getAsString();
String displayText = jsonObject.get("displayText") == null ? null : jsonObject.get("displayText").getAsString();
+ Integer replacementOffset = jsonObject.get("replacementOffset") == null ? null : jsonObject.get("replacementOffset").getAsInt();
+ Integer replacementLength = jsonObject.get("replacementLength") == null ? null : jsonObject.get("replacementLength").getAsInt();
int selectionOffset = jsonObject.get("selectionOffset").getAsInt();
int selectionLength = jsonObject.get("selectionLength").getAsInt();
boolean isDeprecated = jsonObject.get("isDeprecated").getAsBoolean();
@@ -239,7 +260,7 @@
Boolean hasNamedParameters = jsonObject.get("hasNamedParameters") == null ? null : jsonObject.get("hasNamedParameters").getAsBoolean();
String parameterName = jsonObject.get("parameterName") == null ? null : jsonObject.get("parameterName").getAsString();
String parameterType = jsonObject.get("parameterType") == null ? null : jsonObject.get("parameterType").getAsString();
- return new CompletionSuggestion(kind, relevance, completion, displayText, selectionOffset, selectionLength, isDeprecated, isPotential, docSummary, docComplete, declaringType, defaultArgumentListString, defaultArgumentListTextRanges, element, returnType, parameterNames, parameterTypes, requiredParameterCount, hasNamedParameters, parameterName, parameterType);
+ return new CompletionSuggestion(kind, relevance, completion, displayText, replacementOffset, replacementLength, selectionOffset, selectionLength, isDeprecated, isPotential, docSummary, docComplete, declaringType, defaultArgumentListString, defaultArgumentListTextRanges, element, returnType, parameterNames, parameterTypes, requiredParameterCount, hasNamedParameters, parameterName, parameterType);
}
public static List<CompletionSuggestion> fromJsonArray(JsonArray jsonArray) {
@@ -390,6 +411,25 @@
}
/**
+ * The length of the text to be replaced. If supplied, this should be used in preference to the
+ * offset provided on the containing completion results. This value may be provided independently
+ * of replacementOffset (for example if only one differs from the completion result value).
+ */
+ public Integer getReplacementLength() {
+ return replacementLength;
+ }
+
+ /**
+ * The offset of the start of the text to be replaced. If supplied, this should be used in
+ * preference to the offset provided on the containing completion results. This value may be
+ * provided independently of replacementLength (for example if only one differs from the completion
+ * result value).
+ */
+ public Integer getReplacementOffset() {
+ return replacementOffset;
+ }
+
+ /**
* The number of required parameters for the function or method being suggested. This field is
* omitted if the parameterNames field is omitted.
*/
@@ -427,6 +467,8 @@
builder.append(relevance);
builder.append(completion);
builder.append(displayText);
+ builder.append(replacementOffset);
+ builder.append(replacementLength);
builder.append(selectionOffset);
builder.append(selectionLength);
builder.append(isDeprecated);
@@ -455,6 +497,12 @@
if (displayText != null) {
jsonObject.addProperty("displayText", displayText);
}
+ if (replacementOffset != null) {
+ jsonObject.addProperty("replacementOffset", replacementOffset);
+ }
+ if (replacementLength != null) {
+ jsonObject.addProperty("replacementLength", replacementLength);
+ }
jsonObject.addProperty("selectionOffset", selectionOffset);
jsonObject.addProperty("selectionLength", selectionLength);
jsonObject.addProperty("isDeprecated", isDeprecated);
@@ -525,6 +573,10 @@
builder.append(completion + ", ");
builder.append("displayText=");
builder.append(displayText + ", ");
+ builder.append("replacementOffset=");
+ builder.append(replacementOffset + ", ");
+ builder.append("replacementLength=");
+ builder.append(replacementLength + ", ");
builder.append("selectionOffset=");
builder.append(selectionOffset + ", ");
builder.append("selectionLength=");
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index 5a582d5..df3cadf 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -7,7 +7,7 @@
<body>
<h1>Analysis Server API Specification</h1>
<h1 style="color:#999999">Version
- <version>1.32.4</version>
+ <version>1.32.5</version>
</h1>
<p>
This document contains a specification of the API provided by the
@@ -134,6 +134,13 @@
ignoring the item or treating it with some default/fallback handling.
</p>
<h3>Changelog</h3>
+<h4>1.32.5</h4>
+<ul>
+ <li>Added optional <tt>replacementOffset</tt> and <tt>replacementLength</tt> on
+ <tt>CompletionSuggestion</tt> to support different per-completion text replacement
+ ranges (for example when inserting names for arguments, a different range may be
+ supplied than for completions that are not name labels).</li>
+</ul>
<h4>1.32.4</h4>
<ul>
<li>Added <tt>ElementKind.TYPE_ALIAS</tt> and <tt>HighlightRegionType.TYPE_ALIAS</tt>
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
index a2a48cd..dc0fead 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
@@ -601,6 +601,8 @@
/// "relevance": int
/// "completion": String
/// "displayText": optional String
+/// "replacementOffset": optional int
+/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@@ -630,6 +632,10 @@
String _displayText;
+ int _replacementOffset;
+
+ int _replacementLength;
+
int _selectionOffset;
int _selectionLength;
@@ -711,6 +717,36 @@
_displayText = value;
}
+ /// The offset of the start of the text to be replaced. If supplied, this
+ /// should be used in preference to the offset provided on the containing
+ /// completion results. This value may be provided independently of
+ /// replacementLength (for example if only one differs from the completion
+ /// result value).
+ int get replacementOffset => _replacementOffset;
+
+ /// The offset of the start of the text to be replaced. If supplied, this
+ /// should be used in preference to the offset provided on the containing
+ /// completion results. This value may be provided independently of
+ /// replacementLength (for example if only one differs from the completion
+ /// result value).
+ set replacementOffset(int value) {
+ _replacementOffset = value;
+ }
+
+ /// The length of the text to be replaced. If supplied, this should be used
+ /// in preference to the offset provided on the containing completion
+ /// results. This value may be provided independently of replacementOffset
+ /// (for example if only one differs from the completion result value).
+ int get replacementLength => _replacementLength;
+
+ /// The length of the text to be replaced. If supplied, this should be used
+ /// in preference to the offset provided on the containing completion
+ /// results. This value may be provided independently of replacementOffset
+ /// (for example if only one differs from the completion result value).
+ set replacementLength(int value) {
+ _replacementLength = value;
+ }
+
/// The offset, relative to the beginning of the completion, of where the
/// selection should be placed after insertion.
int get selectionOffset => _selectionOffset;
@@ -904,6 +940,8 @@
bool isDeprecated,
bool isPotential,
{String displayText,
+ int replacementOffset,
+ int replacementLength,
String docSummary,
String docComplete,
String declaringType,
@@ -921,6 +959,8 @@
this.relevance = relevance;
this.completion = completion;
this.displayText = displayText;
+ this.replacementOffset = replacementOffset;
+ this.replacementLength = replacementLength;
this.selectionOffset = selectionOffset;
this.selectionLength = selectionLength;
this.isDeprecated = isDeprecated;
@@ -970,6 +1010,16 @@
displayText = jsonDecoder.decodeString(
jsonPath + '.displayText', json['displayText']);
}
+ int replacementOffset;
+ if (json.containsKey('replacementOffset')) {
+ replacementOffset = jsonDecoder.decodeInt(
+ jsonPath + '.replacementOffset', json['replacementOffset']);
+ }
+ int replacementLength;
+ if (json.containsKey('replacementLength')) {
+ replacementLength = jsonDecoder.decodeInt(
+ jsonPath + '.replacementLength', json['replacementLength']);
+ }
int selectionOffset;
if (json.containsKey('selectionOffset')) {
selectionOffset = jsonDecoder.decodeInt(
@@ -1070,6 +1120,8 @@
return CompletionSuggestion(kind, relevance, completion, selectionOffset,
selectionLength, isDeprecated, isPotential,
displayText: displayText,
+ replacementOffset: replacementOffset,
+ replacementLength: replacementLength,
docSummary: docSummary,
docComplete: docComplete,
declaringType: declaringType,
@@ -1097,6 +1149,12 @@
if (displayText != null) {
result['displayText'] = displayText;
}
+ if (replacementOffset != null) {
+ result['replacementOffset'] = replacementOffset;
+ }
+ if (replacementLength != null) {
+ result['replacementLength'] = replacementLength;
+ }
result['selectionOffset'] = selectionOffset;
result['selectionLength'] = selectionLength;
result['isDeprecated'] = isDeprecated;
@@ -1153,6 +1211,8 @@
relevance == other.relevance &&
completion == other.completion &&
displayText == other.displayText &&
+ replacementOffset == other.replacementOffset &&
+ replacementLength == other.replacementLength &&
selectionOffset == other.selectionOffset &&
selectionLength == other.selectionLength &&
isDeprecated == other.isDeprecated &&
@@ -1184,6 +1244,8 @@
hash = JenkinsSmiHash.combine(hash, relevance.hashCode);
hash = JenkinsSmiHash.combine(hash, completion.hashCode);
hash = JenkinsSmiHash.combine(hash, displayText.hashCode);
+ hash = JenkinsSmiHash.combine(hash, replacementOffset.hashCode);
+ hash = JenkinsSmiHash.combine(hash, replacementLength.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionOffset.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionLength.hashCode);
hash = JenkinsSmiHash.combine(hash, isDeprecated.hashCode);
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
index 9b566ba..6772e9d 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
@@ -6,7 +6,7 @@
// To regenerate the file, use the script
// "pkg/analysis_server/tool/spec/generate_files".
-const String PROTOCOL_VERSION = '1.32.4';
+const String PROTOCOL_VERSION = '1.32.5';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
diff --git a/pkg/analyzer_plugin/doc/api.html b/pkg/analyzer_plugin/doc/api.html
index fa5c8e0..768e479 100644
--- a/pkg/analyzer_plugin/doc/api.html
+++ b/pkg/analyzer_plugin/doc/api.html
@@ -1015,6 +1015,30 @@
is only defined if the displayed text should be different than the
completion. Otherwise it is omitted.
</p>
+ </dd><dt class="field"><b>replacementOffset: int<span style="color:#999999"> (optional)</span></b></dt><dd>
+
+ <p>
+ The offset of the start of the text to be
+ replaced. If supplied, this should be used in
+ preference to the offset provided on the
+ containing completion results.
+
+ This value may be provided independently of
+ replacementLength (for example if only one
+ differs from the completion result value).
+ </p>
+ </dd><dt class="field"><b>replacementLength: int<span style="color:#999999"> (optional)</span></b></dt><dd>
+
+ <p>
+ The length of the text to be replaced.
+ If supplied, this should be used in preference
+ to the offset provided on the
+ containing completion results.
+
+ This value may be provided independently of
+ replacementOffset (for example if only one
+ differs from the completion result value).
+ </p>
</dd><dt class="field"><b>selectionOffset: int</b></dt><dd>
<p>
diff --git a/pkg/analyzer_plugin/lib/protocol/protocol_common.dart b/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
index 163a7416..5dbc101 100644
--- a/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
+++ b/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
@@ -601,6 +601,8 @@
/// "relevance": int
/// "completion": String
/// "displayText": optional String
+/// "replacementOffset": optional int
+/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@@ -630,6 +632,10 @@
String _displayText;
+ int _replacementOffset;
+
+ int _replacementLength;
+
int _selectionOffset;
int _selectionLength;
@@ -711,6 +717,36 @@
_displayText = value;
}
+ /// The offset of the start of the text to be replaced. If supplied, this
+ /// should be used in preference to the offset provided on the containing
+ /// completion results. This value may be provided independently of
+ /// replacementLength (for example if only one differs from the completion
+ /// result value).
+ int get replacementOffset => _replacementOffset;
+
+ /// The offset of the start of the text to be replaced. If supplied, this
+ /// should be used in preference to the offset provided on the containing
+ /// completion results. This value may be provided independently of
+ /// replacementLength (for example if only one differs from the completion
+ /// result value).
+ set replacementOffset(int value) {
+ _replacementOffset = value;
+ }
+
+ /// The length of the text to be replaced. If supplied, this should be used
+ /// in preference to the offset provided on the containing completion
+ /// results. This value may be provided independently of replacementOffset
+ /// (for example if only one differs from the completion result value).
+ int get replacementLength => _replacementLength;
+
+ /// The length of the text to be replaced. If supplied, this should be used
+ /// in preference to the offset provided on the containing completion
+ /// results. This value may be provided independently of replacementOffset
+ /// (for example if only one differs from the completion result value).
+ set replacementLength(int value) {
+ _replacementLength = value;
+ }
+
/// The offset, relative to the beginning of the completion, of where the
/// selection should be placed after insertion.
int get selectionOffset => _selectionOffset;
@@ -904,6 +940,8 @@
bool isDeprecated,
bool isPotential,
{String displayText,
+ int replacementOffset,
+ int replacementLength,
String docSummary,
String docComplete,
String declaringType,
@@ -921,6 +959,8 @@
this.relevance = relevance;
this.completion = completion;
this.displayText = displayText;
+ this.replacementOffset = replacementOffset;
+ this.replacementLength = replacementLength;
this.selectionOffset = selectionOffset;
this.selectionLength = selectionLength;
this.isDeprecated = isDeprecated;
@@ -970,6 +1010,16 @@
displayText = jsonDecoder.decodeString(
jsonPath + '.displayText', json['displayText']);
}
+ int replacementOffset;
+ if (json.containsKey('replacementOffset')) {
+ replacementOffset = jsonDecoder.decodeInt(
+ jsonPath + '.replacementOffset', json['replacementOffset']);
+ }
+ int replacementLength;
+ if (json.containsKey('replacementLength')) {
+ replacementLength = jsonDecoder.decodeInt(
+ jsonPath + '.replacementLength', json['replacementLength']);
+ }
int selectionOffset;
if (json.containsKey('selectionOffset')) {
selectionOffset = jsonDecoder.decodeInt(
@@ -1070,6 +1120,8 @@
return CompletionSuggestion(kind, relevance, completion, selectionOffset,
selectionLength, isDeprecated, isPotential,
displayText: displayText,
+ replacementOffset: replacementOffset,
+ replacementLength: replacementLength,
docSummary: docSummary,
docComplete: docComplete,
declaringType: declaringType,
@@ -1097,6 +1149,12 @@
if (displayText != null) {
result['displayText'] = displayText;
}
+ if (replacementOffset != null) {
+ result['replacementOffset'] = replacementOffset;
+ }
+ if (replacementLength != null) {
+ result['replacementLength'] = replacementLength;
+ }
result['selectionOffset'] = selectionOffset;
result['selectionLength'] = selectionLength;
result['isDeprecated'] = isDeprecated;
@@ -1153,6 +1211,8 @@
relevance == other.relevance &&
completion == other.completion &&
displayText == other.displayText &&
+ replacementOffset == other.replacementOffset &&
+ replacementLength == other.replacementLength &&
selectionOffset == other.selectionOffset &&
selectionLength == other.selectionLength &&
isDeprecated == other.isDeprecated &&
@@ -1184,6 +1244,8 @@
hash = JenkinsSmiHash.combine(hash, relevance.hashCode);
hash = JenkinsSmiHash.combine(hash, completion.hashCode);
hash = JenkinsSmiHash.combine(hash, displayText.hashCode);
+ hash = JenkinsSmiHash.combine(hash, replacementOffset.hashCode);
+ hash = JenkinsSmiHash.combine(hash, replacementLength.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionOffset.hashCode);
hash = JenkinsSmiHash.combine(hash, selectionLength.hashCode);
hash = JenkinsSmiHash.combine(hash, isDeprecated.hashCode);
diff --git a/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart b/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
index 7f36610..e00577b 100644
--- a/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
+++ b/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
@@ -119,6 +119,8 @@
/// "relevance": int
/// "completion": String
/// "displayText": optional String
+/// "replacementOffset": optional int
+/// "replacementLength": optional int
/// "selectionOffset": int
/// "selectionLength": int
/// "isDeprecated": bool
@@ -148,6 +150,8 @@
'isPotential': isBool
}, optionalFields: {
'displayText': isString,
+ 'replacementOffset': isInt,
+ 'replacementLength': isInt,
'docSummary': isString,
'docComplete': isString,
'declaringType': isString,
diff --git a/pkg/analyzer_plugin/tool/spec/common_types_spec.html b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
index 8608963..984b617 100644
--- a/pkg/analyzer_plugin/tool/spec/common_types_spec.html
+++ b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
@@ -6,7 +6,7 @@
</head>
<body>
<h1>Common Types</h1>
-<version>1.4.2</version>
+<version>1.4.3</version>
<p>
This document contains a specification of the types that are common between
the analysis server wire protocol and the analysis server plugin wire
@@ -221,6 +221,32 @@
completion. Otherwise it is omitted.
</p>
</field>
+ <field name="replacementOffset" optional="true">
+ <ref>int</ref>
+ <p>
+ The offset of the start of the text to be
+ replaced. If supplied, this should be used in
+ preference to the offset provided on the
+ containing completion results.
+
+ This value may be provided independently of
+ replacementLength (for example if only one
+ differs from the completion result value).
+ </p>
+ </field>
+ <field name="replacementLength" optional="true">
+ <ref>int</ref>
+ <p>
+ The length of the text to be replaced.
+ If supplied, this should be used in preference
+ to the offset provided on the
+ containing completion results.
+
+ This value may be provided independently of
+ replacementOffset (for example if only one
+ differs from the completion result value).
+ </p>
+ </field>
<field name="selectionOffset">
<ref>int</ref>
<p>
diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
index 1a91ae1..fc818c1 100644
--- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart
+++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
@@ -878,54 +878,68 @@
return null;
}
+ TypeInformation _handleLocalFunctionInvocation(
+ ir.Expression node,
+ ir.FunctionDeclaration function,
+ ir.Arguments arguments,
+ Selector selector) {
+ ArgumentsTypes argumentsTypes = analyzeArguments(arguments);
+ ClosureRepresentationInfo info =
+ _closureDataLookup.getClosureInfo(function);
+ if (isIncompatibleInvoke(info.callMethod, argumentsTypes)) {
+ return _types.dynamicType;
+ }
+
+ TypeInformation type =
+ handleStaticInvoke(node, selector, info.callMethod, argumentsTypes);
+ FunctionType functionType =
+ _elementMap.elementEnvironment.getFunctionType(info.callMethod);
+ if (functionType.returnType.containsFreeTypeVariables) {
+ // The return type varies with the call site so we narrow the static
+ // return type.
+ type = _types.narrowType(type, _getStaticType(node));
+ }
+ return type;
+ }
+
@override
- TypeInformation visitMethodInvocation(ir.MethodInvocation node) {
+ TypeInformation visitLocalFunctionInvocation(
+ ir.LocalFunctionInvocation node) {
Selector selector = _elementMap.getSelector(node);
- AbstractValue mask = _typeOfReceiver(node, node.receiver);
+ return _handleLocalFunctionInvocation(
+ node, node.variable.parent, node.arguments, selector);
+ }
- ir.TreeNode receiver = node.receiver;
- if (receiver is ir.VariableGet &&
- receiver.variable.parent is ir.FunctionDeclaration) {
- // This is an invocation of a named local function.
- ArgumentsTypes arguments = analyzeArguments(node.arguments);
- ClosureRepresentationInfo info =
- _closureDataLookup.getClosureInfo(receiver.variable.parent);
- if (isIncompatibleInvoke(info.callMethod, arguments)) {
- return _types.dynamicType;
- }
+ TypeInformation _handleEqualsNull(ir.Expression node, ir.Expression operand,
+ {bool isNot}) {
+ assert(isNot != null);
+ _potentiallyAddNullCheck(node, operand, isNot: isNot);
+ return _types.boolType;
+ }
- TypeInformation type =
- handleStaticInvoke(node, selector, info.callMethod, arguments);
- FunctionType functionType =
- _elementMap.elementEnvironment.getFunctionType(info.callMethod);
- if (functionType.returnType.containsFreeTypeVariables) {
- // The return type varies with the call site so we narrow the static
- // return type.
- type = _types.narrowType(type, _getStaticType(node));
- }
- return type;
- }
+ @override
+ TypeInformation visitEqualsNull(ir.EqualsNull node) {
+ // TODO(johnniwinther). This triggers the computation of the mask for the
+ // receiver of the call to `==`, which doesn't happen in this case. Remove
+ // this when the ssa builder recognized `== null` directly.
+ _typeOfReceiver(node, node.expression);
+ return _handleEqualsNull(node, node.expression, isNot: node.isNot);
+ }
- TypeInformation receiverType = visit(receiver);
- ArgumentsTypes arguments = analyzeArguments(node.arguments);
- if (selector.name == '==') {
- if (_types.isNull(receiverType)) {
- // null == o
- _potentiallyAddNullCheck(node, node.arguments.positional.first);
- return _types.boolType;
- } else if (_types.isNull(arguments.positional[0])) {
- // o == null
- _potentiallyAddNullCheck(node, node.receiver);
- return _types.boolType;
- }
- }
- if (node.receiver is ir.ThisExpression) {
+ TypeInformation _handleMethodInvocation(
+ ir.Expression node,
+ ir.Expression receiver,
+ TypeInformation receiverType,
+ Selector selector,
+ ArgumentsTypes arguments,
+ ir.Member interfaceTarget) {
+ AbstractValue mask = _typeOfReceiver(node, receiver);
+ if (receiver is ir.ThisExpression) {
_checkIfExposesThis(
selector, _types.newTypedSelector(receiverType, mask));
}
TypeInformation type = handleDynamicInvoke(
CallType.access, node, selector, mask, receiverType, arguments);
- ir.Member interfaceTarget = node.interfaceTarget;
if (interfaceTarget != null) {
if (interfaceTarget is ir.Procedure &&
(interfaceTarget.kind == ir.ProcedureKind.Method ||
@@ -952,6 +966,92 @@
return type;
}
+ TypeInformation _handleEqualsCall(ir.Expression node, ir.Expression left,
+ TypeInformation leftType, ir.Expression right, TypeInformation rightType,
+ {bool isNot}) {
+ assert(isNot != null);
+ // TODO(johnniwinther). This triggers the computation of the mask for the
+ // receiver of the call to `==`, which might not happen in this case. Remove
+ // this when the ssa builder recognized `== null` directly.
+ _typeOfReceiver(node, left);
+ if (_types.isNull(leftType)) {
+ // null == o
+ return _handleEqualsNull(node, right, isNot: isNot);
+ } else if (_types.isNull(rightType)) {
+ // o == null
+ return _handleEqualsNull(node, left, isNot: isNot);
+ }
+ Selector selector = Selector.binaryOperator('==');
+ ArgumentsTypes arguments = ArgumentsTypes([rightType], null);
+ return _handleMethodInvocation(
+ node, left, leftType, selector, arguments, null);
+ }
+
+ @override
+ TypeInformation visitEqualsCall(ir.EqualsCall node) {
+ TypeInformation leftType = visit(node.left);
+ TypeInformation rightType = visit(node.right);
+ return _handleEqualsCall(node, node.left, leftType, node.right, rightType,
+ isNot: node.isNot);
+ }
+
+ @override
+ TypeInformation visitInstanceInvocation(ir.InstanceInvocation node) {
+ Selector selector = _elementMap.getSelector(node);
+ ir.Expression receiver = node.receiver;
+ TypeInformation receiverType = visit(receiver);
+ ArgumentsTypes arguments = analyzeArguments(node.arguments);
+ return _handleMethodInvocation(node, node.receiver, receiverType, selector,
+ arguments, node.interfaceTarget);
+ }
+
+ @override
+ TypeInformation visitDynamicInvocation(ir.DynamicInvocation node) {
+ Selector selector = _elementMap.getSelector(node);
+ ir.Expression receiver = node.receiver;
+ TypeInformation receiverType = visit(receiver);
+ ArgumentsTypes arguments = analyzeArguments(node.arguments);
+ return _handleMethodInvocation(
+ node, node.receiver, receiverType, selector, arguments, null);
+ }
+
+ @override
+ TypeInformation visitFunctionInvocation(ir.FunctionInvocation node) {
+ Selector selector = _elementMap.getSelector(node);
+ ir.Expression receiver = node.receiver;
+ TypeInformation receiverType = visit(receiver);
+ ArgumentsTypes arguments = analyzeArguments(node.arguments);
+ return _handleMethodInvocation(
+ node, node.receiver, receiverType, selector, arguments, null);
+ }
+
+ @override
+ TypeInformation visitMethodInvocation(ir.MethodInvocation node) {
+ Selector selector = _elementMap.getSelector(node);
+ ir.Expression receiver = node.receiver;
+ if (receiver is ir.VariableGet &&
+ receiver.variable.parent is ir.FunctionDeclaration) {
+ // TODO(johnniwinther). This triggers the computation of the mask for the
+ // receiver of the call to `call`. Remove this when the ssa builder
+ // recognized local function invocation directly.
+ _typeOfReceiver(node, node.receiver);
+ // This is an invocation of a named local function.
+ return _handleLocalFunctionInvocation(
+ node, receiver.variable.parent, node.arguments, selector);
+ }
+
+ TypeInformation receiverType = visit(receiver);
+ ArgumentsTypes arguments = analyzeArguments(node.arguments);
+ if (selector.name == '==') {
+ return _handleEqualsCall(node, node.receiver, receiverType,
+ node.arguments.positional.first, arguments.positional[0],
+ isNot: false);
+ }
+
+ return _handleMethodInvocation(node, node.receiver, receiverType, selector,
+ arguments, node.interfaceTarget);
+ }
+
TypeInformation _handleDynamic(
CallType callType,
ir.Node node,
@@ -1454,6 +1554,11 @@
return createStaticGetTypeInformation(node, node.target);
}
+ @override
+ TypeInformation visitStaticTearOff(ir.StaticTearOff node) {
+ return createStaticGetTypeInformation(node, node.target);
+ }
+
TypeInformation createStaticGetTypeInformation(
ir.Node node, ir.Member target) {
MemberEntity member = _elementMap.getMember(target);
@@ -1473,12 +1578,12 @@
return rhsType;
}
- TypeInformation handlePropertyGet(ir.TreeNode node, ir.TreeNode receiver,
- TypeInformation receiverType, ir.Member interfaceTarget,
- {bool isThis}) {
+ TypeInformation _handlePropertyGet(ir.Expression node, ir.Expression receiver,
+ {ir.Member interfaceTarget}) {
+ TypeInformation receiverType = visit(receiver);
Selector selector = _elementMap.getSelector(node);
AbstractValue mask = _typeOfReceiver(node, receiver);
- if (isThis) {
+ if (receiver is ir.ThisExpression) {
_checkIfExposesThis(
selector, _types.newTypedSelector(receiverType, mask));
}
@@ -1498,25 +1603,46 @@
}
@override
- TypeInformation visitPropertyGet(ir.PropertyGet node) {
- TypeInformation receiverType = visit(node.receiver);
- return handlePropertyGet(
- node, node.receiver, receiverType, node.interfaceTarget,
- isThis: node.receiver is ir.ThisExpression);
+ TypeInformation visitInstanceGet(ir.InstanceGet node) {
+ return _handlePropertyGet(node, node.receiver,
+ interfaceTarget: node.interfaceTarget);
}
@override
- TypeInformation visitPropertySet(ir.PropertySet node) {
- TypeInformation receiverType = visit(node.receiver);
- Selector selector = _elementMap.getSelector(node);
- AbstractValue mask = _typeOfReceiver(node, node.receiver);
+ TypeInformation visitInstanceTearOff(ir.InstanceTearOff node) {
+ return _handlePropertyGet(node, node.receiver,
+ interfaceTarget: node.interfaceTarget);
+ }
- TypeInformation rhsType = visit(node.value);
- if (node.value is ir.ThisExpression) {
+ @override
+ TypeInformation visitDynamicGet(ir.DynamicGet node) {
+ return _handlePropertyGet(node, node.receiver);
+ }
+
+ @override
+ TypeInformation visitFunctionTearOff(ir.FunctionTearOff node) {
+ return _handlePropertyGet(node, node.receiver);
+ }
+
+ @override
+ TypeInformation visitPropertyGet(ir.PropertyGet node) {
+ return _handlePropertyGet(node, node.receiver,
+ interfaceTarget: node.interfaceTarget);
+ }
+
+ TypeInformation _handlePropertySet(
+ ir.Expression node, ir.Expression receiver, ir.Expression value,
+ {ir.Member interfaceTarget}) {
+ TypeInformation receiverType = visit(receiver);
+ Selector selector = _elementMap.getSelector(node);
+ AbstractValue mask = _typeOfReceiver(node, receiver);
+
+ TypeInformation rhsType = visit(value);
+ if (value is ir.ThisExpression) {
_state.markThisAsExposed();
}
- if (_inGenerativeConstructor && node.receiver is ir.ThisExpression) {
+ if (_inGenerativeConstructor && receiver is ir.ThisExpression) {
AbstractValue typedMask = _types.newTypedSelector(receiverType, mask);
if (!_closedWorld.includesClosureCall(selector, typedMask)) {
Iterable<MemberEntity> targets =
@@ -1533,7 +1659,7 @@
}
}
}
- if (node.receiver is ir.ThisExpression) {
+ if (receiver is ir.ThisExpression) {
_checkIfExposesThis(
selector, _types.newTypedSelector(receiverType, mask));
}
@@ -1542,6 +1668,23 @@
}
@override
+ TypeInformation visitPropertySet(ir.PropertySet node) {
+ return _handlePropertySet(node, node.receiver, node.value,
+ interfaceTarget: node.interfaceTarget);
+ }
+
+ @override
+ TypeInformation visitInstanceSet(ir.InstanceSet node) {
+ return _handlePropertySet(node, node.receiver, node.value,
+ interfaceTarget: node.interfaceTarget);
+ }
+
+ @override
+ TypeInformation visitDynamicSet(ir.DynamicSet node) {
+ return _handlePropertySet(node, node.receiver, node.value);
+ }
+
+ @override
TypeInformation visitThisExpression(ir.ThisExpression node) {
return thisType;
}
@@ -1573,27 +1716,39 @@
}
}
- void _potentiallyAddNullCheck(
- ir.MethodInvocation node, ir.Expression receiver) {
+ void _potentiallyAddNullCheck(ir.Expression node, ir.Expression receiver,
+ {bool isNot}) {
+ assert(isNot != null);
if (!_accumulateIsChecks) return;
if (receiver is ir.VariableGet) {
Local local = _localsMap.getLocalVariable(receiver.variable);
DartType localType = _localsMap.getLocalType(_elementMap, local);
- LocalState stateAfterCheckWhenTrue = new LocalState.childPath(_state);
- LocalState stateAfterCheckWhenFalse = new LocalState.childPath(_state);
+ LocalState stateAfterCheckWhenNull = new LocalState.childPath(_state);
+ LocalState stateAfterCheckWhenNotNull = new LocalState.childPath(_state);
// Narrow tested variable to 'Null' on true branch.
- stateAfterCheckWhenTrue.updateLocal(_inferrer, _capturedAndBoxed, local,
+ stateAfterCheckWhenNull.updateLocal(_inferrer, _capturedAndBoxed, local,
_types.nullType, node, localType);
// Narrow tested variable to 'not null' on false branch.
- TypeInformation currentTypeInformation = stateAfterCheckWhenFalse
+ TypeInformation currentTypeInformation = stateAfterCheckWhenNotNull
.readLocal(_inferrer, _capturedAndBoxed, local);
- stateAfterCheckWhenFalse.updateLocal(_inferrer, _capturedAndBoxed, local,
- currentTypeInformation, node, _closedWorld.commonElements.objectType,
+ stateAfterCheckWhenNotNull.updateLocal(
+ _inferrer,
+ _capturedAndBoxed,
+ local,
+ currentTypeInformation,
+ node,
+ _closedWorld.commonElements.objectType,
excludeNull: true);
- _setStateAfter(_state, stateAfterCheckWhenTrue, stateAfterCheckWhenFalse);
+ if (isNot) {
+ _setStateAfter(
+ _state, stateAfterCheckWhenNotNull, stateAfterCheckWhenNull);
+ } else {
+ _setStateAfter(
+ _state, stateAfterCheckWhenNull, stateAfterCheckWhenNotNull);
+ }
}
}
diff --git a/pkg/compiler/lib/src/inferrer/locals_handler.dart b/pkg/compiler/lib/src/inferrer/locals_handler.dart
index a94ddbd..49ee715 100644
--- a/pkg/compiler/lib/src/inferrer/locals_handler.dart
+++ b/pkg/compiler/lib/src/inferrer/locals_handler.dart
@@ -244,7 +244,8 @@
class ArgumentsTypes extends IterableMixin<TypeInformation> {
final List<TypeInformation> positional;
final Map<String, TypeInformation> named;
- ArgumentsTypes(this.positional, named)
+
+ ArgumentsTypes(this.positional, Map<String, TypeInformation> named)
: this.named = (named == null || named.isEmpty) ? const {} : named {
assert(this.positional.every((TypeInformation type) => type != null));
assert(this.named.values.every((TypeInformation type) => type != null));
diff --git a/pkg/compiler/lib/src/js_model/element_map.dart b/pkg/compiler/lib/src/js_model/element_map.dart
index 4d6febd..44b6cd1 100644
--- a/pkg/compiler/lib/src/js_model/element_map.dart
+++ b/pkg/compiler/lib/src/js_model/element_map.dart
@@ -163,15 +163,21 @@
AbstractValue getReturnTypeOf(FunctionEntity function);
/// Returns the inferred receiver type of the dynamic [invocation].
+ // TODO(johnniwinther): Improve the type of the [invocation] once the new
+ // method invocation encoding is fully utilized.
AbstractValue receiverTypeOfInvocation(
- ir.MethodInvocation invocation, AbstractValueDomain abstractValueDomain);
+ ir.Expression invocation, AbstractValueDomain abstractValueDomain);
/// Returns the inferred receiver type of the dynamic [read].
- AbstractValue receiverTypeOfGet(ir.PropertyGet read);
+ // TODO(johnniwinther): Improve the type of the [invocation] once the new
+ // method invocation encoding is fully utilized.
+ AbstractValue receiverTypeOfGet(ir.Expression read);
/// Returns the inferred receiver type of the dynamic [write].
+ // TODO(johnniwinther): Improve the type of the [invocation] once the new
+ // method invocation encoding is fully utilized.
AbstractValue receiverTypeOfSet(
- ir.PropertySet write, AbstractValueDomain abstractValueDomain);
+ ir.Expression write, AbstractValueDomain abstractValueDomain);
/// Returns the inferred type of [listLiteral].
AbstractValue typeOfListLiteral(
diff --git a/pkg/compiler/lib/src/js_model/js_strategy.dart b/pkg/compiler/lib/src/js_model/js_strategy.dart
index d432e33..431efc6 100644
--- a/pkg/compiler/lib/src/js_model/js_strategy.dart
+++ b/pkg/compiler/lib/src/js_model/js_strategy.dart
@@ -525,18 +525,18 @@
@override
AbstractValue receiverTypeOfInvocation(
- ir.MethodInvocation node, AbstractValueDomain abstractValueDomain) {
+ ir.Expression node, AbstractValueDomain abstractValueDomain) {
return _targetResults.typeOfReceiver(node);
}
@override
- AbstractValue receiverTypeOfGet(ir.PropertyGet node) {
+ AbstractValue receiverTypeOfGet(ir.Expression node) {
return _targetResults.typeOfReceiver(node);
}
@override
AbstractValue receiverTypeOfSet(
- ir.PropertySet node, AbstractValueDomain abstractValueDomain) {
+ ir.Expression node, AbstractValueDomain abstractValueDomain) {
return _targetResults.typeOfReceiver(node);
}
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 235fefb..75156d4 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -3386,6 +3386,19 @@
}
@override
+ void visitStaticTearOff(ir.StaticTearOff node) {
+ // TODO(johnniwinther): This is a constant tear off, so we should have
+ // created a constant value instead. Remove this case when we use CFE
+ // constants.
+ ir.Member staticTarget = node.target;
+ SourceInformation sourceInformation =
+ _sourceInformationBuilder.buildGet(node);
+ FunctionEntity member = _elementMap.getMember(staticTarget);
+ push(new HStatic(member, _typeInferenceMap.getInferredTypeOf(member),
+ sourceInformation));
+ }
+
+ @override
void visitStaticSet(ir.StaticSet node) {
node.value.accept(this);
HInstruction value = pop();
@@ -3411,22 +3424,46 @@
stack.add(value);
}
- @override
- void visitPropertyGet(ir.PropertyGet node) {
- node.receiver.accept(this);
- HInstruction receiver = pop();
-
+ void _handlePropertyGet(
+ ir.Expression node, ir.Expression receiver, ir.Name name) {
+ receiver.accept(this);
+ HInstruction receiverInstruction = pop();
_pushDynamicInvocation(
node,
- _getStaticType(node.receiver),
+ _getStaticType(receiver),
_typeInferenceMap.receiverTypeOfGet(node),
- new Selector.getter(_elementMap.getName(node.name)),
- <HInstruction>[receiver],
+ new Selector.getter(_elementMap.getName(name)),
+ <HInstruction>[receiverInstruction],
const <DartType>[],
_sourceInformationBuilder.buildGet(node));
}
@override
+ void visitInstanceGet(ir.InstanceGet node) {
+ _handlePropertyGet(node, node.receiver, node.name);
+ }
+
+ @override
+ void visitInstanceTearOff(ir.InstanceTearOff node) {
+ _handlePropertyGet(node, node.receiver, node.name);
+ }
+
+ @override
+ void visitDynamicGet(ir.DynamicGet node) {
+ _handlePropertyGet(node, node.receiver, node.name);
+ }
+
+ @override
+ void visitFunctionTearOff(ir.FunctionTearOff node) {
+ _handlePropertyGet(node, node.receiver, ir.Name.callName);
+ }
+
+ @override
+ void visitPropertyGet(ir.PropertyGet node) {
+ _handlePropertyGet(node, node.receiver, node.name);
+ }
+
+ @override
void visitVariableGet(ir.VariableGet node) {
ir.VariableDeclaration variable = node.variable;
HInstruction letBinding = _letBindings[variable];
@@ -3440,24 +3477,39 @@
sourceInformation: _sourceInformationBuilder.buildGet(node)));
}
- @override
- void visitPropertySet(ir.PropertySet node) {
- node.receiver.accept(this);
- HInstruction receiver = pop();
- node.value.accept(this);
- HInstruction value = pop();
+ void _handlePropertySet(ir.Expression node, ir.Expression receiver,
+ ir.Name name, ir.Expression value) {
+ receiver.accept(this);
+ HInstruction receiverInstruction = pop();
+ value.accept(this);
+ HInstruction valueInstruction = pop();
_pushDynamicInvocation(
node,
- _getStaticType(node.receiver),
+ _getStaticType(receiver),
_typeInferenceMap.receiverTypeOfSet(node, _abstractValueDomain),
- new Selector.setter(_elementMap.getName(node.name)),
- <HInstruction>[receiver, value],
+ new Selector.setter(_elementMap.getName(name)),
+ <HInstruction>[receiverInstruction, valueInstruction],
const <DartType>[],
_sourceInformationBuilder.buildAssignment(node));
pop();
- stack.add(value);
+ stack.add(valueInstruction);
+ }
+
+ @override
+ void visitInstanceSet(ir.InstanceSet node) {
+ _handlePropertySet(node, node.receiver, node.name, node.value);
+ }
+
+ @override
+ void visitDynamicSet(ir.DynamicSet node) {
+ _handlePropertySet(node, node.receiver, node.name, node.value);
+ }
+
+ @override
+ void visitPropertySet(ir.PropertySet node) {
+ _handlePropertySet(node, node.receiver, node.name, node.value);
}
@override
@@ -5180,23 +5232,86 @@
push(instruction);
}
- @override
- void visitMethodInvocation(ir.MethodInvocation node) {
- node.receiver.accept(this);
- HInstruction receiver = pop();
+ void _handleMethodInvocation(
+ ir.Expression node, ir.Expression receiver, ir.Arguments arguments) {
+ receiver.accept(this);
+ HInstruction receiverInstruction = pop();
Selector selector = _elementMap.getSelector(node);
List<DartType> typeArguments = <DartType>[];
- selector =
- _fillDynamicTypeArguments(selector, node.arguments, typeArguments);
+ selector = _fillDynamicTypeArguments(selector, arguments, typeArguments);
_pushDynamicInvocation(
node,
- _getStaticType(node.receiver),
+ _getStaticType(receiver),
_typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain),
selector,
- <HInstruction>[receiver]..addAll(_visitArgumentsForDynamicTarget(
- selector, node.arguments, typeArguments)),
+ <HInstruction>[receiverInstruction]..addAll(
+ _visitArgumentsForDynamicTarget(
+ selector, arguments, typeArguments)),
typeArguments,
- _sourceInformationBuilder.buildCall(node.receiver, node));
+ _sourceInformationBuilder.buildCall(receiver, node));
+ }
+
+ @override
+ void visitInstanceInvocation(ir.InstanceInvocation node) {
+ _handleMethodInvocation(node, node.receiver, node.arguments);
+ }
+
+ @override
+ void visitDynamicInvocation(ir.DynamicInvocation node) {
+ _handleMethodInvocation(node, node.receiver, node.arguments);
+ }
+
+ @override
+ void visitFunctionInvocation(ir.FunctionInvocation node) {
+ _handleMethodInvocation(node, node.receiver, node.arguments);
+ }
+
+ @override
+ void visitLocalFunctionInvocation(ir.LocalFunctionInvocation node) {
+ _handleMethodInvocation(
+ node, ir.VariableGet(node.variable), node.arguments);
+ }
+
+ @override
+ void visitMethodInvocation(ir.MethodInvocation node) {
+ _handleMethodInvocation(node, node.receiver, node.arguments);
+ }
+
+ void _handleEquals(ir.Expression node, ir.Expression left,
+ HInstruction leftInstruction, HInstruction rightInstruction,
+ {bool isNot}) {
+ assert(isNot != null);
+ _pushDynamicInvocation(
+ node,
+ _getStaticType(left),
+ _typeInferenceMap.receiverTypeOfInvocation(node, _abstractValueDomain),
+ Selectors.equals,
+ <HInstruction>[leftInstruction, rightInstruction],
+ const <DartType>[],
+ _sourceInformationBuilder.buildCall(left, node));
+ if (isNot) {
+ push(new HNot(popBoolified(), _abstractValueDomain.boolType)
+ ..sourceInformation = _sourceInformationBuilder.buildUnary(node));
+ }
+ }
+
+ @override
+ void visitEqualsNull(ir.EqualsNull node) {
+ node.expression.accept(this);
+ HInstruction receiverInstruction = pop();
+ return _handleEquals(node, node.expression, receiverInstruction,
+ graph.addConstantNull(closedWorld),
+ isNot: node.isNot);
+ }
+
+ @override
+ void visitEqualsCall(ir.EqualsCall node) {
+ node.left.accept(this);
+ HInstruction leftInstruction = pop();
+ node.right.accept(this);
+ HInstruction rightInstruction = pop();
+ return _handleEquals(node, node.left, leftInstruction, rightInstruction,
+ isNot: node.isNot);
}
HInterceptor _interceptorFor(
diff --git a/pkg/compiler/test/analyses/dart2js_allowed.json b/pkg/compiler/test/analyses/dart2js_allowed.json
index 1096f17..10ea069 100644
--- a/pkg/compiler/test/analyses/dart2js_allowed.json
+++ b/pkg/compiler/test/analyses/dart2js_allowed.json
@@ -222,7 +222,6 @@
"Dynamic access of 'type'.": 4
},
"pkg/compiler/lib/src/inferrer/locals_handler.dart": {
- "Dynamic access of 'isEmpty'.": 1,
"Dynamic access of 'positional'.": 2,
"Dynamic access of 'length'.": 2,
"Dynamic access of 'named'.": 2,
diff --git a/pkg/front_end/lib/src/fasta/kernel/collections.dart b/pkg/front_end/lib/src/fasta/kernel/collections.dart
index 5a055f9..9b23f9f 100644
--- a/pkg/front_end/lib/src/fasta/kernel/collections.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/collections.dart
@@ -755,6 +755,14 @@
onConvertForMapEntry(entry, result);
return result;
}
+ Expression key = entry.key;
+ if (key is InvalidExpression) {
+ Expression value = entry.value;
+ if (value is NullLiteral && value.fileOffset == TreeNode.noOffset) {
+ // entry arose from an error. Don't build another error.
+ return key;
+ }
+ }
return helper.buildProblem(
templateExpectedButGot.withArguments(','),
entry.fileOffset,
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index 0d2f50f..547cc29 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -544,7 +544,7 @@
if (node.initializer != null) {
if (node.isConst) {
final Constant constant = evaluateWithContext(node, node.initializer);
- constantEvaluator.env.addVariableValue(node, constant);
+ constantEvaluator.env.updateVariableValue(node, constant);
node.initializer = makeConstantExpression(constant, node.initializer)
..parent = node;
@@ -1644,14 +1644,14 @@
// TODO(johnniwinther): This should call [_evaluateSubexpression].
: _evaluateNullableSubexpression(parameter.initializer);
if (value is AbortConstant) return value;
- env.addVariableValue(parameter, value);
+ env.updateVariableValue(parameter, value);
}
for (final VariableDeclaration parameter in function.namedParameters) {
final Constant value = namedArguments[parameter.name] ??
// TODO(johnniwinther): This should call [_evaluateSubexpression].
_evaluateNullableSubexpression(parameter.initializer);
if (value is AbortConstant) return value;
- env.addVariableValue(parameter, value);
+ env.updateVariableValue(parameter, value);
}
// Step 2) Run all initializers (including super calls) with environment
@@ -1672,7 +1672,7 @@
final VariableDeclaration variable = init.variable;
Constant constant = _evaluateSubexpression(variable.initializer);
if (constant is AbortConstant) return constant;
- env.addVariableValue(variable, constant);
+ env.updateVariableValue(variable, constant);
} else if (init is SuperInitializer) {
AbortConstant error = checkConstructorConst(init, constructor);
if (error != null) return error;
@@ -2374,7 +2374,7 @@
Constant visitLet(Let node) {
Constant value = _evaluateSubexpression(node.variable.initializer);
if (value is AbortConstant) return value;
- env.addVariableValue(node.variable, value);
+ env.updateVariableValue(node.variable, value);
return _evaluateSubexpression(node.body);
}
@@ -2729,14 +2729,14 @@
// TODO(johnniwinther): This should call [_evaluateSubexpression].
: _evaluateNullableSubexpression(parameter.initializer);
if (value is AbortConstant) return value;
- env.addVariableValue(parameter, value);
+ env.updateVariableValue(parameter, value);
}
for (final VariableDeclaration parameter in function.namedParameters) {
final Constant value = namedArguments[parameter.name] ??
// TODO(johnniwinther): This should call [_evaluateSubexpression].
_evaluateNullableSubexpression(parameter.initializer);
if (value is AbortConstant) return value;
- env.addVariableValue(parameter, value);
+ env.updateVariableValue(parameter, value);
}
Statement body = function.body;
if (body is ReturnStatement) {
@@ -3269,9 +3269,9 @@
final Map<TypeParameter, DartType> _typeVariables =
<TypeParameter, DartType>{};
- /// The values of the parameters/variables in scope.
- final Map<VariableDeclaration, Constant> _variables =
- <VariableDeclaration, Constant>{};
+ /// The references to values of the parameters/variables in scope.
+ final Map<VariableDeclaration, EvaluationReference> _variables =
+ <VariableDeclaration, EvaluationReference>{};
/// The variables that hold unevaluated constants.
///
@@ -3288,15 +3288,19 @@
_typeVariables[parameter] = value;
}
- void addVariableValue(VariableDeclaration variable, Constant value) {
- _variables[variable] = value;
+ void updateVariableValue(VariableDeclaration variable, Constant value) {
+ if (!_variables.containsKey(variable)) {
+ _variables[variable] = new EvaluationReference(value);
+ } else {
+ _variables[variable].value = value;
+ }
if (value is UnevaluatedConstant) {
_unreadUnevaluatedVariables.add(variable);
}
}
Constant lookupVariable(VariableDeclaration variable) {
- Constant value = _variables[variable];
+ Constant value = _variables[variable]?.value;
if (value is UnevaluatedConstant) {
_unreadUnevaluatedVariables.remove(variable);
}
@@ -3307,7 +3311,7 @@
Iterable<UnevaluatedConstant> get unevaluatedUnreadConstants {
if (_unreadUnevaluatedVariables.isEmpty) return const [];
return _unreadUnevaluatedVariables.map<UnevaluatedConstant>(
- (VariableDeclaration variable) => _variables[variable]);
+ (VariableDeclaration variable) => _variables[variable].value);
}
DartType substituteType(DartType type) {
@@ -3333,6 +3337,70 @@
}
}
+/// Location that stores a value in the [ConstantEvaluator].
+class EvaluationReference {
+ Constant value;
+
+ EvaluationReference(this.value);
+}
+
+/// An intermediate result that is used within the [ConstantEvaluator].
+class IntermediateValue implements Constant {
+ dynamic value;
+
+ IntermediateValue(this.value);
+
+ @override
+ R accept<R>(ConstantVisitor<R> v) {
+ throw new UnimplementedError();
+ }
+
+ @override
+ R acceptReference<R>(Visitor<R> v) {
+ throw new UnimplementedError();
+ }
+
+ @override
+ Expression asExpression() {
+ throw new UnimplementedError();
+ }
+
+ @override
+ DartType getType(StaticTypeContext context) {
+ throw new UnimplementedError();
+ }
+
+ @override
+ String leakingDebugToString() {
+ throw new UnimplementedError();
+ }
+
+ @override
+ String toString() {
+ throw new UnimplementedError();
+ }
+
+ @override
+ String toStringInternal() {
+ throw new UnimplementedError();
+ }
+
+ @override
+ String toText(AstTextStrategy strategy) {
+ throw new UnimplementedError();
+ }
+
+ @override
+ void toTextInternal(AstPrinter printer) {
+ throw new UnimplementedError();
+ }
+
+ @override
+ void visitChildren(Visitor<dynamic> v) {
+ throw new UnimplementedError();
+ }
+}
+
abstract class AbortConstant implements Constant {}
class _AbortDueToErrorConstant extends AbortConstant {
diff --git a/pkg/front_end/testcases/general/constants/const_collections.dart.weak.expect b/pkg/front_end/testcases/general/constants/const_collections.dart.weak.expect
index 58d0c68..8f4d7e0 100644
--- a/pkg/front_end/testcases/general/constants/const_collections.dart.weak.expect
+++ b/pkg/front_end/testcases/general/constants/const_collections.dart.weak.expect
@@ -15,10 +15,6 @@
// const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
// ^
//
-// pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Expected ',' before this.
-// const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
-// ^
-//
// pkg/front_end/testcases/general/constants/const_collections.dart:42:38: Error: Both Iterable and Map spread elements encountered in ambiguous literal.
// const Set<String> quxWithMapSpread = {...baz, ...quux};
// ^
@@ -427,7 +423,7 @@
static const field core::Set<core::String*>* baz = #C14;
static const field core::Set<core::String*>* qux = #C17;
static const field core::Set<core::String*>* quxWithNullSpread = invalid-expression "Null value during constant evaluation.";
-static const field core::Set<core::String*>* quxWithIntSpread = invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Expected ',' before this.
+static const field core::Set<core::String*>* quxWithIntSpread = invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Unexpected type 'int' of a map spread entry. Expected 'dynamic' or a Map.
const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
^";
static const field core::Set<core::String*>* quxWithMapSpread = invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:42:38: Error: Both Iterable and Map spread elements encountered in ambiguous literal.
diff --git a/pkg/front_end/testcases/general/constants/const_collections.dart.weak.outline.expect b/pkg/front_end/testcases/general/constants/const_collections.dart.weak.outline.expect
index 693f060..8ae1da7 100644
--- a/pkg/front_end/testcases/general/constants/const_collections.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/general/constants/const_collections.dart.weak.outline.expect
@@ -15,10 +15,6 @@
// const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
// ^
//
-// pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Expected ',' before this.
-// const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
-// ^
-//
// pkg/front_end/testcases/general/constants/const_collections.dart:42:38: Error: Both Iterable and Map spread elements encountered in ambiguous literal.
// const Set<String> quxWithMapSpread = {...baz, ...quux};
// ^
@@ -249,7 +245,7 @@
static const field core::Set<core::String*>* baz = const <core::String*>{"hello", "world"};
static const field core::Set<core::String*>* qux = self::baz + const <core::String*>{"!"};
static const field core::Set<core::String*>* quxWithNullSpread = self::baz + self::nullSet;
-static const field core::Set<core::String*>* quxWithIntSpread = self::baz + const <core::String*>{invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Expected ',' before this.
+static const field core::Set<core::String*>* quxWithIntSpread = self::baz + const <core::String*>{invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Unexpected type 'int' of a map spread entry. Expected 'dynamic' or a Map.
const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
^"};
static const field core::Set<core::String*>* quxWithMapSpread = invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:42:38: Error: Both Iterable and Map spread elements encountered in ambiguous literal.
diff --git a/pkg/front_end/testcases/general/constants/const_collections.dart.weak.transformed.expect b/pkg/front_end/testcases/general/constants/const_collections.dart.weak.transformed.expect
index f26aca0..4f6649c 100644
--- a/pkg/front_end/testcases/general/constants/const_collections.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/general/constants/const_collections.dart.weak.transformed.expect
@@ -15,10 +15,6 @@
// const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
// ^
//
-// pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Expected ',' before this.
-// const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
-// ^
-//
// pkg/front_end/testcases/general/constants/const_collections.dart:42:38: Error: Both Iterable and Map spread elements encountered in ambiguous literal.
// const Set<String> quxWithMapSpread = {...baz, ...quux};
// ^
@@ -427,7 +423,7 @@
static const field core::Set<core::String*>* baz = #C14;
static const field core::Set<core::String*>* qux = #C17;
static const field core::Set<core::String*>* quxWithNullSpread = invalid-expression "Null value during constant evaluation.";
-static const field core::Set<core::String*>* quxWithIntSpread = invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Expected ',' before this.
+static const field core::Set<core::String*>* quxWithIntSpread = invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:41:50: Error: Unexpected type 'int' of a map spread entry. Expected 'dynamic' or a Map.
const Set<String> quxWithIntSpread = {...baz, ...fortyTwo};
^";
static const field core::Set<core::String*>* quxWithMapSpread = invalid-expression "pkg/front_end/testcases/general/constants/const_collections.dart:42:38: Error: Both Iterable and Map spread elements encountered in ambiguous literal.
diff --git a/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.expect b/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.expect
index 2a0efc2..ce15d14 100644
--- a/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.expect
+++ b/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.expect
@@ -88,10 +88,6 @@
// ...null,
// ^
//
-// pkg/front_end/testcases/general/spread_collection_inference.dart:137:8: Error: Expected ',' before this.
-// ...null,
-// ^
-//
// pkg/front_end/testcases/general/spread_collection_inference.dart:142:45: Error: Can't spread a value with static type 'Null'.
// Map<String, int> map70 = <String, int>{...null};
// ^
@@ -298,7 +294,7 @@
} =>#t51;
core::Set<dynamic>* set71ambiguous = block {
final core::Set<dynamic>* #t52 = col::LinkedHashSet::•<dynamic>();
- #t52.{core::Set::add}{Invariant}(invalid-expression "pkg/front_end/testcases/general/spread_collection_inference.dart:137:8: Error: Expected ',' before this.
+ #t52.{core::Set::add}{Invariant}(invalid-expression "pkg/front_end/testcases/general/spread_collection_inference.dart:137:8: Error: Can't spread a value with static type 'Null'.
...null,
^");
for (final dynamic #t53 in <dynamic>[]) {
diff --git a/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.transformed.expect b/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.transformed.expect
index 9304728..ceca37a 100644
--- a/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/general/spread_collection_inference.dart.weak.transformed.expect
@@ -88,10 +88,6 @@
// ...null,
// ^
//
-// pkg/front_end/testcases/general/spread_collection_inference.dart:137:8: Error: Expected ',' before this.
-// ...null,
-// ^
-//
// pkg/front_end/testcases/general/spread_collection_inference.dart:142:45: Error: Can't spread a value with static type 'Null'.
// Map<String, int> map70 = <String, int>{...null};
// ^
@@ -367,7 +363,7 @@
} =>#t51;
core::Set<dynamic>* set71ambiguous = block {
final core::Set<dynamic>* #t52 = new col::_CompactLinkedHashSet::•<dynamic>();
- #t52.{core::Set::add}{Invariant}(invalid-expression "pkg/front_end/testcases/general/spread_collection_inference.dart:137:8: Error: Expected ',' before this.
+ #t52.{core::Set::add}{Invariant}(invalid-expression "pkg/front_end/testcases/general/spread_collection_inference.dart:137:8: Error: Can't spread a value with static type 'Null'.
...null,
^");
{
diff --git a/tests/language/spread_collections/issue_45174_error_test.dart b/tests/language/spread_collections/issue_45174_error_test.dart
new file mode 100644
index 0000000..377498c
--- /dev/null
+++ b/tests/language/spread_collections/issue_45174_error_test.dart
@@ -0,0 +1,15 @@
+// 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.
+
+f(Object? objectQuestion) {
+ return {...<int>{}, ...objectQuestion};
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ // [analyzer] COMPILE_TIME_ERROR.AMBIGUOUS_SET_OR_MAP_LITERAL_EITHER
+ // ^^^^^^^^^^^^^^
+ // [analyzer] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
+ // ^
+ // [cfe] Unexpected type 'Object?' of a map spread entry. Expected 'dynamic' or a Map.
+}
+
+main() {}
diff --git a/tests/language_2/spread_collections/issue_45174_error_test.dart b/tests/language_2/spread_collections/issue_45174_error_test.dart
new file mode 100644
index 0000000..d9a24ce
--- /dev/null
+++ b/tests/language_2/spread_collections/issue_45174_error_test.dart
@@ -0,0 +1,13 @@
+// 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.
+
+f(Object objectQuestion) {
+ return {...<int>{}, ...objectQuestion};
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ // [analyzer] COMPILE_TIME_ERROR.AMBIGUOUS_SET_OR_MAP_LITERAL_EITHER
+ // ^
+ // [cfe] Unexpected type 'Object' of a map spread entry. Expected 'dynamic' or a Map.
+}
+
+main() {}
diff --git a/tools/VERSION b/tools/VERSION
index 2d6bb5e..3a05acd 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 103
+PRERELEASE 104
PRERELEASE_PATCH 0
\ No newline at end of file