Specially recognize all supported json types.

Previously, unknown types were allowed and we would just inject a cast which could fail at runtime.

With this change, unknown types are compile time errors.

Change-Id: I537fa6f46a904fe3c2d8998dc91d539a5ac52da5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/366042
Auto-Submit: Jake Macdonald <jakemac@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/json/lib/json.dart b/pkg/json/lib/json.dart
index cb99a66..a21bf10 100644
--- a/pkg/json/lib/json.dart
+++ b/pkg/json/lib/json.dart
@@ -328,45 +328,54 @@
           ])
         : null;
 
-    // Check if `typeDecl` is one of the supported collection types.
-    if (classDecl.isExactly('List', _dartCore)) {
-      return RawCode.fromParts([
-        if (nullCheck != null) nullCheck,
-        '[ for (final item in ',
-        jsonReference,
-        ' as ',
-        introspectionData.jsonListCode,
-        ') ',
-        await _convertTypeFromJson(type.typeArguments.single,
-            RawCode.fromString('item'), builder, introspectionData),
-        ']',
-      ]);
-    } else if (classDecl.isExactly('Set', _dartCore)) {
-      return RawCode.fromParts([
-        if (nullCheck != null) nullCheck,
-        '{ for (final item in ',
-        jsonReference,
-        ' as ',
-        introspectionData.jsonListCode,
-        ')',
-        await _convertTypeFromJson(type.typeArguments.single,
-            RawCode.fromString('item'), builder, introspectionData),
-        '}',
-      ]);
-    } else if (classDecl.isExactly('Map', _dartCore)) {
-      return RawCode.fromParts([
-        if (nullCheck != null) nullCheck,
-        '{ for (final ',
-        introspectionData.mapEntry,
-        '(:key, :value) in (',
-        jsonReference,
-        ' as ',
-        introspectionData.jsonMapCode,
-        ').entries) key: ',
-        await _convertTypeFromJson(type.typeArguments.last,
-            RawCode.fromString('value'), builder, introspectionData),
-        '}',
-      ]);
+    // Check for the supported core types, and deserialize them accordingly.
+    if (classDecl.library.uri == _dartCore) {
+      switch (classDecl.identifier.name) {
+        case 'List':
+          return RawCode.fromParts([
+            if (nullCheck != null) nullCheck,
+            '[ for (final item in ',
+            jsonReference,
+            ' as ',
+            introspectionData.jsonListCode,
+            ') ',
+            await _convertTypeFromJson(type.typeArguments.single,
+                RawCode.fromString('item'), builder, introspectionData),
+            ']',
+          ]);
+        case 'Set':
+          return RawCode.fromParts([
+            if (nullCheck != null) nullCheck,
+            '{ for (final item in ',
+            jsonReference,
+            ' as ',
+            introspectionData.jsonListCode,
+            ')',
+            await _convertTypeFromJson(type.typeArguments.single,
+                RawCode.fromString('item'), builder, introspectionData),
+            '}',
+          ]);
+        case 'Map':
+          return RawCode.fromParts([
+            if (nullCheck != null) nullCheck,
+            '{ for (final ',
+            introspectionData.mapEntry,
+            '(:key, :value) in (',
+            jsonReference,
+            ' as ',
+            introspectionData.jsonMapCode,
+            ').entries) key: ',
+            await _convertTypeFromJson(type.typeArguments.last,
+                RawCode.fromString('value'), builder, introspectionData),
+            '}',
+          ]);
+        case 'int' || 'double' || 'num' || 'String' || 'bool':
+          return RawCode.fromParts([
+            jsonReference,
+            ' as ',
+            type.code,
+          ]);
+      }
     }
 
     // Otherwise, check if `classDecl` has a `fromJson` constructor.
@@ -386,13 +395,15 @@
       ]);
     }
 
-    // Finally, we just cast directly to the field type.
-    // TODO: Check that it is a valid type we can cast from JSON.
-    return RawCode.fromParts([
-      jsonReference,
-      ' as ',
-      type.code,
-    ]);
+    // Unsupported type, report an error and return valid code that throws.
+    builder.report(Diagnostic(
+        DiagnosticMessage(
+            'Unable to deserialize type, it must be a native JSON type or a '
+            'type with a `fromJson(Map<String, Object?> json)` constructor.',
+            target: type.asDiagnosticTarget),
+        Severity.error));
+    return RawCode.fromString(
+        "throw 'Unable to deserialize type ${type.code.debugString}'");
   }
 
   /// Declares a `fromJson` constructor in [clazz], if one does not exist
@@ -583,30 +594,34 @@
           ])
         : null;
 
-    // Check for the supported collection types, and serialize them accordingly.
-    if (classDecl.isExactly('List', _dartCore) ||
-        classDecl.isExactly('Set', _dartCore)) {
-      return RawCode.fromParts([
-        if (nullCheck != null) nullCheck,
-        '[ for (final item in ',
-        valueReference,
-        ') ',
-        await _convertTypeToJson(type.typeArguments.single,
-            RawCode.fromString('item'), builder, introspectionData),
-        ']',
-      ]);
-    } else if (classDecl.isExactly('Map', _dartCore)) {
-      return RawCode.fromParts([
-        if (nullCheck != null) nullCheck,
-        '{ for (final ',
-        introspectionData.mapEntry,
-        '(:key, :value) in ',
-        valueReference,
-        '.entries) key: ',
-        await _convertTypeToJson(type.typeArguments.last,
-            RawCode.fromString('value'), builder, introspectionData),
-        '}',
-      ]);
+    // Check for the supported core types, and serialize them accordingly.
+    if (classDecl.library.uri == _dartCore) {
+      switch (classDecl.identifier.name) {
+        case 'List' || 'Set':
+          return RawCode.fromParts([
+            if (nullCheck != null) nullCheck,
+            '[ for (final item in ',
+            valueReference,
+            ') ',
+            await _convertTypeToJson(type.typeArguments.single,
+                RawCode.fromString('item'), builder, introspectionData),
+            ']',
+          ]);
+        case 'Map':
+          return RawCode.fromParts([
+            if (nullCheck != null) nullCheck,
+            '{ for (final ',
+            introspectionData.mapEntry,
+            '(:key, :value) in ',
+            valueReference,
+            '.entries) key: ',
+            await _convertTypeToJson(type.typeArguments.last,
+                RawCode.fromString('value'), builder, introspectionData),
+            '}',
+          ]);
+        case 'int' || 'double' || 'num' || 'String' || 'bool':
+          return valueReference;
+      }
     }
 
     // Next, check if it has a `toJson()` method and call that.
@@ -622,9 +637,15 @@
       ]);
     }
 
-    // Finally, we just return the value as is if we can't otherwise handle it.
-    // TODO: Check that it is a valid type we can serialize.
-    return valueReference;
+    // Unsupported type, report an error and return valid code that throws.
+    builder.report(Diagnostic(
+        DiagnosticMessage(
+            'Unable to serialize type, it must be a native JSON type or a '
+            'type with a `Map<String, Object?> toJson()` method.',
+            target: type.asDiagnosticTarget),
+        Severity.error));
+    return RawCode.fromString(
+        "throw 'Unable to serialize type ${type.code.debugString}'");
   }
 
   /// Declares a `toJson` method in [clazz], if one does not exist already.