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

Pattern type inference error #52373

Closed
dnys1 opened this issue May 12, 2023 · 8 comments
Closed

Pattern type inference error #52373

dnys1 opened this issue May 12, 2023 · 8 comments
Assignees
Labels
area-front-end Use area-front-end for front end / CFE / kernel format related issues.

Comments

@dnys1
Copy link
Contributor

dnys1 commented May 12, 2023

I'm facing an error with inference of pattern types when using sealed classes.

Here is the full reproduction: https://github.com/dnys1/sealed_repro

Given the following class hierarchy:

Legend:
  Open Class (o)
  Sealed Class (*)
  Final Class (x)
classDiagram
    class BaseException["BaseException o"]
    class AuthException["AuthException *"]
    class UnknownException["UnknownException o"]
    class AuthServiceException["AuthServiceException o"]
    BaseException <|-- AuthException
    AuthException <|-- UnknownException: implements
    AuthException <|-- AuthServiceException: extends

    class AuthResult["AuthResult *"]
    class AuthSuccessResult["AuthSuccessResult x"]
    class AuthPartialSuccessResult["AuthPartialSuccessResult x"] {
        +step1Exception: Step1Exception
        +step2Exception: Step2Exception
    }
    class AuthFailureResult["AuthFailureResult x"] {
        +exception: AuthException
    }
    class Step1Exception["Step1Exception x"]
    class Step2Exception["Step2Exception x"]
    AuthResult <|-- AuthSuccessResult
    AuthResult <|-- AuthPartialSuccessResult
    AuthResult <|-- AuthFailureResult
    UnknownException <|-- Step1Exception: extends
    AuthServiceException <|-- Step2Exception: extends

In base_exception.dart we define an exception hiearchy for which all classes inherit BaseException. From this, we derive AuthException which is sealed and has discrete subtypes, namely UnknownException and AuthServiceException.

In auth_result.dart, we define the result of an Auth operation. The result takes one of three discrete states: a successful result (AuthSuccessResult); a partial success (AuthPartialSuccessResult) where either the first or second step of the operation failed with an AuthException; and a complete failure (AuthFailureResult).


Given a method which performs an Auth operation:

Future<AuthResult> performAuthOperation();

We expect the following to work:

1. Inference on exception type

final result = await performAuthOperation();
switch (result) {
case AuthSuccessResult _:
    print('Success');
case AuthPartialSuccessResult(step1Exception: final exception?) ||
        AuthPartialSuccessResult(step2Exception: final exception?) ||
        AuthFailureResult(:final exception):
    print('Partially failed: $exception');
// For exhaustiveness; should never happen.
case AuthPartialSuccessResult(step1Exception: null, step2Exception: null):
    print('Unknown error occurred');
}

In the second branch, step1Exception, step2Exception, and exception are all subclasses of AuthException, yet its type cannot be inferred. The Analyzer presents the error:

The variable 'exception' has a different type and/or finality in this branch of the logical-or pattern.
Try declaring the variable pattern with the same type and finality in both branches.dartinconsistent_pattern_variable_logical_or
inferred_type

2. Explicit exception type

final result = await performAuthOperation();
switch (result) {
  case AuthSuccessResult _:
    print('Success');
  case AuthPartialSuccessResult(step1Exception: final AuthException exception) ||
        AuthPartialSuccessResult(step2Exception: final AuthException exception) ||
        AuthFailureResult(:final AuthException exception):
    print('Partially failed: $exception');
  // For exhaustiveness; should never happen.
  case AuthPartialSuccessResult(step1Exception: null, step2Exception: null):
    print('Unknown error occurred');
}

By explicitly specifying the exception types as AuthException, the analyzer stops complaining.

explicit_type_analysis

However, upon compilation, we get a similar error:

bin/sealed_repro.dart:28:71: Error: Variable pattern 'exception' doesn't have the same type or finality in all cases.
    case AuthPartialSuccessResult(step1Exception: final AuthException exception) ||
                                                                      ^^^^^^^^^
explicit_type_compile
@dnys1
Copy link
Contributor Author

dnys1 commented May 12, 2023

Number 1 is reproducible on Dart 3.0.0 and 3.1.0-94.0.dev.

Number 2 is only reproducible on Dart 3.0.0. On 3.1.0-94.0.dev, it compiles fine.

@stereotype441
Copy link
Member

CC @chloestefantsova

@stereotype441 stereotype441 added the area-front-end Use area-front-end for front end / CFE / kernel format related issues. label May 12, 2023
@chloestefantsova
Copy link
Contributor

Here are shorter repros.

Case 1 Reproduction

class A {}
class B extends A {}
class C extends A {
  E1 get foo => new E1();
}
class D extends A {
  E2? get bar => new E2();
  E3? get baz => new E3();
}

class E {}
class E1 extends E {}
class E2 extends E1 {}
class E3 extends E1 {}

test(A x) {
  switch (x) {
    case B _:
      return 1;
    case D(bar: final foo?) ||
            D(baz: final foo?) ||
            C(:final foo):
      return 2;
    case D(bar: null, baz: null):
      return 3;
  }
}

Case 2 Reproduction

class A {}
class B extends A {}
class C extends A {
  E1 get foo => new E1();
}
class D extends A {
  E2? get bar => new E2();
  E3? get baz => new E3();
}

class E {}
class E1 extends E {}
class E2 extends E1 {}
class E3 extends E1 {}

test(A x) {
  switch (x) {
    case B _:
      return 1;
    case D(bar: final E1 foo) ||
            D(baz: final E1 foo) ||
            C(:final E1 foo):
      return 2;
    case D(bar: null, baz: null):
      return 3;
  }
}

Currently both the CFE and the Analyzer report the mismatching type or finality on the first one and allow the second one. @stereotype441, since the behavior of the tools is similar, could this issue be a part of the shared analysis?

@stereotype441
Copy link
Member

@chloestefantsova thanks for the shorter repros!

In the main development branch, both the analyzer and CFE report an error for case 1 and no error for case 2. This is correct behaviour, because all the declarations of foo are required to have the same type, whether the type is inferred or explicit; in case 1, the inferred types are different (E2, E3, and E1), so it's correct to report an error.

In the stable branch, the analyzer behaviour is correct, however the CFE reports an error for both case 1 and case 2. So it looks like the correct behaviour has been implemented in the CFE, but it needs to be cherry-picked to stable.

Bisecting, I found that the correct behaviour was implemented in b9b1fdf. I will submit a cherry-pick request.

@stereotype441
Copy link
Member

Cherry-pick request is here: #52488

@chloestefantsova
Copy link
Contributor

Cherry-pick request is here: #52488

Thank you for finding the root of it, @stereotype441! I've +1'd the cherry pick CL.

copybara-service bot pushed a commit that referenced this issue May 24, 2023
Fixes: #52488
Cherry-pick: https://dart-review.googlesource.com/c/sdk/+/297640
Change-Id: Ib46129a45599ab2e7090e6666bfe07837555eeba
Bug: #52373
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/305042
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
@stereotype441
Copy link
Member

As of 10b909a, the cherry-pick has landed on the stable branch. Assuming no problems arise, it should go out as part of the next stable release, probably next Wednesday.

@dnys1
Copy link
Contributor Author

dnys1 commented May 24, 2023

Thank you @stereotype441 @chloestefantsova! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-front-end Use area-front-end for front end / CFE / kernel format related issues.
Projects
None yet
Development

No branches or pull requests

3 participants