Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added "Raw" typedef to: #2473

Merged
merged 1 commit into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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