update json macro and macro code, to handle omitted types better and not crash the analyzer

Change-Id: I2b30568dd4163b0032624f1fd0db6d6a6246f16c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365960
Auto-Submit: Jake Macdonald <jakemac@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/json/lib/json.dart b/pkg/json/lib/json.dart
index b3f9aff..b96fb2f 100644
--- a/pkg/json/lib/json.dart
+++ b/pkg/json/lib/json.dart
@@ -310,19 +310,14 @@
   /// Returns a [Code] object which is an expression that converts a JSON map
   /// (referenced by [jsonReference]) into an instance of type [type].
   Future<Code> _convertTypeFromJson(
-      TypeAnnotation type,
+      TypeAnnotation rawType,
       Code jsonReference,
       DefinitionBuilder builder,
       _SharedIntrospectionData introspectionData) async {
-    if (type is! NamedTypeAnnotation) {
-      builder.report(Diagnostic(
-          DiagnosticMessage(
-              'Only fields with named types are allowed on serializable '
-              'classes',
-              target: type.asDiagnosticTarget),
-          Severity.error));
+    final type = _checkNamedType(rawType, builder);
+    if (type == null) {
       return RawCode.fromString(
-          "throw 'Unable to deserialize type ${type.code.debugString}'");
+          "throw 'Unable to deserialize type ${rawType.code.debugString}'");
     }
 
     // Follow type aliases until we reach an actual named type.
@@ -407,6 +402,28 @@
     ]);
   }
 
+  /// Returns [type] as a [NamedTypeAnnotation] if it is one, otherwise returns
+  /// `null` and emits relevant error diagnostics.
+  NamedTypeAnnotation? _checkNamedType(TypeAnnotation type, Builder builder) {
+    if (type is NamedTypeAnnotation) return type;
+    if (type is OmittedTypeAnnotation) {
+      builder.report(Diagnostic(
+          DiagnosticMessage(
+              'Only fields with explicit types are allowed on serializable '
+              'classes, please add a type.',
+              target: type.asDiagnosticTarget),
+          Severity.error));
+    } else {
+      builder.report(Diagnostic(
+          DiagnosticMessage(
+              'Only fields with named types are allowed on serializable '
+              'classes.',
+              target: type.asDiagnosticTarget),
+          Severity.error));
+    }
+    return null;
+  }
+
   /// Checks that [method] is a valid `toJson` method, and throws a
   /// [DiagnosticException] if not.
   Future<bool> _checkValidToJson(
@@ -431,19 +448,14 @@
   /// Returns a [Code] object which is an expression that converts an instance
   /// of type [type] (referenced by [valueReference]) into a JSON map.
   Future<Code> _convertTypeToJson(
-      TypeAnnotation type,
+      TypeAnnotation rawType,
       Code valueReference,
       DefinitionBuilder builder,
       _SharedIntrospectionData introspectionData) async {
-    if (type is! NamedTypeAnnotation) {
-      builder.report(Diagnostic(
-          DiagnosticMessage(
-              'Only fields with named types are allowed on serializable '
-              'classes',
-              target: type.asDiagnosticTarget),
-          Severity.error));
+    final type = _checkNamedType(rawType, builder);
+    if (type == null) {
       return RawCode.fromString(
-          "throw 'Unable to serialize type ${type.code.debugString}'");
+          "throw 'Unable to serialize type ${rawType.code.debugString}'");
     }
 
     // Follow type aliases until we reach an actual named type.
@@ -651,6 +663,8 @@
           part._writeDebugString(buffer);
         case Identifier():
           buffer.write(part.name);
+        case OmittedTypeAnnotation():
+          buffer.write('<omitted>');
         default:
           buffer.write(part);
       }