diff --git a/moor_generator/lib/src/backends/build/build_backend.dart b/moor_generator/lib/src/backends/build/build_backend.dart index 6c3fdb3a..96944eb9 100644 --- a/moor_generator/lib/src/backends/build/build_backend.dart +++ b/moor_generator/lib/src/backends/build/build_backend.dart @@ -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; - final serializedType = json[dartExpression] as Map; + final json = + (jsonDecode(content) as Map).cast(); + 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; } } diff --git a/moor_generator/lib/src/backends/build/preprocess_builder.dart b/moor_generator/lib/src/backends/build/preprocess_builder.dart index 3a697f28..14572b87 100644 --- a/moor_generator/lib/src/backends/build/preprocess_builder.dart +++ b/moor_generator/lib/src/backends/build/preprocess_builder.dart @@ -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 = {}; + // 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 = {}; - - 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); - } } diff --git a/moor_generator/lib/src/backends/build/serialized_types.dart b/moor_generator/lib/src/backends/build/serialized_types.dart deleted file mode 100644 index d445db3b..00000000 --- a/moor_generator/lib/src/backends/build/serialized_types.dart +++ /dev/null @@ -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 json) { - switch (json['type'] as String) { - case 'interface': - return _SerializedInterfaceType.fromJson(json); - } - throw ArgumentError('Unknown type kind: ${json['type']}'); - } - - SerializedType(); - - Map toJson(); -} - -// todo handle non-interface types, recursive types - -class _SerializedInterfaceType extends SerializedType { - final Uri libraryUri; - final String className; - final List typeArgs; - - _SerializedInterfaceType(this.libraryUri, this.className, this.typeArgs); - - factory _SerializedInterfaceType.fromJson(Map 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)) - .toList(), - ); - } - - @override - Map 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 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 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 _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 _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); - } - } -}