mirror of https://github.com/AMT-Cheif/drift.git
Fix resolving nested serialized types
This commit is contained in:
parent
230a18abdc
commit
cea5a9119c
|
@ -8,7 +8,6 @@ import 'package:build/build.dart' as build show log;
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:moor_generator/src/analyzer/options.dart';
|
||||
import 'package:moor_generator/src/backends/backend.dart';
|
||||
import 'package:moor_generator/src/backends/build/serialized_types.dart';
|
||||
|
||||
class BuildBackend extends Backend {
|
||||
final MoorOptions options;
|
||||
|
@ -29,10 +28,8 @@ class BuildBackend extends Backend {
|
|||
class BuildBackendTask extends BackendTask {
|
||||
final BuildStep step;
|
||||
final BuildBackend backend;
|
||||
final TypeDeserializer typeDeserializer;
|
||||
|
||||
BuildBackendTask(this.step, this.backend)
|
||||
: typeDeserializer = TypeDeserializer(step);
|
||||
BuildBackendTask(this.step, this.backend);
|
||||
|
||||
@override
|
||||
Uri get entrypoint => step.inputId.uri;
|
||||
|
@ -76,6 +73,7 @@ class BuildBackendTask extends BackendTask {
|
|||
// prepare the result. See PreprocessBuilder for details
|
||||
final preparedHelperFile =
|
||||
_resolve(context).changeExtension('.dart_in_moor');
|
||||
final temporaryFile = _resolve(context).changeExtension('.temp.dart');
|
||||
|
||||
if (!await step.canRead(preparedHelperFile)) {
|
||||
throw CannotLoadTypeException('Generated helper file not found. '
|
||||
|
@ -83,10 +81,17 @@ class BuildBackendTask extends BackendTask {
|
|||
}
|
||||
|
||||
final content = await step.readAsString(preparedHelperFile);
|
||||
final json = jsonDecode(content) as Map<String, dynamic>;
|
||||
final serializedType = json[dartExpression] as Map<String, dynamic>;
|
||||
final json =
|
||||
(jsonDecode(content) as Map<String, dynamic>).cast<String, String>();
|
||||
final fieldName = json[dartExpression];
|
||||
if (fieldName == null) {
|
||||
throw CannotLoadTypeException('Generated helper file does not contain '
|
||||
'$dartExpression!');
|
||||
}
|
||||
|
||||
return typeDeserializer
|
||||
.deserialize(SerializedType.fromJson(serializedType));
|
||||
final library = await step.resolver.libraryFor(temporaryFile);
|
||||
final field = library.units.first.topLevelVariables
|
||||
.firstWhere((element) => element.name == fieldName);
|
||||
return field.type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:moor_generator/src/backends/build/serialized_types.dart';
|
||||
import 'package:moor_generator/src/utils/string_escaper.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
|
@ -85,8 +82,11 @@ class PreprocessBuilder extends Builder {
|
|||
final importedDartFiles =
|
||||
seenFiles.where((asset) => asset.extension == '.dart');
|
||||
|
||||
final codeToField = <String, String>{};
|
||||
|
||||
// to analyze the expressions, generate a fake Dart file that declares each
|
||||
// expression in a `var`, we can then read the static type.
|
||||
// expression in a `var`, we can then read the static type when resolving
|
||||
// file later.
|
||||
|
||||
final dartBuffer = StringBuffer();
|
||||
for (final import in importedDartFiles) {
|
||||
|
@ -95,38 +95,20 @@ class PreprocessBuilder extends Builder {
|
|||
}
|
||||
|
||||
for (var i = 0; i < dartLexemes.length; i++) {
|
||||
dartBuffer.write('var ${_nameForDartExpr(i)} = ${dartLexemes[i]};\n');
|
||||
final name = _nameForDartExpr(i);
|
||||
dartBuffer.write('var $name = ${dartLexemes[i]};\n');
|
||||
codeToField[dartLexemes[i]] = name;
|
||||
}
|
||||
|
||||
final tempDartAsset = input.changeExtension('.temp.dart');
|
||||
|
||||
// Await the file needed to resolve types.
|
||||
await buildStep.writeAsString(tempDartAsset, dartBuffer.toString());
|
||||
|
||||
// we can now resolve the library we just wrote
|
||||
|
||||
final createdLibrary = await buildStep.resolver.libraryFor(tempDartAsset);
|
||||
final serializer = TypeSerializer(buildStep.resolver);
|
||||
final codeToType = <String, SerializedType>{};
|
||||
|
||||
for (var i = 0; i < dartLexemes.length; i++) {
|
||||
final member =
|
||||
_findVariableDefinition(_nameForDartExpr(i), createdLibrary);
|
||||
final node = await buildStep.resolver.astNodeFor(member, resolve: true)
|
||||
as VariableDeclaration;
|
||||
|
||||
final type = node.initializer.staticType;
|
||||
codeToType[dartLexemes[i]] = await serializer.serialize(type);
|
||||
}
|
||||
|
||||
// And the file mapping Dart expressions onto the variable names here
|
||||
final outputAsset = input.changeExtension('.dart_in_moor');
|
||||
await buildStep.writeAsString(outputAsset, jsonEncode(codeToType));
|
||||
await buildStep.writeAsString(outputAsset, json.encode(codeToField));
|
||||
}
|
||||
|
||||
String _nameForDartExpr(int i) => 'expr_$i';
|
||||
|
||||
TopLevelVariableElement _findVariableDefinition(
|
||||
String name, LibraryElement element) {
|
||||
return element.units
|
||||
.expand((u) => u.topLevelVariables)
|
||||
.firstWhere((e) => e.name == name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
//@dart=2.9
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/analysis/session.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:moor_generator/src/backends/backend.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
|
||||
/// A serialized version of a [DartType].
|
||||
abstract class SerializedType {
|
||||
factory SerializedType.fromJson(Map<String, dynamic> json) {
|
||||
switch (json['type'] as String) {
|
||||
case 'interface':
|
||||
return _SerializedInterfaceType.fromJson(json);
|
||||
}
|
||||
throw ArgumentError('Unknown type kind: ${json['type']}');
|
||||
}
|
||||
|
||||
SerializedType();
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
}
|
||||
|
||||
// todo handle non-interface types, recursive types
|
||||
|
||||
class _SerializedInterfaceType extends SerializedType {
|
||||
final Uri libraryUri;
|
||||
final String className;
|
||||
final List<SerializedType> typeArgs;
|
||||
|
||||
_SerializedInterfaceType(this.libraryUri, this.className, this.typeArgs);
|
||||
|
||||
factory _SerializedInterfaceType.fromJson(Map<String, dynamic> json) {
|
||||
final serializedTypes = json['type_args'] as List;
|
||||
|
||||
return _SerializedInterfaceType(
|
||||
Uri.parse(json['library'] as String),
|
||||
json['class_name'] as String,
|
||||
serializedTypes
|
||||
.map((raw) => SerializedType.fromJson(raw as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'type': 'interface',
|
||||
'library': libraryUri.toString(),
|
||||
'class_name': className,
|
||||
'type_args': [for (var type in typeArgs) type.toJson()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TypeSerializer {
|
||||
final Resolver resolver;
|
||||
|
||||
TypeSerializer(this.resolver);
|
||||
|
||||
Future<SerializedType> serialize(DartType type) async {
|
||||
if (type is InterfaceType) {
|
||||
final dartClass = type.element;
|
||||
|
||||
Uri uri;
|
||||
if (dartClass.librarySource.uri.scheme == 'dart') {
|
||||
uri = dartClass.librarySource.uri;
|
||||
} else {
|
||||
uri = (await resolver.assetIdForElement(dartClass)).uri;
|
||||
}
|
||||
|
||||
final serializedArgs =
|
||||
await Future.wait(type.typeArguments.map(serialize));
|
||||
|
||||
return _SerializedInterfaceType(
|
||||
uri,
|
||||
dartClass.name,
|
||||
serializedArgs,
|
||||
);
|
||||
} else {
|
||||
throw UnsupportedError(
|
||||
"Couldn't serialize $type, we only support interface types");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TypeDeserializer {
|
||||
/// The [BuildStep] used to resolve
|
||||
final BuildStep buildStep;
|
||||
|
||||
/// The analysis session used to read libraries from the Dart SDK which can't
|
||||
/// be obtained via build apis.
|
||||
AnalysisSession _lastSession;
|
||||
|
||||
TypeDeserializer(this.buildStep);
|
||||
|
||||
Future<DartType> deserialize(SerializedType type) async {
|
||||
if (type is _SerializedInterfaceType) {
|
||||
final library = await _libraryFromUri(type.libraryUri);
|
||||
final args = await Future.wait(type.typeArgs.map(deserialize));
|
||||
|
||||
return LibraryReader(library).findType(type.className).instantiate(
|
||||
typeArguments: args, nullabilitySuffix: NullabilitySuffix.star);
|
||||
}
|
||||
|
||||
throw AssertionError('Unhandled type: $type');
|
||||
}
|
||||
|
||||
Future<LibraryElement> _libraryFromUri(Uri uri) async {
|
||||
if (uri.scheme == 'dart') {
|
||||
var session = await _obtainSession();
|
||||
|
||||
// The session could be invalidated by other builders outside of our
|
||||
// control. There's no better way than to continue fetching a new session
|
||||
// in that case.
|
||||
var attempts = 0;
|
||||
const maxAttempts = 5;
|
||||
|
||||
// ignore: literal_only_boolean_expressions
|
||||
while (true) {
|
||||
try {
|
||||
final info = await session.getLibraryByUri2(uri.toString());
|
||||
if (info is LibraryElementResult) {
|
||||
return info.element;
|
||||
} else {
|
||||
throw NotALibraryException(uri);
|
||||
}
|
||||
} on InconsistentAnalysisException {
|
||||
_lastSession = null; // Invalidate session, then try again
|
||||
session = await _obtainSession();
|
||||
attempts++;
|
||||
|
||||
if (attempts == maxAttempts) rethrow;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final library = await buildStep.resolver.libraryFor(AssetId.resolve(uri));
|
||||
_lastSession ??= library?.session;
|
||||
return library;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<AnalysisSession> _obtainSession() {
|
||||
if (_lastSession != null) {
|
||||
return _lastSession;
|
||||
} else {
|
||||
// resolve bogus library that's not going to change often. We can use the
|
||||
// session from that library. Technically, this is non-hermetic, but the
|
||||
// build runner will throw everything away after an SDK update so it
|
||||
// should be safe
|
||||
return _libraryFromUri(Uri.parse('package:moor/sqlite_keywords.dart'))
|
||||
.then((_) => _lastSession);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue