Skip to content

Cleanup inference to enable clean DDC compile #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 21, 2017
15 changes: 12 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
## 0.1.1
## 0.2.0

* Fail generation when undefined types are encountered.
Throw a helpful error.
* **BREAKING** Fail generation for types that are not a JSON primitive or that
do not explicitly supports JSON serialization.

* Eliminated all implicit casts in generated code. These would end up being
runtime checks in most cases.

* Provide a helpful error when generation fails due to undefined types.

## 0.1.0+1

Expand All @@ -10,7 +15,11 @@
## 0.1.0

* Split off from [source_gen](https://pub.dartlang.org/packages/source_gen).

* Add `/* unsafe */` comments to generated output likely to be unsafe.

* Support (de)serializing values in `Map`.

* Fix ordering of fields when they are initialized via constructor.

* Don't use static members when calculating fields to (de)serialize.
3 changes: 2 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
analyzer:
strong-mode: true
strong-mode:
implicit-casts: false
errors:
unused_element: error
unused_import: error
Expand Down
6 changes: 3 additions & 3 deletions example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Person extends Object with _$PersonSerializerMixin {

Person(this.firstName, this.lastName, {this.middleName, this.dateOfBirth});

factory Person.fromJson(json) => _$PersonFromJson(json);
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}

@JsonSerializable()
Expand All @@ -29,7 +29,7 @@ class Order extends Object with _$OrderSerializerMixin {

Order();

factory Order.fromJson(json) => _$OrderFromJson(json);
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
}

@JsonSerializable(createToJson: false)
Expand All @@ -40,7 +40,7 @@ class Item {

Item();

factory Item.fromJson(json) => _$ItemFromJson(json);
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
}

@JsonLiteral('data.json')
Expand Down
27 changes: 15 additions & 12 deletions example/example.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 47 additions & 20 deletions lib/src/json_serializable_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class JsonSerializableGenerator
// Explicitly using `LinkedHashMap` – we want these ordered.
var fields = new LinkedHashMap<String, FieldElement>.fromIterable(
fieldsList,
key: (f) => f.name);
key: (FieldElement f) => f.name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[aside] I'm a little surprised this one is necessary - is fieldsList not well typed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// Get the constructor to use for the factory

Expand Down Expand Up @@ -111,8 +111,14 @@ class JsonSerializableGenerator

var pairs = <String>[];
fields.forEach((fieldName, field) {
pairs.add("'${_fieldToJsonMapKey(fieldName, field)}': "
"${_fieldToJsonMapValue(fieldName, field.type)}");
try {
pairs.add("'${_fieldToJsonMapKey(fieldName, field)}': "
"${_fieldToJsonMapValue(fieldName, field.type)}");
} on _UnsupportedTypeError {
throw new InvalidGenerationSourceError(
"Could not generate `toJson` code for `${friendlyNameForElement(field)}`.",
todo: "Make sure all of the types are serializable.");
}
});
buffer.writeln(pairs.join(','));

Expand Down Expand Up @@ -188,7 +194,8 @@ class JsonSerializableGenerator
// Generate the static factory method
//
buffer.writeln();
buffer.writeln('$className ${prefix}FromJson(Map json) =>');
buffer
.writeln('$className ${prefix}FromJson(Map<String, dynamic> json) =>');
buffer.write(' new $className(');
buffer.writeAll(
ctorArguments.map((paramElement) => _jsonMapAccessToField(
Expand Down Expand Up @@ -245,20 +252,20 @@ class JsonSerializableGenerator
return _mapFieldToJsonMapValue(expression, fieldType, depth);
}

return "$expression /* unsafe */";
throw new _UnsupportedTypeError(fieldType, expression);
}

String _listFieldToJsonMapValue(
String expression, DartType fieldType, int depth) {
assert(_coreListChecker.isAssignableFromType(fieldType));
assert(_coreIterableChecker.isAssignableFromType(fieldType));

// This block will yield a regular list, which works fine for JSON
// Although it's possible that child elements may be marked unsafe

var isList = _coreListChecker.isAssignableFromType(fieldType);
var substitute = "v$depth";
var subFieldValue = _fieldToJsonMapValue(substitute,
_getIterableGenericType(fieldType as InterfaceType), depth + 1);
var subFieldValue = _fieldToJsonMapValue(
substitute, _getIterableGenericType(fieldType), depth + 1);

// In the case of trivial JSON types (int, String, etc), `subFieldValue`
// will be identical to `substitute` – so no explicit mapping is needed.
Expand Down Expand Up @@ -313,7 +320,7 @@ class JsonSerializableGenerator
"$expression.keys,"
"$expression.values.map(($substitute) => $subFieldValue))";
}
return "$expression /* unsafe */";
throw new _UnsupportedTypeError(fieldType, expression);
}

String _jsonMapAccessToField(String name, FieldElement field,
Expand All @@ -322,7 +329,14 @@ class JsonSerializableGenerator
var result = "json['$name']";

var targetType = ctorParam?.type ?? field.type;
return _writeAccessToJsonValue(result, targetType);

try {
return _writeAccessToJsonValue(result, targetType);
} on _UnsupportedTypeError {
throw new InvalidGenerationSourceError(
"Could not generate fromJson code for `${friendlyNameForElement(field)}`.",
todo: "Make sure all of the types are serializable.");
}
}

String _writeAccessToJsonValue(String varExpression, DartType searchType,
Expand All @@ -335,16 +349,15 @@ class JsonSerializableGenerator
}

if (_coreIterableChecker.isAssignableFromType(searchType)) {
var iterableGenericType =
_getIterableGenericType(searchType as InterfaceType);
var iterableGenericType = _getIterableGenericType(searchType);

var itemVal = "v$depth";
var itemSubVal = _writeAccessToJsonValue(itemVal, iterableGenericType,
depth: depth + 1);

// If `itemSubVal` is the same, then we don't need to do anything fancy
if (itemVal == itemSubVal) {
return varExpression;
return '$varExpression as List';
}

var output = "($varExpression as List)?.map(($itemVal) => $itemSubVal)";
Expand All @@ -370,7 +383,7 @@ class JsonSerializableGenerator
_coreStringChecker.isExactlyType(keyArg);

if (!safeKey) {
return "$varExpression /* unsafe key type */";
throw new _UnsupportedTypeError(keyArg, varExpression);
}

// this is the trivial case. Do a runtime cast to the known type of JSON
Expand All @@ -379,15 +392,22 @@ class JsonSerializableGenerator
return "$varExpression as Map<String, dynamic>";
}

if (_stringBoolNumChecker.isAssignableFromType(valueArg)) {
// No mapping of the values is required!
return "$varExpression == null ? null :"
"new Map<String, $valueArg>.from($varExpression as Map)";
}

// In this case, we're going to create a new Map with matching reified
// types.

var itemVal = "v$depth";
var itemSubVal =
_writeAccessToJsonValue(itemVal, valueArg, depth: depth + 1);

// In this case, we're going to create a new Map with matching reified
// types.
return "$varExpression == null ? null :"
"new Map<String, $valueArg>.fromIterables("
"($varExpression as Map).keys,"
"($varExpression as Map<String, dynamic>).keys,"
"($varExpression as Map).values.map(($itemVal) => $itemSubVal))";
} else if (searchType.isDynamic || searchType.isObject) {
// just return it as-is. We'll hope it's safe.
Expand All @@ -396,7 +416,7 @@ class JsonSerializableGenerator
return "$varExpression as $searchType";
}

return "$varExpression /* unsafe */";
throw new _UnsupportedTypeError(searchType, varExpression);
}
}

Expand All @@ -414,10 +434,10 @@ String _fieldToJsonMapKey(String fieldName, FieldElement field) {
return fieldName;
}

DartType _getIterableGenericType(InterfaceType type) =>
DartType _getIterableGenericType(DartType type) =>
_getTypeArguments(type, _coreIterableChecker).single;

List<DartType> _getTypeArguments(InterfaceType type, TypeChecker checker) {
List<DartType> _getTypeArguments(DartType type, TypeChecker checker) {
var iterableImplementation =
_getImplementationType(type, checker) as InterfaceType;

Expand Down Expand Up @@ -457,3 +477,10 @@ final _stringBoolNumChecker = new TypeChecker.any([
new TypeChecker.fromUrl('dart:core#bool'),
new TypeChecker.fromUrl('dart:core#num')
]);

class _UnsupportedTypeError extends Error {
final String expression;
final DartType type;

_UnsupportedTypeError(this.type, this.expression);
}
7 changes: 5 additions & 2 deletions lib/src/type_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ class JsonHelper extends TypeHelper {
@override
String deserialize(DartType targetType, String expression) {
// TODO: the type could be imported from a library with a prefix!
return "new ${targetType.name}.fromJson($expression)";
// github.com/dart-lang/json_serializable/issues/19
return "new ${targetType.name}.fromJson($expression as Map<String, dynamic>)";
}
}

Expand All @@ -114,5 +115,7 @@ class DateTimeHelper extends TypeHelper {

@override
String deserialize(DartType targetType, String expression) =>
"DateTime.parse($expression)";
// TODO(kevmoo) `String` here is ignoring
// github.com/dart-lang/json_serializable/issues/19
"DateTime.parse($expression as String)";
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_serializable
version: 0.1.1-dev
version: 0.2.0-dev
author: Dart Team <misc@dartlang.org>
description: Generates utilities to aid in serializing to/from JSON.
homepage: https://github.com/dart-lang/json_serializable
Expand Down
7 changes: 3 additions & 4 deletions test/analysis_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:analyzer/source/pub_package_map_provider.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart' show FolderBasedDartSdk;
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_io.dart';
import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:cli_util/cli_util.dart' as cli;
Expand All @@ -26,7 +25,7 @@ Future<AnalysisContext> getAnalysisContextForProjectPath(
var sdkPath = cli.getSdkPath();

var resourceProvider = PhysicalResourceProvider.INSTANCE;
DartSdk sdk = new FolderBasedDartSdk(
var sdk = new FolderBasedDartSdk(
resourceProvider, resourceProvider.getFolder(sdkPath));

var packageResolver = _getPackageResolver(projectPath, sdk);
Expand All @@ -51,7 +50,7 @@ Future<AnalysisContext> getAnalysisContextForProjectPath(
return context;
}

UriResolver _getPackageResolver(String projectPath, DartSdk sdk) {
UriResolver _getPackageResolver(String projectPath, FolderBasedDartSdk sdk) {
var dotPackagesPath = p.join(projectPath, '.packages');

if (!FileSystemEntity.isFileSync(dotPackagesPath)) {
Expand All @@ -62,7 +61,7 @@ UriResolver _getPackageResolver(String projectPath, DartSdk sdk) {
var pubPackageMapProvider =
new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk);
var packageMapInfo = pubPackageMapProvider.computePackageMap(
PhysicalResourceProvider.INSTANCE.getResource(projectPath));
PhysicalResourceProvider.INSTANCE.getResource(projectPath) as Folder);
var packageMap = packageMapInfo.packageMap;
if (packageMap == null) {
throw new StateError('An error occurred getting the package map.');
Expand Down
Loading