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

Switch expression with sealed classes fails type Inference when sharing interface #55274

Closed
yehorh opened this issue Mar 22, 2024 · 1 comment

Comments

@yehorh
Copy link

yehorh commented Mar 22, 2024

General info

  • Dart 3.3.2 (stable) (Tue Mar 19 20:44:48 2024 +0000) on "macos_arm64"
  • on macos / Version 14.4 (Build 23E214)
  • locale is en-UA

Reproduction Steps:

  • Define a set of sealed classes that implement a common interface.
  • Use a switch expression to return an instance of these sealed classes based on some condition.
  • Notice that the type inference only succeeds if the switch expression outputs classes in some specific order.

examples:

Error: A value of type 'Object' can't be assigned to a variable of type 'Sealed'.

import 'dart:math' as math;

void main() {
  final generator = math.Random();
  final randomInt = generator.nextInt(2);
  
    final Sealed result = switch (randomInt) {
      0 => Baz(),
      1 => Bar(),
      2 => Foo(),
      _ => throw Exception('unexpected error')
    };
  print(result);
}

sealed class Sealed {}

class Foo extends Sealed {}

class Bar extends Sealed implements SomeInterface {}

class Baz extends Sealed implements SomeInterface {}

abstract interface class SomeInterface {}

OK

import 'dart:math' as math;

void main() {
  final generator = math.Random();
  final randomInt = generator.nextInt(2);
  
    final Sealed result = switch (randomInt) {
      2 => Foo(),
      0 => Baz(),
      1 => Bar(),
      _ => throw Exception('unexpected error')
    };
  print(result);
}

sealed class Sealed {}

class Foo extends Sealed {}

class Bar extends Sealed implements SomeInterface {}

class Baz extends Sealed implements SomeInterface {}

abstract interface class SomeInterface {}
@eernstg
Copy link
Member

eernstg commented Mar 22, 2024

This is working as specified, though not quite as desired.

Note that Bar and Baz have two immediate supertypes Sealed and SomeInterface, and there is no way a general upper bound algorithm could choose one over the other. So the algorithm proceeds to take the next shared supertype which is Object.

Hence, the resulting upper bound of the types Bar and Baz is Object, and it remains Object when we take the upper bound of that and Foo.

In contrast, Sealed is a shared upper bound of Foo and Baz (and SomeInterface is not, so there's no ambiguity here, and no need to go to Object), and the upper bound remains Sealed when we proceed with Bar.

This topic has been discussed extensively here: https://github.com/dart-lang/language/issues?q=is%3Aopen+is%3Aissue+label%3Aleast-upper-bound.

Note that this proposal is currently being implemented (and it's very likely to be accepted into the language), and it would resolve this issue by allowing the context type to help choosing a useful upper bound.

I'll close the issue because it doesn't report new issues. Discussions about possible remedies can be taken in issues on the language repository.

@eernstg eernstg closed this as completed Mar 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants