Version 2.10.0-136.0.dev

Merge commit '0eec9ee53c4306abea1733b3b5cc2bfb57cbe380' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/bug42653.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/bug42653.dart
new file mode 100644
index 0000000..45304e0
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/bug42653.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+void forEachStatement(Object x) {
+  if (x is int) {
+    /*int*/ x;
+    for (x in [0]) {
+      /*int*/ x;
+    }
+  }
+}
+
+forEachElementInListLiteral(Object x) {
+  if (x is int) {
+    /*int*/ x;
+    return [
+      for (x in [0]) /*int*/ x
+    ];
+  }
+}
+
+forEachElementInMapLiteral(Object x) {
+  if (x is int) {
+    /*int*/ x;
+    return {
+      for (x in [0]) /*int*/ x: /*int*/ x
+    };
+  }
+}
+
+forEachElementInSetLiteral(Object x) {
+  if (x is int) {
+    /*int*/ x;
+    return {
+      for (x in [0]) /*int*/ x
+    };
+  }
+}
diff --git a/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart b/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart
index 1a6a820..7feaa01 100644
--- a/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart
@@ -4938,8 +4938,8 @@
           reporter.reportError('must not be null');
           return false;
         }
-        if (!(obj['kind'] is String)) {
-          reporter.reportError('must be of type String');
+        if (!(obj['kind'] == 'create')) {
+          reporter.reportError('must be the literal \'create\'');
           return false;
         }
       } finally {
@@ -5963,8 +5963,8 @@
           reporter.reportError('must not be null');
           return false;
         }
-        if (!(obj['kind'] is String)) {
-          reporter.reportError('must be of type String');
+        if (!(obj['kind'] == 'delete')) {
+          reporter.reportError('must be the literal \'delete\'');
           return false;
         }
       } finally {
@@ -13198,7 +13198,10 @@
     final capabilities = json['capabilities'] != null
         ? ClientCapabilities.fromJson(json['capabilities'])
         : null;
-    final trace = json['trace'];
+    final trace = const {null, 'off', 'messages', 'verbose'}
+            .contains(json['trace'])
+        ? json['trace']
+        : throw '''${json['trace']} was not one of (null, 'off', 'messages', 'verbose')''';
     final workspaceFolders = json['workspaceFolders']
         ?.map((item) => item != null ? WorkspaceFolder.fromJson(item) : null)
         ?.cast<WorkspaceFolder>()
@@ -13363,8 +13366,12 @@
       }
       reporter.push('trace');
       try {
-        if (obj['trace'] != null && !(obj['trace'] is String)) {
-          reporter.reportError('must be of type String');
+        if (obj['trace'] != null &&
+            !((obj['trace'] == 'off' ||
+                obj['trace'] == 'messages' ||
+                obj['trace'] == 'verbose'))) {
+          reporter.reportError(
+              'must be one of the literals \'off\', \'messages\', \'verbose\'');
           return false;
         }
       } finally {
@@ -16647,8 +16654,8 @@
           reporter.reportError('must not be null');
           return false;
         }
-        if (!(obj['kind'] is String)) {
-          reporter.reportError('must be of type String');
+        if (!(obj['kind'] == 'rename')) {
+          reporter.reportError('must be the literal \'rename\'');
           return false;
         }
       } finally {
@@ -23813,8 +23820,8 @@
           reporter.reportError('must not be null');
           return false;
         }
-        if (!(obj['kind'] is String)) {
-          reporter.reportError('must be of type String');
+        if (!(obj['kind'] == 'begin')) {
+          reporter.reportError('must be the literal \'begin\'');
           return false;
         }
       } finally {
@@ -24090,8 +24097,8 @@
           reporter.reportError('must not be null');
           return false;
         }
-        if (!(obj['kind'] is String)) {
-          reporter.reportError('must be of type String');
+        if (!(obj['kind'] == 'end')) {
+          reporter.reportError('must be the literal \'end\'');
           return false;
         }
       } finally {
@@ -24464,8 +24471,8 @@
           reporter.reportError('must not be null');
           return false;
         }
-        if (!(obj['kind'] is String)) {
-          reporter.reportError('must be of type String');
+        if (!(obj['kind'] == 'report')) {
+          reporter.reportError('must be the literal \'report\'');
           return false;
         }
       } finally {
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/make_variable_nullable.dart b/pkg/analysis_server/lib/src/services/correction/dart/make_variable_nullable.dart
new file mode 100644
index 0000000..5a4d2ec
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/make_variable_nullable.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
+import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class MakeVariableNullable extends CorrectionProducer {
+  /// The name of the variable whose type is to be made nullable.
+  String _variableName;
+
+  @override
+  List<Object> get fixArguments => [_variableName];
+
+  @override
+  FixKind get fixKind => DartFixKind.MAKE_VARIABLE_NULLABLE;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    var node = coveredNode;
+    var parent = node?.parent;
+    if (unit.featureSet.isEnabled(Feature.non_nullable) &&
+        parent is AssignmentExpression &&
+        parent.rightHandSide == node) {
+      var leftHandSide = parent.leftHandSide;
+      if (leftHandSide is SimpleIdentifier) {
+        var element = leftHandSide.staticElement;
+        if (element is LocalVariableElement) {
+          var oldType = element.type;
+          var newType = (node as Expression).staticType;
+          if (node is NullLiteral) {
+            newType = (oldType as InterfaceTypeImpl)
+                .withNullability(NullabilitySuffix.question);
+          } else if (!typeSystem.isAssignableTo(
+              oldType, typeSystem.promoteToNonNull(newType))) {
+            return;
+          }
+          var declarationList =
+              _findDeclaration(element, parent.thisOrAncestorOfType<Block>());
+          if (declarationList == null || declarationList.variables.length > 1) {
+            return;
+          }
+          var variable = declarationList.variables[0];
+          _variableName = variable.name.name;
+          await builder.addDartFileEdit(file, (builder) {
+            var keyword = declarationList.keyword;
+            if (keyword != null && keyword.type == Keyword.VAR) {
+              builder.addReplacement(range.token(keyword), (builder) {
+                builder.writeType(newType);
+              });
+            } else if (keyword == null) {
+              if (declarationList.type == null) {
+                builder.addInsertion(variable.offset, (builder) {
+                  builder.writeType(newType);
+                  builder.write(' ');
+                });
+              } else {
+                builder.addSimpleInsertion(declarationList.type.end, '?');
+              }
+            }
+          });
+        }
+      }
+    }
+  }
+
+  /// Return the list of variable declarations containing the declaration of the
+  /// given [variable] that is located in the given [block] or in a surrounding
+  /// block. Return `null` if the declaration can't be found.
+  VariableDeclarationList _findDeclaration(
+      LocalVariableElement variable, Block block) {
+    if (variable == null) {
+      return null;
+    }
+    var currentBlock = block;
+    while (currentBlock != null) {
+      for (var statement in block.statements) {
+        if (statement is VariableDeclarationStatement) {
+          var variableList = statement.variables;
+          if (variableList != null) {
+            var variables = variableList.variables;
+            for (var declaration in variables) {
+              if (declaration.declaredElement == variable) {
+                return variableList;
+              }
+            }
+          }
+        }
+      }
+      currentBlock = currentBlock.parent.thisOrAncestorOfType<Block>();
+    }
+    return null;
+  }
+
+  /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+  static MakeVariableNullable newInstance() => MakeVariableNullable();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 0f67130..7dd5825 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -323,6 +323,8 @@
       'Move type arguments to after class name');
   static const MAKE_VARIABLE_NOT_FINAL = FixKind(
       'dart.fix.makeVariableNotFinal', 50, "Make variable '{0}' not final");
+  static const MAKE_VARIABLE_NULLABLE =
+      FixKind('dart.fix.makeVariableNullable', 50, "Make '{0}' nullable");
   static const ORGANIZE_IMPORTS =
       FixKind('dart.fix.organize.imports', 50, 'Organize Imports');
   static const QUALIFY_REFERENCE =
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 adfaa6e..86ed829 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -80,6 +80,7 @@
 import 'package:analysis_server/src/services/correction/dart/make_final.dart';
 import 'package:analysis_server/src/services/correction/dart/make_return_type_nullable.dart';
 import 'package:analysis_server/src/services/correction/dart/make_variable_not_final.dart';
+import 'package:analysis_server/src/services/correction/dart/make_variable_nullable.dart';
 import 'package:analysis_server/src/services/correction/dart/move_type_arguments_to_class.dart';
 import 'package:analysis_server/src/services/correction/dart/organize_imports.dart';
 import 'package:analysis_server/src/services/correction/dart/qualify_reference.dart';
@@ -716,6 +717,7 @@
       AddExplicitCast.newInstance,
       AddNullCheck.newInstance,
       ChangeTypeAnnotation.newInstance,
+      MakeVariableNullable.newInstance,
     ],
     CompileTimeErrorCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION: [
       RemoveParenthesesInGetterInvocation.newInstance,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/make_variable_nullable_test.dart b/pkg/analysis_server/test/src/services/correction/fix/make_variable_nullable_test.dart
new file mode 100644
index 0000000..133daf5
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/make_variable_nullable_test.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(MakeVariableNullableTest);
+  });
+}
+
+@reflectiveTest
+class MakeVariableNullableTest extends FixProcessorTest {
+  @override
+  List<String> get experiments => [EnableString.non_nullable];
+
+  @override
+  FixKind get kind => DartFixKind.MAKE_VARIABLE_NULLABLE;
+
+  Future<void> test_lhsNotIdentifier() async {
+    await resolveTestUnit('''
+void f(C c) {
+  c.s = null;
+}
+class C {
+  String s = '';
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_lhsNotLocalVariable() async {
+    await resolveTestUnit('''
+var s = '';
+void f() {
+  s = null;
+  print(s);
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_multipleVariables() async {
+    await resolveTestUnit('''
+void f() {
+  var s = '', t = '';
+  s = null;
+  print(s);
+  print(t);
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_noKeywordOrType() async {
+    await resolveTestUnit('''
+void f() {
+  late s = '';
+  s = null;
+  print(s);
+}
+''');
+    await assertHasFix('''
+void f() {
+  late String? s = '';
+  s = null;
+  print(s);
+}
+''');
+  }
+
+  Future<void> test_type() async {
+    await resolveTestUnit('''
+void f() {
+  String s = '';
+  s = null;
+  print(s);
+}
+''');
+    await assertHasFix('''
+void f() {
+  String? s = '';
+  s = null;
+  print(s);
+}
+''');
+  }
+
+  Future<void> test_var() async {
+    await resolveTestUnit('''
+void f() {
+  var s = '';
+  s = null;
+  print(s);
+}
+''');
+    await assertHasFix('''
+void f() {
+  String? s = '';
+  s = null;
+  print(s);
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index 64dde02..7fcd197 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -94,6 +94,7 @@
 import 'make_final_test.dart' as make_final;
 import 'make_return_type_nullable_test.dart' as make_return_type_nullable;
 import 'make_variable_not_final_test.dart' as make_variable_not_final;
+import 'make_variable_nullable_test.dart' as make_variable_nullable;
 import 'move_type_arguments_to_class_test.dart' as move_type_arguments_to_class;
 import 'organize_imports_test.dart' as organize_imports;
 import 'qualify_reference_test.dart' as qualify_reference;
@@ -249,6 +250,7 @@
     make_final.main();
     make_return_type_nullable.main();
     make_variable_not_final.main();
+    make_variable_nullable.main();
     move_type_arguments_to_class.main();
     organize_imports.main();
     qualify_reference.main();
diff --git a/pkg/analysis_server/test/tool/lsp_spec/json_test.dart b/pkg/analysis_server/test/tool/lsp_spec/json_test.dart
index da31eff..1334077 100644
--- a/pkg/analysis_server/test/tool/lsp_spec/json_test.dart
+++ b/pkg/analysis_server/test/tool/lsp_spec/json_test.dart
@@ -142,6 +142,50 @@
       expect(canParse, isTrue);
     });
 
+    test('canParse allows matching literal strings', () {
+      // The CreateFile type is defined with `{ kind: 'create' }` so the only
+      // allowed value for `kind` is "create".
+      final canParse = CreateFile.canParse({
+        'kind': 'create',
+        'uri': 'file:///temp/foo',
+      }, nullLspJsonReporter);
+      expect(canParse, isTrue);
+    });
+
+    test('canParse disallows non-matching literal strings', () {
+      // The CreateFile type is defined with `{ kind: 'create' }` so the only
+      // allowed value for `kind` is "create".
+      final canParse = CreateFile.canParse({
+        'kind': 'not-create',
+        'uri': 'file:///temp/foo',
+      }, nullLspJsonReporter);
+      expect(canParse, isFalse);
+    });
+
+    test('canParse handles unions of literals', () {
+      // Key = value to test
+      // Value whether expected to parse
+      const testTraceValues = {
+        'off': true,
+        'messages': true,
+        'verbose': true,
+        null: true,
+        'invalid': false,
+      };
+      for (final testValue in testTraceValues.keys) {
+        final expected = testTraceValues[testValue];
+        final canParse = InitializeParams.canParse({
+          'processId': null,
+          'rootUri': null,
+          'capabilities': <String, Object>{},
+          'trace': testValue,
+        }, nullLspJsonReporter);
+        expect(canParse, expected,
+            reason: 'InitializeParams.canParse returned $canParse with a '
+                '"trace" value of "$testValue" but expected $expected');
+      }
+    });
+
     test('canParse validates optional fields', () {
       expect(
         RenameFileOptions.canParse(<String, Object>{}, nullLspJsonReporter),
@@ -336,7 +380,7 @@
           InitializeParamsClientInfo(name: 'server name', version: '1.2.3'),
       rootPath: '!root',
       capabilities: ClientCapabilities(),
-      trace: '!trace',
+      trace: 'off',
       workspaceFolders: [
         WorkspaceFolder(uri: '!uri1', name: '!name1'),
         WorkspaceFolder(uri: '!uri2', name: '!name2'),
diff --git a/pkg/analysis_server/test/tool/lsp_spec/matchers.dart b/pkg/analysis_server/test/tool/lsp_spec/matchers.dart
index 9744f04..e5ed507 100644
--- a/pkg/analysis_server/test/tool/lsp_spec/matchers.dart
+++ b/pkg/analysis_server/test/tool/lsp_spec/matchers.dart
@@ -9,6 +9,9 @@
 
 Matcher isArrayOf(Matcher matcher) => ArrayTypeMatcher(wrapMatcher(matcher));
 
+Matcher isLiteralOf(Matcher typeMatcher, Object value) =>
+    LiteralTypeMatcher(typeMatcher, value);
+
 Matcher isMapOf(Matcher indexMatcher, Matcher valueMatcher) =>
     MapTypeMatcher(wrapMatcher(indexMatcher), wrapMatcher(valueMatcher));
 
@@ -43,6 +46,25 @@
   }
 }
 
+class LiteralTypeMatcher extends Matcher {
+  final Matcher _typeMatcher;
+  final String _value;
+  LiteralTypeMatcher(this._typeMatcher, this._value);
+
+  @override
+  Description describe(Description description) => description
+      .add('a literal where type is ')
+      .addDescriptionOf(_typeMatcher)
+      .add(' and value is $_value');
+
+  @override
+  bool matches(item, Map matchState) {
+    return item is LiteralType &&
+        _typeMatcher.matches(item.type, matchState) &&
+        item.literal == _value;
+  }
+}
+
 class MapTypeMatcher extends Matcher {
   final Matcher _indexMatcher, _valueMatcher;
   const MapTypeMatcher(this._indexMatcher, this._valueMatcher);
diff --git a/pkg/analysis_server/test/tool/lsp_spec/typescript_test.dart b/pkg/analysis_server/test/tool/lsp_spec/typescript_test.dart
index 843ac88..b439e68 100644
--- a/pkg/analysis_server/test/tool/lsp_spec/typescript_test.dart
+++ b/pkg/analysis_server/test/tool/lsp_spec/typescript_test.dart
@@ -385,5 +385,49 @@
       expect(output[0].commentNode.token.lexeme, equals('''// This is line 1
 // This is line 2'''));
     });
+
+    test('parses literal string values', () {
+      final input = '''
+export interface MyType {
+	kind: 'one';
+}
+    ''';
+      final output = parseString(input);
+      expect(output, hasLength(1));
+      expect(output[0], const TypeMatcher<Interface>());
+      final Interface interface = output[0];
+      expect(interface.name, equals('MyType'));
+      expect(interface.members, hasLength(1));
+      expect(interface.members[0], const TypeMatcher<Field>());
+      final Field field = interface.members[0];
+      expect(field.name, equals('kind'));
+      expect(field.allowsNull, isFalse);
+      expect(field.allowsUndefined, isFalse);
+      expect(field.type, isLiteralOf(isSimpleType('string'), "'one'"));
+    });
+
+    test('parses literal union values', () {
+      final input = '''
+export interface MyType {
+	kind: 'one' | 'two';
+}
+    ''';
+      final output = parseString(input);
+      expect(output, hasLength(1));
+      expect(output[0], const TypeMatcher<Interface>());
+      final Interface interface = output[0];
+      expect(interface.name, equals('MyType'));
+      expect(interface.members, hasLength(1));
+      expect(interface.members[0], const TypeMatcher<Field>());
+      final Field field = interface.members[0];
+      expect(field.name, equals('kind'));
+      expect(field.allowsNull, isFalse);
+      expect(field.allowsUndefined, isFalse);
+      expect(field.type, const TypeMatcher<LiteralUnionType>());
+      LiteralUnionType union = field.type;
+      expect(union.types, hasLength(2));
+      expect(union.types[0], isLiteralOf(isSimpleType('string'), "'one'"));
+      expect(union.types[1], isLiteralOf(isSimpleType('string'), "'two'"));
+    });
   });
 }
diff --git a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
index cefcce6..e25450b 100644
--- a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
+++ b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
@@ -120,6 +120,16 @@
   return sortedList;
 }
 
+String _getTypeCheckFailureMessage(TypeBase type) {
+  if (type is LiteralType) {
+    return 'must be the literal ${type.literal}';
+  } else if (type is LiteralUnionType) {
+    return 'must be one of the literals ${type.literalTypes.map((t) => t.literal).join(', ')}';
+  } else {
+    return 'must be of type ${type.dartTypeWithTypeArgs}';
+  }
+}
+
 bool _isSimpleType(TypeBase type) {
   const literals = ['num', 'String', 'bool'];
   return type is Type && literals.contains(type.dartType);
@@ -224,7 +234,7 @@
       ..write(')) {')
       ..indent()
       ..writeIndentedln(
-          "reporter.reportError('must be of type ${field.type.dartTypeWithTypeArgs}');")
+          "reporter.reportError('${_getTypeCheckFailureMessage(field.type).replaceAll("'", "\\'")}');")
       ..writeIndentedln('return false;')
       ..outdent()
       ..writeIndentedln('}')
@@ -310,8 +320,13 @@
   final consts = namespace.members.cast<Const>().toList();
   final allowsAnyValue = enumClassAllowsAnyValue(namespace.name);
   final constructorName = allowsAnyValue ? '' : '._';
+  final firstValueType = consts.first.type;
+  // Enums can have constant values in their fields so if a field is a literal
+  // use its underlying type for type checking.
+  final requiredValueType =
+      firstValueType is LiteralType ? firstValueType.type : firstValueType;
   final typeOfValues =
-      resolveTypeAlias(consts.first.type, resolveEnumClasses: true);
+      resolveTypeAlias(requiredValueType, resolveEnumClasses: true);
 
   buffer
     ..writeln('class ${namespace.name} {')
@@ -449,6 +464,9 @@
     _writeFromJsonCode(buffer, type.valueType, 'value', allowsNull: allowsNull);
     buffer.write(
         '))?.cast<${type.indexType.dartTypeWithTypeArgs}, ${type.valueType.dartTypeWithTypeArgs}>()');
+  } else if (type is LiteralUnionType) {
+    _writeFromJsonCodeForLiteralUnion(buffer, type, valueCode,
+        allowsNull: allowsNull);
   } else if (type is UnionType) {
     _writeFromJsonCodeForUnion(buffer, type, valueCode, allowsNull: allowsNull);
   } else {
@@ -456,6 +474,18 @@
   }
 }
 
+void _writeFromJsonCodeForLiteralUnion(
+    IndentableStringBuffer buffer, LiteralUnionType union, String valueCode,
+    {bool allowsNull}) {
+  final allowedValues = [
+    if (allowsNull) null,
+    ...union.literalTypes.map((t) => t.literal)
+  ];
+  buffer.write(
+      "const {${allowedValues.join(', ')}}.contains($valueCode) ? $valueCode : "
+      "throw '''\${$valueCode} was not one of (${allowedValues.join(', ')})'''");
+}
+
 void _writeFromJsonCodeForUnion(
     IndentableStringBuffer buffer, UnionType union, String valueCode,
     {bool allowsNull}) {
@@ -720,6 +750,8 @@
     buffer.write('true');
   } else if (_isSimpleType(type)) {
     buffer.write('$valueCode is $fullDartType');
+  } else if (type is LiteralType) {
+    buffer.write('$valueCode == ${type.literal}');
   } else if (_isSpecType(type)) {
     buffer.write('$dartType.canParse($valueCode, $reporter)');
   } else if (type is ArrayType) {
diff --git a/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart b/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart
index 1f510d6..9e9d814 100644
--- a/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart
+++ b/pkg/analysis_server/tool/lsp_spec/typescript_parser.dart
@@ -29,6 +29,8 @@
 bool isAnyType(TypeBase t) =>
     t is Type && (t.name == 'any' || t.name == 'object');
 
+bool isLiteralType(TypeBase t) => t is LiteralType;
+
 bool isNullType(TypeBase t) => t is Type && t.name == 'null';
 
 bool isUndefinedType(TypeBase t) => t is Type && t.name == 'undefined';
@@ -85,6 +87,7 @@
   Token valueToken;
   Const(Comment comment, this.nameToken, this.type, this.valueToken)
       : super(comment);
+
   @override
   String get name => nameToken.lexeme;
 
@@ -172,6 +175,36 @@
       : '';
 }
 
+class LiteralType extends TypeBase {
+  final Type type;
+  final String literal;
+
+  LiteralType(this.type, this.literal);
+
+  @override
+  String get dartType => type.dartType;
+
+  @override
+  String get typeArgsString => type.typeArgsString;
+
+  @override
+  String get uniqueTypeIdentifier => '$literal:${super.uniqueTypeIdentifier}';
+}
+
+/// A special class of Union types where the values are all literals of the same
+/// type so the Dart field can be the base type rather than an EitherX<>.
+class LiteralUnionType extends UnionType {
+  final List<LiteralType> literalTypes;
+
+  LiteralUnionType(this.literalTypes) : super(literalTypes);
+
+  @override
+  String get dartType => types.first.dartType;
+
+  @override
+  String get typeArgsString => types.first.typeArgsString;
+}
+
 class MapType extends TypeBase {
   final TypeBase indexType;
   final TypeBase valueType;
@@ -180,6 +213,7 @@
 
   @override
   String get dartType => 'Map';
+
   @override
   String get typeArgsString =>
       '<${indexType.dartTypeWithTypeArgs}, ${valueType.dartTypeWithTypeArgs}>';
@@ -364,9 +398,7 @@
       // simplify the unions.
       remainingTypes.removeWhere((t) => !allowTypeInSignatures(t));
 
-      type = remainingTypes.length > 1
-          ? UnionType(remainingTypes)
-          : remainingTypes.single;
+      type = _simplifyUnionTypes(remainingTypes);
     } else if (isAnyType(type)) {
       // There are values in the spec marked as `any` that allow nulls (for
       // example, the result field on ResponseMessage can be null for a
@@ -376,25 +408,6 @@
     return Field(leadingComment, name, type, canBeNull, canBeUndefined);
   }
 
-  /// Remove any duplicate types (for ex. if we map multiple types into dynamic)
-  /// we don't want to end up with `dynamic | dynamic`. Key on dartType to
-  /// ensure we different types that will map down to the same type.
-  List<TypeBase> _getUniqueTypes(List<TypeBase> types) {
-    final uniqueTypes = Map.fromEntries(
-      types.map((t) => MapEntry(t.dartTypeWithTypeArgs, t)),
-    ).values.toList();
-
-    // If our list includes something that maps to dynamic as well as other
-    // types, we should just treat the whole thing as dynamic as we get no value
-    // typing Either4<bool, String, num, dynamic> but it becomes much more
-    // difficult to use.
-    if (uniqueTypes.any(isAnyType)) {
-      return [uniqueTypes.firstWhere(isAnyType)];
-    }
-
-    return uniqueTypes;
-  }
-
   Indexer _indexer(String containerName, Comment leadingComment) {
     final indexer = _field(containerName, leadingComment);
     _consume(TokenType.RIGHT_BRACKET, 'Expected ]');
@@ -490,6 +503,29 @@
   /// Returns the next token without advancing.
   Token _peek() => _tokenAt(_current);
 
+  /// Remove any duplicate types (for ex. if we map multiple types into dynamic)
+  /// we don't want to end up with `dynamic | dynamic`. Key on dartType to
+  /// ensure we different types that will map down to the same type.
+  TypeBase _simplifyUnionTypes(List<TypeBase> types) {
+    final uniqueTypes = Map.fromEntries(
+      types.map((t) => MapEntry(t.uniqueTypeIdentifier, t)),
+    ).values.toList();
+
+    // If our list includes something that maps to dynamic as well as other
+    // types, we should just treat the whole thing as dynamic as we get no value
+    // typing Either4<bool, String, num, dynamic> but it becomes much more
+    // difficult to use.
+    if (uniqueTypes.any(isAnyType)) {
+      return uniqueTypes.firstWhere(isAnyType);
+    }
+
+    return uniqueTypes.length == 1
+        ? uniqueTypes.single
+        : uniqueTypes.every(isLiteralType)
+            ? LiteralUnionType(uniqueTypes.cast<LiteralType>())
+            : UnionType(uniqueTypes);
+  }
+
   Token _tokenAt(int index) =>
       index < _tokens.length ? _tokens[index] : Token.EOF;
 
@@ -562,17 +598,17 @@
         // Some types are in (parens), so we just parse the contents as a nested type.
         type = _type(containerName, fieldName);
         _consume(TokenType.RIGHT_PAREN, 'Expected )');
-      } else if (_match([TokenType.STRING])) {
+      } else if (_check(TokenType.STRING)) {
+        final token = _advance();
         // In TS and the spec, literal strings can be types:
         // export const PlainText: 'plaintext' = 'plaintext';
         // trace?: 'off' | 'messages' | 'verbose';
-        // the best we can do is use their base type (string).
-        type = Type.identifier('string');
-      } else if (_match([TokenType.NUMBER])) {
+        type = LiteralType(Type.identifier('string'), token.lexeme);
+      } else if (_check(TokenType.NUMBER)) {
+        final token = _advance();
         // In TS and the spec, literal numbers can be types:
         // export const Invoked: 1 = 1;
-        // the best we can do is use their base type (number).
-        type = Type.identifier('number');
+        type = LiteralType(Type.identifier('number'), token.lexeme);
       } else if (_match([TokenType.LEFT_BRACKET])) {
         // Tuples will just be converted to List/Array.
         final tupleElementTypes = <TypeBase>[];
@@ -583,10 +619,7 @@
         }
         _consume(TokenType.RIGHT_BRACKET, 'Expected ]');
 
-        final uniqueTypes = _getUniqueTypes(tupleElementTypes);
-        var tupleType = uniqueTypes.length == 1
-            ? uniqueTypes.single
-            : UnionType(uniqueTypes);
+        var tupleType = _simplifyUnionTypes(tupleElementTypes);
         type = ArrayType(tupleType);
       } else {
         var typeName = _consume(TokenType.IDENTIFIER, 'Expected identifier');
@@ -629,10 +662,7 @@
       }
     }
 
-    final uniqueTypes = _getUniqueTypes(types);
-
-    var type =
-        uniqueTypes.length == 1 ? uniqueTypes.single : UnionType(uniqueTypes);
+    var type = _simplifyUnionTypes(types);
 
     // Handle improved type mappings for things that aren't very tight in the spec.
     if (improveTypes) {
@@ -957,6 +987,10 @@
   String get dartType;
   String get dartTypeWithTypeArgs => '$dartType$typeArgsString';
   String get typeArgsString;
+
+  /// A unique identifier for this type. Used for folding types together
+  /// (for example two types that resolve to "dynamic" in Dart).
+  String get uniqueTypeIdentifier => dartTypeWithTypeArgs;
 }
 
 class UnionType extends TypeBase {
diff --git a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
index 3c2ce69..fc73415 100644
--- a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
@@ -59,6 +59,11 @@
     var left = node.leftHandSide;
     var right = node.rightHandSide;
 
+    if (left is PrefixedIdentifier) {
+      _resolve_PrefixedIdentifier(node, left);
+      return;
+    }
+
     if (left is PropertyAccess) {
       _resolve_PropertyAccess(node, left);
       return;
@@ -311,6 +316,37 @@
     }
   }
 
+  void _resolve_PrefixedIdentifier(
+    AssignmentExpressionImpl node,
+    PrefixedIdentifier left,
+  ) {
+    left.prefix?.accept(_resolver);
+
+    var propertyName = left.identifier;
+    var operator = node.operator.type;
+    var hasRead = operator != TokenType.EQ;
+
+    var resolver = PropertyElementResolver(_resolver);
+    var result = resolver.resolvePrefixedIdentifier(
+      node: left,
+      hasRead: hasRead,
+      hasWrite: true,
+    );
+
+    var readElement = result.readElement;
+    var writeElement = result.writeElement;
+
+    if (hasRead) {
+      _resolver.setReadElement(left, readElement);
+    }
+    _resolver.setWriteElement(left, writeElement);
+
+    _setBackwardCompatibility(node, propertyName);
+
+    var right = node.rightHandSide;
+    _resolve3(node, left, operator, right);
+  }
+
   void _resolve_PropertyAccess(
     AssignmentExpressionImpl node,
     PropertyAccess left,
@@ -323,9 +359,6 @@
 
     _resolver.startNullAwarePropertyAccess(left);
 
-    ExecutableElement readElement;
-    ExecutableElement writeElement;
-
     var resolver = PropertyElementResolver(_resolver);
     var result = resolver.resolvePropertyAccess(
       node: left,
@@ -333,14 +366,12 @@
       hasWrite: true,
     );
 
-    readElement = result.readElement;
-    writeElement = result.writeElement;
+    var readElement = result.readElement;
+    var writeElement = result.writeElement;
 
     if (hasRead) {
       _resolver.setReadElement(left, readElement);
     }
-
-    // var writeElement = result.setter ?? result.getter;
     _resolver.setWriteElement(left, writeElement);
 
     _setBackwardCompatibility(node, propertyName);
@@ -407,7 +438,9 @@
     }
 
     var parent = left.parent;
-    if (parent is PropertyAccess && parent.propertyName == left) {
+    if (parent is PrefixedIdentifier && parent.identifier == left) {
+      _recordStaticType(parent, node.writeType);
+    } else if (parent is PropertyAccess && parent.propertyName == left) {
       _recordStaticType(parent, node.writeType);
     }
   }
diff --git a/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart
index 454eeef..8946ab4 100644
--- a/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/for_resolver.dart
@@ -100,7 +100,7 @@
     if (identifier != null) {
       identifierElement = identifier.staticElement;
       if (identifierElement is VariableElement) {
-        valueType = identifierElement.type;
+        valueType = _resolver.localVariableTypeProvider.getType(identifier);
       } else if (identifierElement is PropertyAccessorElement) {
         var parameters = identifierElement.parameters;
         if (parameters.isNotEmpty) {
diff --git a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
index db73a0c..4d3fffb 100644
--- a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -32,11 +32,37 @@
     @required bool hasRead,
     @required bool hasWrite,
   }) {
+    var prefix = node.prefix;
+    var identifier = node.identifier;
+
+    var prefixElement = prefix.staticElement;
+    if (prefixElement is PrefixElement) {
+      var lookupResult = prefixElement.scope.lookup2(identifier.name);
+
+      var readElement = _resolver.toLegacyElement(lookupResult.getter);
+      var writeElement = _resolver.toLegacyElement(lookupResult.setter);
+
+      if (hasRead && readElement == null || hasWrite && writeElement == null) {
+        _errorReporter.reportErrorForNode(
+          CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME,
+          identifier,
+          [identifier.name, prefixElement.name],
+        );
+      }
+
+      return PropertyElementResolverResult(
+        readElementRequested: readElement,
+        readElementRecovery: null,
+        writeElementRequested: writeElement,
+        writeElementRecovery: null,
+      );
+    }
+
     return _resolve(
-      target: node.prefix,
+      target: prefix,
       isCascaded: false,
       isNullAware: false,
-      propertyName: node.identifier,
+      propertyName: identifier,
       hasRead: hasRead,
       hasWrite: hasWrite,
     );
@@ -469,10 +495,10 @@
 }
 
 class PropertyElementResolverResult {
-  final ExecutableElement readElementRequested;
-  final ExecutableElement readElementRecovery;
-  final ExecutableElement writeElementRequested;
-  final ExecutableElement writeElementRecovery;
+  final Element readElementRequested;
+  final Element readElementRecovery;
+  final Element writeElementRequested;
+  final Element writeElementRecovery;
 
   PropertyElementResolverResult({
     this.readElementRequested,
@@ -481,11 +507,11 @@
     this.writeElementRecovery,
   });
 
-  ExecutableElement get readElement {
+  Element get readElement {
     return readElementRequested ?? readElementRecovery;
   }
 
-  ExecutableElement get writeElement {
+  Element get writeElement {
     return writeElementRequested ?? writeElementRecovery;
   }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/ambiguous_import_test.dart b/pkg/analyzer/test/src/diagnostics/ambiguous_import_test.dart
index 61d593a..01e301c 100644
--- a/pkg/analyzer/test/src/diagnostics/ambiguous_import_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/ambiguous_import_test.dart
@@ -219,34 +219,99 @@
     ]);
   }
 
-  test_varRead() async {
-    newFile("$testPackageLibPath/lib1.dart", content: '''
-library lib1;
-var v;''');
-    newFile("$testPackageLibPath/lib2.dart", content: '''
-library lib2;
-var v;''');
+  test_variable_read() async {
+    newFile('$testPackageLibPath/a.dart', content: '''
+var x;
+''');
+
+    newFile('$testPackageLibPath/b.dart', content: '''
+var x;
+''');
+
     await assertErrorsInCode('''
-import 'lib1.dart';
-import 'lib2.dart';
-f() { g(v); }
-g(p) {}''', [
+import 'a.dart';
+import 'b.dart';
+
+void f() {
+  x;
+}
+''', [
       error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 48, 1),
     ]);
   }
 
-  test_varWrite() async {
-    newFile("$testPackageLibPath/lib1.dart", content: '''
-library lib1;
-var v;''');
-    newFile("$testPackageLibPath/lib2.dart", content: '''
-library lib2;
-var v;''');
+  test_variable_read_prefixed() async {
+    newFile('$testPackageLibPath/a.dart', content: '''
+var x;
+''');
+
+    newFile('$testPackageLibPath/b.dart', content: '''
+var x;
+''');
+
     await assertErrorsInCode('''
-import 'lib1.dart';
-import 'lib2.dart';
-f() { v = 0; }''', [
-      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 46, 1),
+import 'a.dart' as p;
+import 'b.dart' as p;
+
+void f() {
+  p.x;
+}
+''', [
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 60, 1),
+    ]);
+  }
+
+  test_variable_write() async {
+    newFile('$testPackageLibPath/a.dart', content: '''
+var x;
+''');
+
+    newFile('$testPackageLibPath/b.dart', content: '''
+var x;
+''');
+
+    await assertErrorsInCode('''
+import 'a.dart';
+import 'b.dart';
+
+void f() {
+  x = 0;
+  x += 1;
+  ++x;
+  x++;
+}
+''', [
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 48, 1),
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 57, 1),
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 69, 1),
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 74, 1),
+    ]);
+  }
+
+  test_variable_write_prefixed() async {
+    newFile('$testPackageLibPath/a.dart', content: '''
+var x;
+''');
+
+    newFile('$testPackageLibPath/b.dart', content: '''
+var x;
+''');
+
+    await assertErrorsInCode('''
+import 'a.dart' as p;
+import 'b.dart' as p;
+
+void f() {
+  p.x = 0;
+  p.x += 1;
+  ++p.x;
+  p.x++;
+}
+''', [
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 60, 1),
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 71, 1),
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 85, 1),
+      error(CompileTimeErrorCode.AMBIGUOUS_IMPORT, 92, 1),
     ]);
   }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart b/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart
index c5129ed..1ea665c 100644
--- a/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/receiver_of_type_never_test.dart
@@ -432,7 +432,7 @@
       findNode.simple('foo'),
       readElement: null,
       writeElement: null,
-      type: 'Never',
+      type: 'dynamic',
     );
 
     assertAssignment(
diff --git a/pkg/front_end/lib/src/base/processed_options.dart b/pkg/front_end/lib/src/base/processed_options.dart
index 5e51442..373eed1 100644
--- a/pkg/front_end/lib/src/base/processed_options.dart
+++ b/pkg/front_end/lib/src/base/processed_options.dart
@@ -13,7 +13,12 @@
 import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
 
 import 'package:kernel/kernel.dart'
-    show CanonicalName, Component, Location, Version;
+    show
+        CanonicalName,
+        Component,
+        Location,
+        NonNullableByDefaultCompiledMode,
+        Version;
 
 import 'package:kernel/target/targets.dart'
     show NoneTarget, Target, TargetFlags;
@@ -357,6 +362,18 @@
             _raw.experimentReleasedVersionForTesting);
   }
 
+  Component _validateNullSafetyMode(Component component) {
+    if (nnbdMode == NnbdMode.Strong &&
+        !(component.mode == NonNullableByDefaultCompiledMode.Strong ||
+            component.mode == NonNullableByDefaultCompiledMode.Agnostic)) {
+      throw new FormatException(
+          'Provided .dill file for the following libraries does not '
+          'support sound null safety:\n'
+          '${component.libraries.join('\n')}');
+    }
+    return component;
+  }
+
   /// Get an outline component that summarizes the SDK, if any.
   // TODO(sigmund): move, this doesn't feel like an "option".
   Future<Component> loadSdkSummary(CanonicalName nameRoot) async {
@@ -365,6 +382,7 @@
       List<int> bytes = await loadSdkSummaryBytes();
       if (bytes != null && bytes.isNotEmpty) {
         _sdkSummaryComponent = loadComponent(bytes, nameRoot);
+        _validateNullSafetyMode(_sdkSummaryComponent);
       }
     }
     return _sdkSummaryComponent;
@@ -374,6 +392,7 @@
     if (_sdkSummaryComponent != null) {
       throw new StateError("sdkSummary already loaded.");
     }
+    _validateNullSafetyMode(platform);
     _sdkSummaryComponent = platform;
   }
 
@@ -389,7 +408,8 @@
           uris.map((uri) => _readAsBytes(fileSystem.entityForUri(uri))));
       _additionalDillComponents = allBytes
           .where((bytes) => bytes != null)
-          .map((bytes) => loadComponent(bytes, nameRoot))
+          .map((bytes) =>
+              _validateNullSafetyMode(loadComponent(bytes, nameRoot)))
           .toList();
     }
     return _additionalDillComponents;
@@ -399,6 +419,7 @@
     if (_additionalDillComponents != null) {
       throw new StateError("inputAdditionalDillsComponents already loaded.");
     }
+    components.forEach(_validateNullSafetyMode);
     _additionalDillComponents = components;
   }
 
@@ -414,7 +435,7 @@
             disableLazyReading: false,
             alwaysCreateNewNamedNodes: alwaysCreateNewNamedNodes)
         .readComponent(component);
-    return component;
+    return _validateNullSafetyMode(component);
   }
 
   /// Get the [UriTranslator] which resolves "package:" and "dart:" URIs.
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index a956473..f823190 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -6061,8 +6061,14 @@
 
   LocalForInVariable(this.variableSet);
 
-  DartType computeElementType(TypeInferrerImpl inferrer) =>
-      variableSet.variable.type;
+  DartType computeElementType(TypeInferrerImpl inferrer) {
+    VariableDeclaration variable = variableSet.variable;
+    DartType promotedType;
+    if (inferrer.isNonNullableByDefault) {
+      promotedType = inferrer.flowAnalysis.promotedType(variable);
+    }
+    return promotedType ?? variable.type;
+  }
 
   Expression inferAssignment(TypeInferrerImpl inferrer, DartType rhsType) {
     Expression rhs = inferrer.ensureAssignable(
diff --git a/pkg/front_end/lib/src/kernel_generator_impl.dart b/pkg/front_end/lib/src/kernel_generator_impl.dart
index 5cef59a..76a3697 100644
--- a/pkg/front_end/lib/src/kernel_generator_impl.dart
+++ b/pkg/front_end/lib/src/kernel_generator_impl.dart
@@ -83,28 +83,11 @@
     // sdkSummary between multiple invocations.
     CanonicalName nameRoot = sdkSummary?.root ?? new CanonicalName.root();
     if (sdkSummary != null) {
-      if (options.nnbdMode == NnbdMode.Strong &&
-          !(sdkSummary.mode == NonNullableByDefaultCompiledMode.Strong ||
-              sdkSummary.mode == NonNullableByDefaultCompiledMode.Agnostic)) {
-        throw new FormatException(
-            'Provided SDK .dill does not support sound null safety.');
-      }
       dillTarget.loader.appendLibraries(sdkSummary);
     }
 
     for (Component additionalDill
         in await options.loadAdditionalDills(nameRoot)) {
-      if (options.nnbdMode == NnbdMode.Strong &&
-          !(additionalDill.mode == NonNullableByDefaultCompiledMode.Strong ||
-              // In some VM tests the SDK dill appears as an additionalDill so
-              // allow agnostic here as well.
-              additionalDill.mode ==
-                  NonNullableByDefaultCompiledMode.Agnostic)) {
-        throw new FormatException(
-            'Provided .dill file for the following libraries does not support '
-            'sound null safety:\n'
-            '${additionalDill.libraries.join('\n')}');
-      }
       loadedComponents.add(additionalDill);
       dillTarget.loader.appendLibraries(additionalDill);
     }
diff --git a/pkg/front_end/lib/widget_cache.dart b/pkg/front_end/lib/widget_cache.dart
index 9ccfa38..158125b 100644
--- a/pkg/front_end/lib/widget_cache.dart
+++ b/pkg/front_end/lib/widget_cache.dart
@@ -61,7 +61,7 @@
   /// `State` subtype.
   ///
   /// Returns the class name if located, otherwise `null`.
-  String checkWidgetCache(
+  String checkSingleWidgetTypeModified(
     Component lastGoodComponent,
     Component partialComponent,
     ClassHierarchy classHierarchy,
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index af48251..c95b0a2 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -20,6 +20,7 @@
 import 'package:front_end/src/api_prototype/compiler_options.dart'
     show CompilerOptions, parseExperimentalFlags;
 import 'package:front_end/src/api_unstable/vm.dart';
+import 'package:front_end/widget_cache.dart';
 import 'package:kernel/ast.dart' show Library, Procedure, LibraryDependency;
 import 'package:kernel/binary/ast_to_binary.dart';
 import 'package:kernel/kernel.dart'
@@ -176,7 +177,10 @@
       defaultsTo: false)
   ..addOption('dartdevc-module-format',
       help: 'The module format to use on for the dartdevc compiler',
-      defaultsTo: 'amd');
+      defaultsTo: 'amd')
+  ..addFlag('flutter-widget-cache',
+      help: 'Enable the widget cache to track changes to Widget subtypes',
+      defaultsTo: false);
 
 String usage = '''
 Usage: server [options] [input.dart]
@@ -345,6 +349,8 @@
   IncrementalCompiler _generator;
   JavaScriptBundler _bundler;
 
+  WidgetCache _widgetCache;
+
   String _kernelBinaryFilename;
   String _kernelBinaryFilenameIncremental;
   String _kernelBinaryFilenameFull;
@@ -530,6 +536,9 @@
           component.uriToSource.keys);
 
       incrementalSerializer = _generator.incrementalSerializer;
+      if (options['flutter-widget-cache']) {
+        _widgetCache = WidgetCache(component);
+      }
     } else {
       if (options['link-platform']) {
         // TODO(aam): Remove linkedDependencies once platform is directly embedded
@@ -904,6 +913,7 @@
       await writeDillFile(results, _kernelBinaryFilename,
           incrementalSerializer: _generator.incrementalSerializer);
     }
+    _updateWidgetCache(deltaProgram);
 
     _outputStream.writeln(boundaryKey);
     await _outputDependenciesDelta(results.compiledSources);
@@ -1095,6 +1105,7 @@
   @override
   void acceptLastDelta() {
     _generator.accept();
+    _widgetCache?.reset();
   }
 
   @override
@@ -1108,11 +1119,13 @@
   @override
   void invalidate(Uri uri) {
     _generator.invalidate(uri);
+    _widgetCache?.invalidate(uri);
   }
 
   @override
   void resetIncrementalCompiler() {
     _generator.resetDeltaState();
+    _widgetCache?.reset();
     _kernelBinaryFilename = _kernelBinaryFilenameFull;
   }
 
@@ -1122,6 +1135,29 @@
         incrementalSerialization: incrementalSerialization);
   }
 
+  /// If the flutter widget cache is enabled, check if a single class was modified.
+  ///
+  /// The resulting class name is written as a String to
+  /// `_kernelBinaryFilename`.widget_cache, or else the file is deleted
+  /// if it exists.
+  void _updateWidgetCache(Component partialComponent) {
+    if (_widgetCache == null) {
+      return;
+    }
+    final String singleModifiedClassName =
+        _widgetCache.checkSingleWidgetTypeModified(
+      _generator.lastKnownGoodComponent,
+      partialComponent,
+      _generator.getClassHierarchy(),
+    );
+    final File outputFile = File('$_kernelBinaryFilename.widget_cache');
+    if (singleModifiedClassName != null) {
+      outputFile.writeAsStringSync(singleModifiedClassName);
+    } else if (outputFile.existsSync()) {
+      outputFile.deleteSync();
+    }
+  }
+
   Uri _ensureFolderPath(String path) {
     String uriPath = Uri.file(path).toString();
     if (!uriPath.endsWith('/')) {
diff --git a/pkg/frontend_server/test/frontend_server_test.dart b/pkg/frontend_server/test/frontend_server_test.dart
index d4b17b7..90b4d5f 100644
--- a/pkg/frontend_server/test/frontend_server_test.dart
+++ b/pkg/frontend_server/test/frontend_server_test.dart
@@ -68,6 +68,24 @@
       expect(capturedArgs.single['sdk-root'], equals('sdkroot'));
       expect(capturedArgs.single['link-platform'], equals(true));
     });
+
+    test('compile from command line with widget cache', () async {
+      final List<String> args = <String>[
+        'server.dart',
+        '--sdk-root',
+        'sdkroot',
+        '--flutter-widget-cache',
+      ];
+      await starter(args, compiler: compiler);
+      final List<dynamic> capturedArgs = verify(compiler.compile(
+        argThat(equals('server.dart')),
+        captureAny,
+        generator: anyNamed('generator'),
+      )).captured;
+      expect(capturedArgs.single['sdk-root'], equals('sdkroot'));
+      expect(capturedArgs.single['link-platform'], equals(true));
+      expect(capturedArgs.single['flutter-widget-cache'], equals(true));
+    });
   });
 
   group('interactive compile with mocked compiler', () {
@@ -230,6 +248,34 @@
       inputStreamController.close();
     });
 
+    test('recompile one file with widget cache does not fail', () async {
+      // The component will not contain the flutter framework sources so
+      // this should no-op.
+      final StreamController<List<int>> inputStreamController =
+          StreamController<List<int>>();
+      final ReceivePort recompileCalled = ReceivePort();
+
+      when(compiler.recompileDelta(entryPoint: null))
+          .thenAnswer((Invocation invocation) async {
+        recompileCalled.sendPort.send(true);
+      });
+      Future<int> result = starter(
+        <String>[...args, '--flutter-widget-cache'],
+        compiler: compiler,
+        input: inputStreamController.stream,
+      );
+      inputStreamController.add('recompile abc\nfile1.dart\nabc\n'.codeUnits);
+      await recompileCalled.first;
+
+      verifyInOrder(<void>[
+        compiler.invalidate(Uri.base.resolve('file1.dart')),
+        await compiler.recompileDelta(entryPoint: null),
+      ]);
+      inputStreamController.add('quit\n'.codeUnits);
+      expect(await result, 0);
+      inputStreamController.close();
+    });
+
     test('recompile few files with new entrypoint', () async {
       final StreamController<List<int>> inputStreamController =
           StreamController<List<int>>();
@@ -923,6 +969,218 @@
       inputStreamController.close();
     });
 
+    test(
+        'recompile request with flutter widget cache outputs change in class name',
+        () async {
+      var frameworkDirectory = Directory('${tempDir.path}/flutter');
+      var flutterFramework =
+          File('${frameworkDirectory.path}/lib/src/widgets/framework.dart')
+            ..createSync(recursive: true);
+      flutterFramework.writeAsStringSync('''
+abstract class Widget {}
+class StatelessWidget extends Widget {}
+class StatefulWidget extends Widget {}
+class State<T extends StatefulWidget> {}
+''');
+
+      var file = File('${tempDir.path}/foo.dart')..createSync();
+      file.writeAsStringSync("""
+import "package:flutter/src/widgets/framework.dart";
+
+void main() {}
+
+class FooWidget extends StatelessWidget {}
+
+class FizzWidget extends StatefulWidget {}
+
+class BarState extends State<FizzWidget> {}
+""");
+      var config = File('${tempDir.path}/package_config.json')..createSync();
+      config.writeAsStringSync('''
+{
+  "configVersion": 2,
+  "packages": [
+    {
+      "name": "flutter",
+      "rootUri": "${frameworkDirectory.uri}",
+      "packageUri": "lib/",
+      "languageVersion": "2.2"
+    }
+  ]
+}
+''');
+
+      var dillFile = File('${tempDir.path}/app.dill');
+      expect(dillFile.existsSync(), equals(false));
+      final List<String> args = <String>[
+        '--sdk-root=${sdkRoot.toFilePath()}',
+        '--incremental',
+        '--platform=${platformKernel.path}',
+        '--output-dill=${dillFile.path}',
+        '--flutter-widget-cache',
+        '--packages=${config.path}',
+      ];
+
+      final StreamController<List<int>> inputStreamController =
+          StreamController<List<int>>();
+      final StreamController<List<int>> stdoutStreamController =
+          StreamController<List<int>>();
+      final IOSink ioSink = IOSink(stdoutStreamController.sink);
+      StreamController<Result> receivedResults = StreamController<Result>();
+
+      final outputParser = OutputParser(receivedResults);
+      stdoutStreamController.stream
+          .transform(utf8.decoder)
+          .transform(const LineSplitter())
+          .listen(outputParser.listener);
+
+      Future<int> result =
+          starter(args, input: inputStreamController.stream, output: ioSink);
+      inputStreamController.add('compile ${file.path}\n'.codeUnits);
+      int count = 0;
+      receivedResults.stream.listen((Result compiledResult) {
+        if (count == 0) {
+          // First request is to 'compile', which results in full kernel file.
+          expect(dillFile.existsSync(), equals(true));
+          compiledResult.expectNoErrors(filename: dillFile.path);
+          count += 1;
+          inputStreamController.add('accept\n'.codeUnits);
+          file.writeAsStringSync("""
+import "package:flutter/src/widgets/framework.dart";
+
+void main() {}
+
+class FooWidget extends StatelessWidget {
+  // Added.
+}
+
+class FizzWidget extends StatefulWidget {}
+
+class BarState extends State<FizzWidget> {}
+""");
+          inputStreamController.add('recompile ${file.path} abc\n'
+                  '${file.path}\n'
+                  'abc\n'
+              .codeUnits);
+        } else if (count == 1) {
+          expect(count, 1);
+          // Second request is to 'recompile', which results in incremental
+          // kernel file and invalidation of StatelessWidget.
+          var dillIncFile = File('${dillFile.path}.incremental.dill');
+          var widgetCacheFile =
+              File('${dillFile.path}.incremental.dill.widget_cache');
+          compiledResult.expectNoErrors(filename: dillIncFile.path);
+          expect(dillIncFile.existsSync(), equals(true));
+          expect(widgetCacheFile.existsSync(), equals(true));
+          expect(widgetCacheFile.readAsStringSync(), 'FooWidget');
+          count += 1;
+          inputStreamController.add('accept\n'.codeUnits);
+
+          file.writeAsStringSync("""
+import "package:flutter/src/widgets/framework.dart";
+
+void main() {}
+
+class FooWidget extends StatelessWidget {
+  // Added.
+}
+
+class FizzWidget extends StatefulWidget {
+  // Added.
+}
+
+class BarState extends State<FizzWidget> {}
+""");
+          inputStreamController.add('recompile ${file.path} abc\n'
+                  '${file.path}\n'
+                  'abc\n'
+              .codeUnits);
+        } else if (count == 2) {
+          // Second request is to 'recompile', which results in incremental
+          // kernel file and invalidation of StatelessWidget.
+          var dillIncFile = File('${dillFile.path}.incremental.dill');
+          var widgetCacheFile =
+              File('${dillFile.path}.incremental.dill.widget_cache');
+          compiledResult.expectNoErrors(filename: dillIncFile.path);
+          expect(dillIncFile.existsSync(), equals(true));
+          expect(widgetCacheFile.existsSync(), equals(true));
+          expect(widgetCacheFile.readAsStringSync(), 'FizzWidget');
+          count += 1;
+          inputStreamController.add('accept\n'.codeUnits);
+
+          file.writeAsStringSync("""
+import "package:flutter/src/widgets/framework.dart";
+
+void main() {}
+
+class FooWidget extends StatelessWidget {
+  // Added.
+}
+
+class FizzWidget extends StatefulWidget {
+  // Added.
+}
+
+class BarState extends State<FizzWidget> {
+  // Added.
+}
+""");
+          inputStreamController.add('recompile ${file.path} abc\n'
+                  '${file.path}\n'
+                  'abc\n'
+              .codeUnits);
+        } else if (count == 3) {
+          // Third request is to 'recompile', which results in incremental
+          // kernel file and invalidation of State class.
+          var dillIncFile = File('${dillFile.path}.incremental.dill');
+          var widgetCacheFile =
+              File('${dillFile.path}.incremental.dill.widget_cache');
+          compiledResult.expectNoErrors(filename: dillIncFile.path);
+          expect(dillIncFile.existsSync(), equals(true));
+          expect(widgetCacheFile.existsSync(), equals(true));
+          expect(widgetCacheFile.readAsStringSync(), 'FizzWidget');
+          count += 1;
+          inputStreamController.add('accept\n'.codeUnits);
+
+          file.writeAsStringSync("""
+import "package:flutter/src/widgets/framework.dart";
+
+void main() {}
+
+// Added
+
+class FooWidget extends StatelessWidget {
+  // Added.
+}
+
+class FizzWidget extends StatefulWidget {
+  // Added.
+}
+
+class BarState extends State<FizzWidget> {
+  // Added.
+}
+""");
+          inputStreamController.add('recompile ${file.path} abc\n'
+                  '${file.path}\n'
+                  'abc\n'
+              .codeUnits);
+        } else if (count == 4) {
+          // Fourth request is to 'recompile', which results in incremental
+          // kernel file and no widget cache
+          var dillIncFile = File('${dillFile.path}.incremental.dill');
+          var widgetCacheFile =
+              File('${dillFile.path}.incremental.dill.widget_cache');
+          compiledResult.expectNoErrors(filename: dillIncFile.path);
+          expect(dillIncFile.existsSync(), equals(true));
+          expect(widgetCacheFile.existsSync(), equals(false));
+          inputStreamController.add('quit\n'.codeUnits);
+        }
+      });
+      expect(await result, 0);
+      inputStreamController.close();
+    });
+
     test('unsafe-package-serialization', () async {
       // Package A.
       var file = File('${tempDir.path}/pkgA/a.dart')
diff --git a/tools/VERSION b/tools/VERSION
index 6597a0f..d2bf2b9 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 135
+PRERELEASE 136
 PRERELEASE_PATCH 0
\ No newline at end of file