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