Skip to content

Generic Never types in switch expressions. #3028

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
water-mizuu opened this issue Apr 28, 2023 · 2 comments
Closed

Generic Never types in switch expressions. #3028

water-mizuu opened this issue Apr 28, 2023 · 2 comments
Labels
request Requests to resolve a particular developer problem state-duplicate This issue or pull request already exists

Comments

@water-mizuu
Copy link

I apologize for my ignorance for the proper terminologies in my problem, but I will show it here. Please feel free to change the title.

I have a snippet from a library I've created for my own purposes, which has a problem like below when using switch expressions.

sealed class Box<E> {
  E get item;
  const Box();
}

class FilledBox<E> extends Box<E> {
  @override
  final E item;

  const FilledBox(this.item);
}

class NeverBox extends Box<Never> {
  @override
  Never get item => throw Error();

  const NeverBox();
}

/// This works.
Box<int> traditionalFunction(RegExp regex, String input) {
  Match? match = regex.matchAsPrefix(input);
  if (match is Match) {
    return FilledBox(match.groupCount);
  } else {
    return NeverBox();
  }
}

/// This does not.
Box<int> switchFunction(RegExp regex, String input) => //
    /// A value of type 'Object' can't be returned from the function
    ///   'switchFunction' because it has a return type of 'Box<int>'
    switch (regex.matchAsPrefix(input)) {
      Match(:int groupCount) => FilledBox(groupCount),
      null => NeverBox(),
    };
@water-mizuu water-mizuu added the request Requests to resolve a particular developer problem label Apr 28, 2023
@water-mizuu
Copy link
Author

Also, the current workaround that I do for this is to just do a typecast (which the analyzer recognizes is unnecessary.)

/// This works now.
Box<int> switchFunction(RegExp regex, String input) => //
    switch (regex.matchAsPrefix(input)) {
      Match(:int groupCount) => FilledBox(groupCount),
      ///  Unnecessary cast. Try removing the cast.
      null => NeverBox() as Box<int>,
    };

@lrhn
Copy link
Member

lrhn commented Apr 28, 2023

This is not really related to switches, you get the same error with a conditional expression:

Box<int> foo(bool b) {  
  return b ? FilledBox(2) : NeverBox();
  // A value of type 'Object' can't be returned from the function 'foo' because it has a return type of 'Box<int>'.              
}

The problem is the upper-bound algorithm trying to find a common superinterface of FilledBox<int>(2) an NeverBox(), and failing because their immediate superinterfaces are Box<int> and Box<Never>, which are different, so neither is considered as a common superinterface. So it gives up and chooses Object.
It doesn't use the context type, Box<int>, which could be used as an extra hint.

We're considering whether we can make the upper-bound algorithm better, but it hasn't happened yet. It's non-trivial to do something which is both correct, efficient and doesn't break too much existing code.

We have a bunch of examples of this problem. One issue is #1877

Your workaround is precisely what is needed, because it ensures that Box<int> occurs in the superinterfaces of both branches.

@lrhn lrhn closed this as completed Apr 28, 2023
@lrhn lrhn added the state-duplicate This issue or pull request already exists label Apr 28, 2023
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 state-duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

2 participants