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:logging/logging.dart';
|
||||||
import 'package:moor_generator/src/analyzer/options.dart';
|
import 'package:moor_generator/src/analyzer/options.dart';
|
||||||
import 'package:moor_generator/src/backends/backend.dart';
|
import 'package:moor_generator/src/backends/backend.dart';
|
||||||
import 'package:moor_generator/src/backends/build/serialized_types.dart';
|
|
||||||
|
|
||||||
class BuildBackend extends Backend {
|
class BuildBackend extends Backend {
|
||||||
final MoorOptions options;
|
final MoorOptions options;
|
||||||
|
@ -29,10 +28,8 @@ class BuildBackend extends Backend {
|
||||||
class BuildBackendTask extends BackendTask {
|
class BuildBackendTask extends BackendTask {
|
||||||
final BuildStep step;
|
final BuildStep step;
|
||||||
final BuildBackend backend;
|
final BuildBackend backend;
|
||||||
final TypeDeserializer typeDeserializer;
|
|
||||||
|
|
||||||
BuildBackendTask(this.step, this.backend)
|
BuildBackendTask(this.step, this.backend);
|
||||||
: typeDeserializer = TypeDeserializer(step);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Uri get entrypoint => step.inputId.uri;
|
Uri get entrypoint => step.inputId.uri;
|
||||||
|
@ -76,6 +73,7 @@ class BuildBackendTask extends BackendTask {
|
||||||
// prepare the result. See PreprocessBuilder for details
|
// prepare the result. See PreprocessBuilder for details
|
||||||
final preparedHelperFile =
|
final preparedHelperFile =
|
||||||
_resolve(context).changeExtension('.dart_in_moor');
|
_resolve(context).changeExtension('.dart_in_moor');
|
||||||
|
final temporaryFile = _resolve(context).changeExtension('.temp.dart');
|
||||||
|
|
||||||
if (!await step.canRead(preparedHelperFile)) {
|
if (!await step.canRead(preparedHelperFile)) {
|
||||||
throw CannotLoadTypeException('Generated helper file not found. '
|
throw CannotLoadTypeException('Generated helper file not found. '
|
||||||
|
@ -83,10 +81,17 @@ class BuildBackendTask extends BackendTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
final content = await step.readAsString(preparedHelperFile);
|
final content = await step.readAsString(preparedHelperFile);
|
||||||
final json = jsonDecode(content) as Map<String, dynamic>;
|
final json =
|
||||||
final serializedType = json[dartExpression] as Map<String, dynamic>;
|
(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
|
final library = await step.resolver.libraryFor(temporaryFile);
|
||||||
.deserialize(SerializedType.fromJson(serializedType));
|
final field = library.units.first.topLevelVariables
|
||||||
|
.firstWhere((element) => element.name == fieldName);
|
||||||
|
return field.type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
|
||||||
import 'package:build/build.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:moor_generator/src/utils/string_escaper.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
|
||||||
|
@ -85,8 +82,11 @@ class PreprocessBuilder extends Builder {
|
||||||
final importedDartFiles =
|
final importedDartFiles =
|
||||||
seenFiles.where((asset) => asset.extension == '.dart');
|
seenFiles.where((asset) => asset.extension == '.dart');
|
||||||
|
|
||||||
|
final codeToField = <String, String>{};
|
||||||
|
|
||||||
// to analyze the expressions, generate a fake Dart file that declares each
|
// 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();
|
final dartBuffer = StringBuffer();
|
||||||
for (final import in importedDartFiles) {
|
for (final import in importedDartFiles) {
|
||||||
|
@ -95,38 +95,20 @@ class PreprocessBuilder extends Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < dartLexemes.length; i++) {
|
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');
|
final tempDartAsset = input.changeExtension('.temp.dart');
|
||||||
|
|
||||||
|
// Await the file needed to resolve types.
|
||||||
await buildStep.writeAsString(tempDartAsset, dartBuffer.toString());
|
await buildStep.writeAsString(tempDartAsset, dartBuffer.toString());
|
||||||
|
|
||||||
// we can now resolve the library we just wrote
|
// And the file mapping Dart expressions onto the variable names here
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
final outputAsset = input.changeExtension('.dart_in_moor');
|
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';
|
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