Skip to content

Commit

Permalink
(refactor) Pull out query method return type into own class
Browse files Browse the repository at this point in the history
  • Loading branch information
mqus committed Mar 14, 2021
1 parent 7cc7b70 commit c92b156
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 122 deletions.
4 changes: 2 additions & 2 deletions floor_generator/lib/processor/dao_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ class DaoProcessor extends Processor<Dao> {
final transactionMethods = _getTransactionMethods(methods);

final streamQueryables = queryMethods
.where((method) => method.returnsStream)
.map((method) => method.queryable);
.where((method) => method.returnType.isStream)
.map((method) => method.returnType.queryable);
final streamEntities = streamQueryables.whereType<Entity>().toSet();
final streamViews = streamQueryables.whereType<View>().toSet();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ class QueryMethodProcessorError {
);
}

InvalidGenerationSourceError get doesNotReturnFutureVoid {
return InvalidGenerationSourceError(
'A query returning `void` has to return exactly `Future<void>`.',
todo:
'Define the return type as `Future<void>` or return a non-empty value.',
element: _methodElement,
);
}

ProcessorError queryMethodParameterIsNullable(
final ParameterElement parameterElement,
) {
Expand Down
95 changes: 34 additions & 61 deletions floor_generator/lib/processor/query_method_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:floor_generator/misc/type_utils.dart';
import 'package:floor_generator/processor/error/query_method_processor_error.dart';
import 'package:floor_generator/processor/processor.dart';
import 'package:floor_generator/value_object/query_method.dart';
import 'package:floor_generator/value_object/query_method_return_type.dart';
import 'package:floor_generator/value_object/queryable.dart';
import 'package:floor_generator/value_object/type_converter.dart';

Expand All @@ -34,29 +35,9 @@ class QueryMethodProcessor extends Processor<QueryMethod> {
QueryMethod process() {
final name = _methodElement.displayName;
final parameters = _methodElement.parameters;
final rawReturnType = _methodElement.returnType;
final returnType = _getAndCheckReturnType();

final query = _getQuery();
final returnsStream = rawReturnType.isStream;

_assertReturnsFutureOrStream(rawReturnType, returnsStream);

final returnsList = _getReturnsList(rawReturnType, returnsStream);
final flattenedReturnType = _getFlattenedReturnType(
rawReturnType,
returnsStream,
returnsList,
);

_assertReturnsNullableSingle(
returnsStream,
returnsList,
flattenedReturnType,
);

final queryable = _queryables.firstWhereOrNull((queryable) =>
queryable.classElement.displayName ==
flattenedReturnType.getDisplayString(withNullability: false));

final parameterTypeConverters = parameters
.expand((parameter) =>
Expand All @@ -67,20 +48,17 @@ class QueryMethodProcessor extends Processor<QueryMethod> {
_methodElement.getTypeConverters(TypeConverterScope.daoMethod) +
parameterTypeConverters;

if (queryable != null) {
final fieldTypeConverters =
queryable.fields.mapNotNull((field) => field.typeConverter);
if (returnType.queryable != null) {
final fieldTypeConverters = returnType.queryable!.fields
.mapNotNull((field) => field.typeConverter);
allTypeConverters.addAll(fieldTypeConverters);
}

return QueryMethod(
_methodElement,
name,
query,
rawReturnType,
flattenedReturnType,
parameters,
queryable,
returnType,
allTypeConverters,
);
}
Expand Down Expand Up @@ -115,30 +93,8 @@ class QueryMethodProcessor extends Processor<QueryMethod> {
);
}

DartType _getFlattenedReturnType(
final DartType rawReturnType,
final bool returnsStream,
final bool returnsList,
) {
final type = returnsStream
? _methodElement.returnType.flatten()
: _methodElement.library.typeSystem.flatten(rawReturnType);
return returnsList ? type.flatten() : type;
}

bool _getReturnsList(final DartType returnType, final bool returnsStream) {
final type = returnsStream
? returnType.flatten()
: _methodElement.library.typeSystem.flatten(returnType);

return type.isDartCoreList;
}

void _assertReturnsFutureOrStream(
final DartType rawReturnType,
final bool returnsStream,
) {
if (!rawReturnType.isDartAsyncFuture && !returnsStream) {
void _assertReturnsFutureOrStream(final DartType rawType) {
if (!rawType.isDartAsyncFuture && !rawType.isStream) {
throw _processorError.doesNotReturnFutureNorStream;
}
}
Expand All @@ -159,19 +115,36 @@ class QueryMethodProcessor extends Processor<QueryMethod> {
}
}

void _assertReturnsNullableSingle(
final bool returnsStream,
final bool returnsList,
final DartType flattenedReturnType,
) {
if (!returnsList &&
!flattenedReturnType.isVoid &&
!flattenedReturnType.isNullable) {
if (returnsStream) {
void _assertReturnsNullableSingle(QueryMethodReturnType returnType) {
if (!returnType.isList &&
!returnType.isVoid &&
!returnType.flat.isNullable) {
if (returnType.isStream) {
throw _processorError.doesNotReturnNullableStream;
} else {
throw _processorError.doesNotReturnNullableFuture;
}
}
}

void _assertReturnsFutureOnVoid(QueryMethodReturnType returnType) {
if (returnType.isVoid && (returnType.isStream || returnType.isList)) {
throw _processorError.doesNotReturnFutureVoid;
}
}

QueryMethodReturnType _getAndCheckReturnType() {
_assertReturnsFutureOrStream(_methodElement.returnType);

final type = QueryMethodReturnType(_methodElement.returnType);

_assertReturnsNullableSingle(type);
_assertReturnsFutureOnVoid(type);

type.queryable = _queryables.firstWhereOrNull((queryable) =>
queryable.classElement.displayName ==
type.flat.getDisplayString(withNullability: false));

return type;
}
}
49 changes: 6 additions & 43 deletions floor_generator/lib/value_object/query_method.dart
Original file line number Diff line number Diff line change
@@ -1,89 +1,52 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';
import 'package:floor_generator/misc/extension/set_equality_extension.dart';
import 'package:floor_generator/misc/type_utils.dart';
import 'package:floor_generator/value_object/queryable.dart';
import 'package:floor_generator/value_object/query_method_return_type.dart';
import 'package:floor_generator/value_object/type_converter.dart';

/// Wraps a method annotated with Query
/// to enable easy access to code generation relevant data.
class QueryMethod {
final MethodElement methodElement;

final String name;

/// Query where ':' got replaced with '$'.
final String query;

final DartType rawReturnType;

/// Flattened return type.
///
/// E.g.
/// Future<T> -> T,
/// Future<List<T>> -> T
///
/// Stream<T> -> T
/// Stream<List<T>> -> T
final DartType flattenedReturnType;
final QueryMethodReturnType returnType;

final List<ParameterElement> parameters;

final Queryable? queryable;

final Set<TypeConverter> typeConverters;

QueryMethod(
this.methodElement,
this.name,
this.query,
this.rawReturnType,
this.flattenedReturnType,
this.parameters,
this.queryable,
this.returnType,
this.typeConverters,
);

bool get returnsList {
final type = returnsStream
? rawReturnType.flatten()
: methodElement.library.typeSystem.flatten(rawReturnType);

return type.isDartCoreList;
}

bool get returnsStream => rawReturnType.isStream;

bool get returnsVoid => flattenedReturnType.isVoid;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is QueryMethod &&
runtimeType == other.runtimeType &&
methodElement == other.methodElement &&
name == other.name &&
query == other.query &&
rawReturnType == other.rawReturnType &&
flattenedReturnType == other.flattenedReturnType &&
parameters.equals(other.parameters) &&
queryable == other.queryable &&
returnType == other.returnType &&
typeConverters.equals(other.typeConverters);

@override
int get hashCode =>
methodElement.hashCode ^
name.hashCode ^
query.hashCode ^
rawReturnType.hashCode ^
flattenedReturnType.hashCode ^
parameters.hashCode ^
queryable.hashCode ^
returnType.hashCode ^
typeConverters.hashCode;

@override
String toString() {
return 'QueryMethod{methodElement: $methodElement, name: $name, query: $query, rawReturnType: $rawReturnType, flattenedReturnType: $flattenedReturnType, parameters: $parameters, queryable: $queryable, typeConverters: $typeConverters}';
return 'QueryMethod{name: $name, query: $query, parameters: $parameters, returnType: $returnType, typeConverters: $typeConverters}';
}
}
63 changes: 63 additions & 0 deletions floor_generator/lib/value_object/query_method_return_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'package:floor_generator/misc/type_utils.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:floor_generator/value_object/queryable.dart';

/// A simple accessor class for providing all properties of
/// the return type of a query method
class QueryMethodReturnType {
final DartType raw;

late Queryable? queryable;

// the following attributes are derived on construction and stored.
final bool isStream;
final bool isFuture;
final bool isList;

/// Flattened return type
///
/// E.g.
/// Future<T> -> T
/// Stream<List<T>> -> T
final DartType flat;

bool get isVoid => flat.isVoid;
bool get isPrimitive =>
flat.isVoid ||
flat.isDartCoreDouble ||
flat.isDartCoreInt ||
flat.isDartCoreBool ||
flat.isDartCoreString ||
flat.isUint8List;

QueryMethodReturnType(this.raw)
: isStream = raw.isStream,
isFuture = raw.isDartAsyncFuture,
isList = raw.flatten().isDartCoreList,
flat = _flattenWithList(raw);

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is QueryMethodReturnType &&
runtimeType == other.runtimeType &&
raw == other.raw &&
queryable == other.queryable;

@override
int get hashCode => raw.hashCode ^ queryable.hashCode;

@override
String toString() {
return 'QueryMethodReturnType{raw: $raw, flat: $flat, queryable: $queryable}';
}

static DartType _flattenWithList(DartType raw) {
final flattenedOnce = raw.flatten();
if (flattenedOnce.isDartCoreList) {
return flattenedOnce.flatten();
} else {
return flattenedOnce;
}
}
}
16 changes: 8 additions & 8 deletions floor_generator/lib/writer/query_method_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ class QueryMethodWriter implements Writer {
Method write() {
final builder = MethodBuilder()
..annotations.add(overrideAnnotationExpression)
..returns = refer(_queryMethod.rawReturnType.getDisplayString(
..returns = refer(_queryMethod.returnType.raw.getDisplayString(
withNullability: true,
))
..name = _queryMethod.name
..requiredParameters.addAll(_generateMethodParameters())
..body = Code(_generateMethodBody());

if (!_queryMethod.returnsStream || _queryMethod.returnsVoid) {
if (!_queryMethod.returnType.isStream || _queryMethod.returnType.isVoid) {
builder..modifier = MethodModifier.async;
}
return builder.build();
Expand All @@ -54,17 +54,17 @@ class QueryMethodWriter implements Writer {
}

final arguments = _generateArguments();
final queryable = _queryMethod.queryable;
final queryable = _queryMethod.returnType.queryable;
// null queryable implies void-returning query method
if (_queryMethod.returnsVoid || queryable == null) {
if (_queryMethod.returnType.isVoid || queryable == null) {
_methodBody.write(_generateNoReturnQuery(arguments));
return _methodBody.toString();
}

final constructor = queryable.constructor;
final mapper = '(Map<String, Object?> row) => $constructor';

if (_queryMethod.returnsStream) {
if (_queryMethod.returnType.isStream) {
_methodBody.write(_generateStreamQuery(arguments, mapper));
} else {
_methodBody.write(_generateQuery(arguments, mapper));
Expand Down Expand Up @@ -129,7 +129,7 @@ class QueryMethodWriter implements Writer {
if (arguments != null) parameters.write('arguments: $arguments, ');
parameters.write('mapper: $mapper');

if (_queryMethod.returnsList) {
if (_queryMethod.returnType.isList) {
return 'return _queryAdapter.queryList($parameters);';
} else {
return 'return _queryAdapter.query($parameters);';
Expand All @@ -140,7 +140,7 @@ class QueryMethodWriter implements Writer {
final String? arguments,
final String mapper,
) {
final queryable = _queryMethod.queryable;
final queryable = _queryMethod.returnType.queryable;
// can't be null as validated before
if (queryable == null) throw ArgumentError.notNull();

Expand All @@ -153,7 +153,7 @@ class QueryMethodWriter implements Writer {
..write('isView: $isView, ')
..write('mapper: $mapper');

if (_queryMethod.returnsList) {
if (_queryMethod.returnType.isList) {
return 'return _queryAdapter.queryListStream($parameters);';
} else {
return 'return _queryAdapter.queryStream($parameters);';
Expand Down
Loading

0 comments on commit c92b156

Please sign in to comment.