Skip to content
New issue

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

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

Already on GitHub? # to your account

Access to parent generic type #54122

Closed
MuhammadEhsanMirzaei opened this issue Nov 21, 2023 · 3 comments
Closed

Access to parent generic type #54122

MuhammadEhsanMirzaei opened this issue Nov 21, 2023 · 3 comments
Labels
type-question A question about expected behavior or functionality

Comments

@MuhammadEhsanMirzaei
Copy link

MuhammadEhsanMirzaei commented Nov 21, 2023

Dart SDK version: 3.2.0 (stable) on "macos_arm64"

I have these samples and think the 'dart' doesn't support this.

abstract class AClass<T> {

}
class BClass implements AClass<List<int>> {`
}
 Future<R>

      callAsync<T extends AClass<R>, R>(`
      
          T input) async {
          
      final result = exampleAsyncMethod();`
    
     return result;  // result type is R`
   
   }

Current:

final bObj = BClass();
final result = callAsync(bObj);  // result type is dynamic now

What I excepted:(*)

final bObj = BClass();
final result = callAsync(bObj);  // result type is List<int> now

I can do this now:

final result = callAsync<BClass, List<int>>(bObj);

But
can I except this(*) improvement in dart?

@lrhn
Copy link
Member

lrhn commented Nov 21, 2023

I see the signature:

Future<R> callAsync<T extends AClass<R>, R>(T input) async  

and I can almost certainly guarantee that it won't work. (It might just type check in some cases, because the R can be inferred from the context and the T from the argument, but then it'll be hit-or-miss whether the two will match up in the middle.

Without a context type, as your bObj example shows, inference has no clue what R should be, and using dynamic is a valid solution. The only constraint is that List<int> <: R (and R <: dynamic from the bound on the type variable), but that's hardly restrictive.

Would it be sufficient for you to write:

Future<R> callAsync<R>(AClass<R> input) async  {
  // ...
}

or do you need the T for something.

Generally, this is not something that Dart type inference is able to solve for you.
The constraints you can describe are not constratining enough to ensure that you get the result that you want, and in many cases, you don't.

@lrhn lrhn added the type-question A question about expected behavior or functionality label Nov 21, 2023
@eernstg
Copy link
Member

eernstg commented Nov 21, 2023

As @lrhn mentioned, the sky is the limit when it comes to inference of the type parameter R, because it just has to be sufficiently general, and then all the constraints are satisfied.

However, @MuhammadEhsanMirzaei, you could use invariance (dart-lang/language#524) to get the effect that the desired type arguments are required. You'll have to write them manually because they aren't inferred (at this time), but at least you won't forget those type arguments, and you won't get them wrong (because that's a compile-time error).

// NB: Needs `--enable-experiment=variance` in order to use the `inout` modifier.

abstract class AClass<inout T> {}

class BClass implements AClass<List<int>> {}

Future<T> exampleAsyncMethod<T>() async => <Never>[] as T;

Future<R> callAsync<T extends AClass<R>, R>(T input) async {
  final result = await exampleAsyncMethod<R>();
  return result; // result type is R
}

void main() async {
  print(await callAsync<BClass, List<int>>(BClass()));
}

With this adjustment, callAsync<BClass, List<int>>(BClass()) succeeds, but callAsync<BClass, List<num>>(BClass()) and callAsync<BClass, List<Never>>(BClass()) are compile-time errors (so now we don't allow R when it's too general, and also not when it's too special). Finally, callAsync(BClass()) is also an error because type inference doesn't choose type arguments that succeed, but this serves as a notification that we have to specify those type arguments explicitly. Most likely type inference will be able to find the correct type arguments when the mechanism is further developed.

However, the above example relies on using a feature that hasn't been released at this time (and we can't promise a 100% that it will be released at all).

So today you'd probably prefer to emulate invariance, which is possible in current Dart as follows:

// --- Provide an invariant `AClass`

typedef Inv<X> = X Function(X);
typedef AClass<X> = _AClass<X, Inv<X>>;
abstract class _AClass<X, Invariance extends Inv<X>> {}

// --- End of invariance emulation.

class BClass implements AClass<List<int>> {}

Future<T> exampleAsyncMethod<T>() async => <Never>[] as T;

Future<R> callAsync<T extends AClass<R>, R>(T input) async {
  final result = await exampleAsyncMethod<R>();
  return result; // result type is R
}

void main() async {
  print(await callAsync<BClass, List<int>>(BClass()));
}

It may or may not be convenient (or even possible) for AClass to be invariant in its type argument because that is a hard commitment to give up a certain amount of typing flexibility. However, if it works for your software to make it invariant then it is certainly an option that you might want to be aware of.

@MuhammadEhsanMirzaei
Copy link
Author

MuhammadEhsanMirzaei commented Mar 3, 2024

@lrhn @eernstg Thank you, just for acknowledging and Appreciate , I add I had planned to use it in the following package Thank you again for your help
Link to exact line
Link to package

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
type-question A question about expected behavior or functionality
Projects
None yet
Development

No branches or pull requests

4 participants