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

Switching on bounded generic types #4085

Open
LeoBound opened this issue Sep 6, 2024 · 5 comments
Open

Switching on bounded generic types #4085

LeoBound opened this issue Sep 6, 2024 · 5 comments
Labels
request Requests to resolve a particular developer problem

Comments

@LeoBound
Copy link

LeoBound commented Sep 6, 2024

It appears exhaustiveness checking isn't performed when switching on bounded generic types.

sealed class A {
  abstract final String s;
}

class B extends A {
  final String s = "B";
}
class C extends A {
    final String s = "C";
}

// Error: The type 'Type' is not exhaustively matched by the switch cases since it doesn't match 'Type()'.
// Try adding a wildcard pattern or cases that match 'Type()'.
String getString<T extends A>() => switch(T) {
   const (A) => throw UnimplementedError,
   const (B) => B().s,
   const (C) => C().s, 
}

By adding a default case to this (which I don't can ever be called) the error goes away and this behaves as expected.
I may have missed the proper way to do this. If I had an object of type T then I understand I could use the usual exhaustiveness checking (Matching on B _ etc).

As an aside, matching const nullable types seems to require typedef'ing them, e.g.

typedef AorNull = A?;
switch(T) {
const (A?) => ,  // Error: Expected an identifier
const (AorNull) => , // Analyser is happy

}

Thanks for your help 🙂

@LeoBound LeoBound added the request Requests to resolve a particular developer problem label Sep 6, 2024
@lrhn
Copy link
Member

lrhn commented Sep 6, 2024

You're switching on Type objects, which are not an exhaustive type.

Then if Dart had the ability to recognize all the possible Type objects that could possibly be values for the types bound to T, it's non-trivial to exhaust them off any type is generic, because Type object comparison is only by equality.

And in the current case, where the types are not generic, you haven't checked for Never.

So, unlikely to become a feature.
The way to write generic methods is to trust the type parameters generically. Switching on them is closer to reflection than generics.

@LeoBound
Copy link
Author

LeoBound commented Sep 6, 2024

Yep that makes sense 🙂

Could you explain what you mean by trusting type parameters "genetically"?

Is there a 'proper' way to do something like

class FromJsonObject {
  static FromJsonObject fromJson(json) => ...;
}

class Subclass1 extends FromJsonObject {
  static Subclass1 fromJson(json) => ...;
}

class Subclass2 extends FromJsonObject {
  static Subclass2 fromJson(json) => ...;
}

// switch expression here is pseudocode
T fromJson<T extends FromJsonObject>(Map<String, dynamic> json) {
  return switch(T) {
    Subclass1 => Subclass1.fromJson(json),
    Subclass2 => Subclass1.fromJson(json),
    FromJsonObject => FromJsonObject.fromJson(json),
    _ => throw UnimplementedError(),
}
}

Essentially something like static inheritance - or maybe this would be considered the wrong way to go about such a thing?

Typically I have this sort of thing pop up when constructing model classes from some serialised format, where the model classes inherit some (often sealed) base class.

Thanks again for your help

@lrhn
Copy link
Member

lrhn commented Sep 7, 2024

Could you explain what you mean by trusting type parameters "genetically"?

I mean "generically". Damn mobile keyboard autocorrect.
Fixed!

@lrhn
Copy link
Member

lrhn commented Sep 7, 2024

Is there a 'proper' way to do something like
...
Essentially something like static inheritance

No. You cannot go from type parameter, or the even less useful Type object, to static members. It's safer not to try.

If you knew the actual static type at the point where you call fromJson, you could just do ThatType.fromJson(map), so I'm guessing you only have a type parameter for it. And then you have already lost.
That type parameter should be followed, from start to end, by an object that allows you to create an object of that type from a JSON-like map. Maybe just the T Function(Map<String, Object?>) function that is the fromJson function of the type.

@TekExplorer
Copy link

TekExplorer commented Sep 24, 2024

This is where static interfaces would be useful. Rust just... does this. ie: <T extends FromJson>() => T::fromJson(map)
Sadly, we're stuck doing <T>(T Function(Map<String, dynamic>) fromJson) => fromJson(map)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

3 participants