Skip to content

Commit

Permalink
Added "Raw" typedef to: (#2473)
Browse files Browse the repository at this point in the history
- disable unsupported_provider_value knowingly
- disable the convertion of Futures/Streams into AsyncValues
  • Loading branch information
rrousselGit committed Apr 18, 2023
1 parent 29b89c0 commit 45ff7eb
Show file tree
Hide file tree
Showing 18 changed files with 908 additions and 65 deletions.
5 changes: 5 additions & 0 deletions packages/riverpod_analyzer_utils/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## Unreleased minor

- Added `DartType.isRaw` extension property for checking if a type is from a `Raw` typedef.
- Added `isFromRiverpodAnnotation` type checker.

## 0.2.1 - 2023-04-07

- Handle cascade operators in ref expressions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
part of '../riverpod_ast.dart';

extension RawTypeX on DartType {
/// Returns whether this type is a `Raw` typedef from `package:riverpod_annotation`.
bool get isRaw {
final alias = this.alias;
if (alias == null) return false;
return alias.element.name == 'Raw' &&
isFromRiverpodAnnotation.isExactly(alias.element);
}
}

extension on LibraryElement {
static final _asyncValueCache = Expando<ClassElement>();

Expand Down Expand Up @@ -79,6 +89,8 @@ DartType? _computeExposedType(
DartType createdType,
LibraryElement library,
) {
if (createdType.isRaw) return createdType;

if (createdType.isDartAsyncFuture ||
createdType.isDartAsyncFutureOr ||
createdType.isDartAsyncStream) {
Expand All @@ -90,6 +102,8 @@ DartType? _computeExposedType(
}

DartType _getValueType(DartType createdType) {
if (createdType.isRaw) return createdType;

if (createdType.isDartAsyncFuture ||
createdType.isDartAsyncFutureOr ||
createdType.isDartAsyncStream) {
Expand Down
3 changes: 3 additions & 0 deletions packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ const widgetRefType =
/// Checks that the value is coming from a `package:riverpod` package
const isFromRiverpod = TypeChecker.fromPackage('riverpod');

/// Checks that the value is coming from a `package:riverpod_annotation` package
const isFromRiverpodAnnotation = TypeChecker.fromPackage('riverpod_annotation');

/// Checks that the value is coming from a `package:riverpod` package
const isFromFlutterRiverpod = TypeChecker.fromPackage('flutter_riverpod');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,50 @@ import 'package:test/test.dart';
import 'analyser_test_utils.dart';

void main() {
testSource('Parses Raw types', source: '''
import 'package:riverpod_annotation/riverpod_annotation.dart';
@riverpod
Raw<Future<int>> value(ValueRef ref) async => 0;
@riverpod
Future<int> value2(Value2Ref ref) async => 0;
@riverpod
Future<Raw<int>> value3(Value3Ref ref) async => 0;
''', (resolver) async {
final result = await resolver.resolveRiverpodAnalyssiResult(
ignoreErrors: true,
);

final value = result.statelessProviderDeclarations.singleWhere(
(e) => e.name.toString() == 'value',
);
final value2 = result.statelessProviderDeclarations.singleWhere(
(e) => e.name.toString() == 'value2',
);
final value3 = result.statelessProviderDeclarations.singleWhere(
(e) => e.name.toString() == 'value3',
);
expect(value.createdType.toString(), 'Future<int>');
expect(value.exposedType.toString(), 'Future<int>');
expect(value.valueType.toString(), 'Future<int>');
expect(value.createdType.isRaw, true);
expect(value.valueType.isRaw, true);

expect(value2.createdType.toString(), 'Future<int>');
expect(value2.exposedType.toString(), 'AsyncValue<int>');
expect(value2.valueType.toString(), 'int');
expect(value2.createdType.isRaw, false);
expect(value2.valueType.isRaw, false);

expect(value3.createdType.toString(), 'Future<int>');
expect(value3.exposedType.toString(), 'AsyncValue<int>');
expect(value3.valueType.toString(), 'int');
expect(value3.createdType.isRaw, false);
expect(value3.valueType.isRaw, true);
});

testSource('Decode needsOverride/isScoped', source: '''
import 'package:riverpod_annotation/riverpod_annotation.dart';
Expand Down
12 changes: 12 additions & 0 deletions packages/riverpod_annotation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## Unreleased minor

- Added support for `Raw` typedef in the return value of providers.
This can be used to disable the conversion of Futures/Streams into AsyncValues
```dart
@riverpod
Raw<Future<int>> myProvider(...) async => ...;
...
// returns a Future<int> instead of AsyncValue<int>
Future<int> value = ref.watch(myProvider);
```

## 2.0.4 - 2023-04-07

- `riverpod` upgraded to `2.3.4`
Expand Down
50 changes: 49 additions & 1 deletion packages/riverpod_annotation/lib/src/riverpod_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
import 'package:meta/meta.dart';
import 'package:meta/meta_meta.dart';

import '../riverpod_annotation.dart';

/// {@template riverpod_annotation.provider}
/// An annotation placed on classes or functions.
///
/// This tells riverpod_generator to generate a provider out of the annotated
/// This tells riverpod_generator to generate a provider out of the annotated.
///
/// By default, Riverpod will convert [Future]s and [Stream]s into [AsyncValue]s.
/// If this is undesired, you can use [Raw] to have Riverpod forcibly return
/// the raw [Future]/[Stream] instead.
/// element.
/// {@endtemplate}
@Target({TargetKind.classType, TargetKind.function})
Expand Down Expand Up @@ -53,3 +59,45 @@ class ProviderFor {
/// The code annotated by `@riverpod`
final Object value;
}

/// {@template riverpod_annotation.raw}
/// An annotation for marking a value type as "should not be handled
/// by Riverpod".
///
/// This is a type-alias to [T], and has no runtime effect. It is only used
/// as metadata for the code-generator/linter.
///
/// This serves two purposes:
/// - It enables a provider to return a [Future]/[Stream] without
/// having the provider converting it into an [AsyncValue].
/// ```dart
/// @riverpod
/// Raw<Future<int>> myProvider(...) async => ...;
/// ...
/// // returns a Future<int> instead of AsyncValue<int>
/// Future<int> value = ref.watch(myProvider);
/// ```
///
/// - It can silence the linter when a provider returns a value that
/// is otherwise not supported, such as Flutter's `ChangeNotifier`:
/// ```dart
/// // Will not trigger the "unsupported return type" lint
/// @riverpod
/// Raw<MyChangeNotifier> myProvider(...) => MyChangeNotifier();
/// ```
///
/// The typedef can be used at various places within the return type declaration.
///
/// For example, a valid return type is `Future<Raw<ChangeNotifier>>`.
/// This way, Riverpod will convert the [Future] into an [AsyncValue], and
/// the usage of `ChangeNotifier` will not trigger the linter:
///
/// ```dart
/// @riverpod
/// Future<Raw<ChangeNotifier>> myProvider(...) async => ...;
/// ...
/// AsyncValue<ChangeNotifier> value = ref.watch(myProvider);
/// ```
///
/// {@endtemplate}
typedef Raw<T> = T;
12 changes: 12 additions & 0 deletions packages/riverpod_generator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## Unreleased minor

- Added support for `Raw` typedef in the return value of providers.
This can be used to disable the conversion of Futures/Streams into AsyncValues
```dart
@riverpod
Raw<Future<int>> myProvider(...) async => ...;
...
// returns a Future<int> instead of AsyncValue<int>
Future<int> value = ref.watch(myProvider);
```

## 2.1.6 - 2023-04-07

- If a provider has an empty list of dependencies, the generated list is now `const`
Expand Down
37 changes: 19 additions & 18 deletions packages/riverpod_generator/lib/src/templates/family.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,19 @@ class FamilyTemplate extends Template {
required BuildYamlOptions options,
}) {
var leading = '';
String providerType;
if (!provider.annotation.element.keepAlive) {
leading = 'AutoDispose';
}

var providerType = '${leading}Provider';

final returnType = provider.createdType;
if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) {
providerType = '${leading}FutureProvider';
} else if (returnType.isDartAsyncStream) {
providerType = '${leading}StreamProvider';
} else {
providerType = '${leading}Provider';
if (!returnType.isRaw) {
if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) {
providerType = '${leading}FutureProvider';
} else if (returnType.isDartAsyncStream) {
providerType = '${leading}StreamProvider';
}
}

final parameters = provider
Expand Down Expand Up @@ -94,18 +95,18 @@ typedef $refName = ${providerType}Ref<${provider.valueType}>;
leading = 'AutoDispose';
}

String providerType;
String notifierBaseType;
var providerType = '${leading}NotifierProviderImpl';
var notifierBaseType = 'Buildless${leading}Notifier';

final returnType = provider.createdType;
if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) {
providerType = '${leading}AsyncNotifierProviderImpl';
notifierBaseType = 'Buildless${leading}AsyncNotifier';
} else if (returnType.isDartAsyncStream) {
providerType = '${leading}StreamNotifierProviderImpl';
notifierBaseType = 'Buildless${leading}StreamNotifier';
} else {
providerType = '${leading}NotifierProviderImpl';
notifierBaseType = 'Buildless${leading}Notifier';
if (!returnType.isRaw) {
if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) {
providerType = '${leading}AsyncNotifierProviderImpl';
notifierBaseType = 'Buildless${leading}AsyncNotifier';
} else if (returnType.isDartAsyncStream) {
providerType = '${leading}StreamNotifierProviderImpl';
notifierBaseType = 'Buildless${leading}StreamNotifier';
}
}

final parameters = provider.buildMethod.parameters!.parameterElements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,24 @@ class StatefulProviderTemplate extends Template {

@override
void run(StringBuffer buffer) {
String notifierBaseType;
String providerType;
var leading = '';
if (!provider.annotation.element.keepAlive) {
leading = 'AutoDispose';
}

var notifierBaseType = '${leading}Notifier';
var providerType = '${leading}NotifierProvider';

final providerName = providerNameFor(provider.providerElement, options);
final returnType = provider.buildMethod.returnType?.type;
if ((returnType?.isDartAsyncFutureOr ?? false) ||
(returnType?.isDartAsyncFuture ?? false)) {
notifierBaseType = '${leading}AsyncNotifier';
providerType = '${leading}AsyncNotifierProvider';
} else if (returnType?.isDartAsyncStream ?? false) {
notifierBaseType = '${leading}StreamNotifier';
providerType = '${leading}StreamNotifierProvider';
} else {
notifierBaseType = '${leading}Notifier';
providerType = '${leading}NotifierProvider';
final returnType = provider.createdType;
if (!returnType.isRaw) {
if ((returnType.isDartAsyncFutureOr) || (returnType.isDartAsyncFuture)) {
notifierBaseType = '${leading}AsyncNotifier';
providerType = '${leading}AsyncNotifierProvider';
} else if (returnType.isDartAsyncStream) {
notifierBaseType = '${leading}StreamNotifier';
providerType = '${leading}StreamNotifierProvider';
}
}

buffer.write('''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@ class StatelessProviderTemplate extends Template {

@override
void run(StringBuffer buffer) {
String providerType;
var leading = '';

if (!provider.annotation.element.keepAlive) {
leading = 'AutoDispose';
}

final returnType = provider.node.returnType?.type;
if ((returnType?.isDartAsyncFutureOr ?? false) ||
(returnType?.isDartAsyncFuture ?? false)) {
providerType = '${leading}FutureProvider';
} else if (returnType?.isDartAsyncStream ?? false) {
providerType = '${leading}StreamProvider';
} else {
providerType = '${leading}Provider';
var providerType = '${leading}Provider';

final returnType = provider.createdType;
if (!returnType.isRaw) {
if ((returnType.isDartAsyncFutureOr) || (returnType.isDartAsyncFuture)) {
providerType = '${leading}FutureProvider';
} else if (returnType.isDartAsyncStream) {
providerType = '${leading}StreamProvider';
}
}

final providerName = providerNameFor(provider.providerElement, options);
Expand Down
52 changes: 52 additions & 0 deletions packages/riverpod_generator/test/integration/sync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,58 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'sync.g.dart';

@riverpod
Raw<Future<String>> rawFuture(RawFutureRef ref) async {
return 'Hello world';
}

@riverpod
Raw<Stream<String>> rawStream(RawStreamRef ref) async* {
yield 'Hello world';
}

@riverpod
class RawFutureClass extends _$RawFutureClass {
@override
Raw<Future<String>> build() async {
return 'Hello world';
}
}

@riverpod
class RawStreamClass extends _$RawStreamClass {
@override
Raw<Stream<String>> build() async* {
yield 'Hello world';
}
}

@riverpod
Raw<Future<String>> rawFamilyFuture(RawFamilyFutureRef ref, int id) async {
return 'Hello world';
}

@riverpod
Raw<Stream<String>> rawFamilyStream(RawFamilyStreamRef ref, int id) async* {
yield 'Hello world';
}

@riverpod
class RawFamilyFutureClass extends _$RawFamilyFutureClass {
@override
Raw<Future<String>> build(int id) async {
return 'Hello world';
}
}

@riverpod
class RawFamilyStreamClass extends _$RawFamilyStreamClass {
@override
Raw<Stream<String>> build(int id) async* {
yield 'Hello world';
}
}

/// This is some documentation
@riverpod
String public(PublicRef ref) {
Expand Down
Loading

0 comments on commit 45ff7eb

Please sign in to comment.