Skip to content

Assignability of generic type Never #3670

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

Closed
SandroMaglione opened this issue Mar 21, 2024 · 6 comments
Closed

Assignability of generic type Never #3670

SandroMaglione opened this issue Mar 21, 2024 · 6 comments
Labels
request Requests to resolve a particular developer problem

Comments

@SandroMaglione
Copy link

SandroMaglione commented Mar 21, 2024

I noticed that the Never type seems to be assignable to any other type when working with generics:

class Obj<A> {
  const Obj();
}

final val = Obj<Never>();

void fun(Obj<String> obj) {}

void main() {
  fun(val); // 👈 No error here when Obj<Never> passed to Obj<String>
}

I would expect calling fun with val to error at compile time, since Obj<Never> cannot be assigned to Obj<String>.

In my specific case I am using Never to express a "missing"/"not possible" value (reference), and this breaks at compile time in cases like below:

class Obj<A> {
  const Obj();
  
  void execute(A a) {}
}

final val = Obj<Never>();

void fun(Obj<String> obj) {
  obj.execute("abc"); // 👈 `String` not `Never`
}

void main() {
  fun(val);
}

Is this the correct/expected behaviour?

Note: In my usecase would void be preferable to Never?

@SandroMaglione SandroMaglione added the request Requests to resolve a particular developer problem label Mar 21, 2024
SandroMaglione added a commit to SandroMaglione/fpdart that referenced this issue Mar 21, 2024
@mateusfccp
Copy link
Contributor

mateusfccp commented Mar 21, 2024

Never is the bottom type, which means Obj<Never> <: Obj<String>.

In a static analysis, it's completely valid to pass a value of type Never to any place that requires T, being T any other type (in your example, String).

The only problem is that, in practice, it's not possible to create a value of type Never. The only expression, AFAIK, that inherently has a static type Never is a throw expression. You can also make a function that always throws return Never.

Thus, it seems it's working perfectly as expected.

Ps. I tried your second code and it does not break at compile time, but at runtime.

@mateusfccp
Copy link
Contributor

mateusfccp commented Mar 21, 2024

In my usecase would void be preferable to Never?

I am not sure what you are trying to achieve (I didn't read the entire code). In your second sample code, by using void you would get the static analysis to tell you that, obviously, Obj<void> is not a subtype of Obj<String>. However, the semantics are the completely opposite of Never, so I don't know if this is what you want.

void is one of the top types of Dart (just like Object? and dynamic). So, for example, a List<void> can receive any object, just like a List<Object?>. The only difference is that void conveys the semantics of "the type of this value is irrelevant" or "the value is unused", so the static analysis imposes some additional constraints that, in general, disallow using a "void value".

void main() {
  final list = <void>[1, 'String'];
  print('First value: ${list[0]}'); // Compile-time error: this expression has a type of 'void' so its value can't be used.
}

@lrhn
Copy link
Member

lrhn commented Mar 21, 2024

What @mateusfccp says, Never is the bottom type, it's deliberately a subtype of all types.

Your problem isn't Never, it's covariant generics. Your method Object<A>.execute uses the type parameter A contravariantly, but Obj<A> is covariant in A.

That means that Obj<String> o = Obj<Never>(); is allowed, but then doing o.execute("a string") is unsafe and throws at runtime.

That's not just about never.
Doing Obj<Object> o = Obj<String>(); is also valid, and doing o.execute(87) will also throw.
Precisely like List<Object> l = <String>[]; l.add(87);.

So this is all working as intended, and until Dart gets a way to make class type parameters contravariant, is just something to be careful around.

@lrhn lrhn closed this as not planned Won't fix, can't repro, duplicate, stale Mar 21, 2024
@SandroMaglione
Copy link
Author

@lrhn should I look at #524 for type parameters variance in dart? Is the dart team planning to work on it in the near future?

@lrhn
Copy link
Member

lrhn commented Mar 21, 2024

Yes, #524 is the issue for variance annotations.
The language team is always planning to work on that, it just tends to never be the highest priority.

SandroMaglione added a commit to SandroMaglione/fpdart that referenced this issue Mar 21, 2024
@SandroMaglione
Copy link
Author

@lrhn is it possible to check if a generic type is Never?

In my usecase dart is inferring generic types as Never, which causes cryptic runtime errors for users of the library. The problem is that since Never is "assignable" to everything, I cannot place checks on the generic type.

スクリーンショット 2024-03-24 6 55 54

Any suggestions on how to prevent this?

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