language icon indicating copy to clipboard operation
language copied to clipboard

`if case` should be negatable

Open feinstein opened this issue 1 year ago • 6 comments

Sorry if this issue already exists, but I couldn't find anything about this is the search.

I think it would be very useful if we could negate a if-case statement. In my code I wanted to execute something only if a pattern match happened, but I couldn't, so the only other option was using an else with an empty if-case body, which looked very ugly.

What I wanted:

// test case for when a particular go_router route is not the current route:
final matches = router.routerDelegate.currentConfiguration.matches;
if (matches case! [RouteMatchBase(matchedLocation: '/home'), ImperativeRouteMatch(matchedLocation: '/details')]) {
  fail('No route found');
}

What I can do now:

final matches = router.routerDelegate.currentConfiguration.matches;
if (matches case [RouteMatchBase(matchedLocation: '/home'), ImperativeRouteMatch(matchedLocation: '/details')]) {
  // Empty body because I can't negate the if-case
} else {
  fail('No route found');
}

feinstein avatar Jun 01 '24 10:06 feinstein

The main reason there is no negation of a pattern match, is that most pattern matches include capture variables, which are only available on the positive branch. You have a pattern with no captures, which is why it makes sense to only care about the false branch.

I'm not sure that comes up often enough that it warrants specialized syntax. You can also use a switch:

  switch (matches) {
    case [RouteMatchBase(matchedLocation: '/home'), ImperativeRouteMatch(matchedLocation: '/details')]: break
    default: fail('No route found');
  }

I'd consider it more likely (not highly likely, but more) that we'd allow (expression case pattern) and (expression case pattern when expression) as an expression, fully parenthesized, and not just as an entire if condition, #3062. That would allow you to write:

  if (!(matches case [RouteMatchBase(matchedLocation: '/home'), ImperativeRouteMatch(matchedLocation: '/details')])) {
    default: fail('No route found');
  } 

lrhn avatar Jun 01 '24 12:06 lrhn

Wouldn't it run into the same issue?

I think we would expect a runtime exception or a compile time warning, if we tried to access a variable inside a pattern that was not matched.... Or a limitation that case! can't define variables for pattern matching.

feinstein avatar Jun 01 '24 12:06 feinstein

See #1548

ghost avatar Jun 01 '24 15:06 ghost

C# has similar pattern matching features to dart, except they also have a not pattern:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#logical-patterns

if (input is not null)
{
    // ...
}

And pattern matches are also expressions:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/is

static bool IsFirstFridayOfOctober(DateTime date) =>
    date is { Month: 10, Day: <=7, DayOfWeek: DayOfWeek.Friday };

mmcdon20 avatar Jun 01 '24 15:06 mmcdon20

You can find this syntax suggestion in issue #2537.

The scoping rules of case! would be similar to Java patterns: image

Java doesn't have an instanceof! / is not / is! operator, I belive most people would prefer if (shape instanceof! Rectangle) over if (!(shape instanceof Rectangle)), the same idea applies to if (shape case! Rectangle(final x, final y)) over if (!(shape case Rectangle(final x, final y))). Thus, if (a case! b) and if (!(a case b)) need to coexist.

Wdestroier avatar Jun 01 '24 19:06 Wdestroier

@Wdestroier : java doesn't allow shadowing. If you replace "Rectangle r" by "Rectangle s", the compiler will complain. The entire logic of negative conditions in java is based on this. In dart, shadowing is quite common in "case" conditions.

What happens in java is this:

    public static void main(String args[]) {
        var name="abc";
        if (!(name instanceof String s)) {
            System.out.println("name is not an instance of String");
            return;
        } 
        System.out.println(s); 
    }

This works. However, if you remove "return", the compiler will flag the last line: error: cannot find symbol s The same will happen if you add an "else" block. This is quite logical, and if dart can disallow shadowing in negative conditions, it may work in dart, too

(It should be case! - to rhyme with is!, and to avoid an awkward pair of parentheses)

ghost avatar Jun 01 '24 22:06 ghost

Thanks @tatumizer. Yes, I think this request is essentially a duplicate of #1548, or at least close enough that merging the issues is more helpful than not.

munificent avatar Jul 11 '24 23:07 munificent