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

Treat if-case pattern test result as booleans #3152

Closed
Nhowka opened this issue Jun 16, 2023 · 2 comments
Closed

Treat if-case pattern test result as booleans #3152

Nhowka opened this issue Jun 16, 2023 · 2 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@Nhowka
Copy link

Nhowka commented Jun 16, 2023

If-case statements have been a huge time-saver when deconstructing JSON values, but I was a little surprised when I couldn't mix a regular boolean test with a pattern test.

This happened when I was checking for the status of a resource in a REST API. I tried the following:

if (response.statusCode == 200 && jsonDecode(response.body) case {'active': true}) {
    // Continue with valid flow
}

At first, it returned a warning that was somewhat cryptic at the time, The matched value type 'bool' can never match the required type 'Map<Object?, Object?>'. By having other eyes on the code, I was told that it was being parsed as (response.statusCode == 200 && jsonDecode(response.body)) case {'active': true}, which made the error understandable. && returns a bool, and that bool was being tested but using response.statusCode == 200 && (jsonDecode(response.body) case {'active': true}) only made things break more.

Proposal

We could handle the pattern test as if it were a boolean with some restrictions. This should also only be permitted in a context block where the possible captured variables are. That restriction should prevent the usage of variables not initialized by failing the pattern.

bool test = jsonDecode(response.body) case {'active': true, 'value': String value};
print('Response had value $value'); // Use of value without checking for the test result shouldn't happen

But we could accept it where a boolean would be expected.

if (response.statusCode == 200 &&
      (jsonDecode(response.body) case {'active': true, 'value': String value})) {
     print('Response had value $value'); // Here value is guaranteed to be initialized
}

We could also accept it in a when guard, being it on if-case or also in switch:

if (response case Response(statusCode: 200, :var body) when
      jsonDecode(body) case {'active': true, 'value': String value}) {}

String? extractValue(Response response) => switch(response) {
    Response(statusCode: 200, :var body) when
      jsonDecode(body) case {'active': true, 'value': String value}
        => value,
    _ => null
  };

As this feature also permits some calculations to happen over the captured variables that are exposed to when, it could prevent some code duplication or hard-to-reason code flow as we would enter in a case that we don't want when it should continue to the next one.

Alternative

In cases where the calculation has no arguments, it could be provided via extensions:

extension JsonBody on Response {
  dynamic get json => jsonDecode(body);
}

if (response case Response(statusCode: 200, json: {'active': true, 'value': String value}) {}

I couldn't find an equivalent alternative for cases where an argument is needed.

@Nhowka Nhowka added the feature Proposed language feature that solves one or more problems label Jun 16, 2023
@lrhn
Copy link
Member

lrhn commented Jun 17, 2023

Sounds related to #3059 (case-checks in conditional expression, and then why not any expression) and/or #2433 (allow other selectors than getters in object patterns).

The json getter is actually what I'd still recommend, I'd just make it Object? toJson() => jsonDecode(body) with #2433:

if (response case Response(statusCode: 200, toJson(): {...}) { ... }

If we allow (expr case pattern when expr) (optional when clause, outer parentheses needed in most contexts other than <expression>), then it would be:

if (response.statusCode == 200 && (jsonDecode(response.body) case { ... })) { ... }

The parentheses are important since we need to know where the switched-on-value expression starts.
We need the parentheses afterwards too, to correctly parse

if ((something case List(:var isEmpty:, :var length)) && isEmpty) { ... }

With no parentheses, the && isEmpty would be parsed as a constant pattern, and can't refer to the isEmpty variable yet.

@Nhowka
Copy link
Author

Nhowka commented Jun 19, 2023

Issues #3059 and #2433 should cover enough to support the proposed syntax.

@Nhowka Nhowka closed this as completed Jun 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

2 participants