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

Proposal: Overlapping pattern variable names in switch #118

Closed
alrz opened this issue Feb 15, 2017 · 12 comments
Closed

Proposal: Overlapping pattern variable names in switch #118

alrz opened this issue Feb 15, 2017 · 12 comments

Comments

@alrz
Copy link
Contributor

alrz commented Feb 15, 2017

Overlapping pattern variable names in switch

Summary

Relax pattern-matching in switch labels to allow overlapping variable names.

Motivation

When we are matching a value against multiple patterns, sometimes we do not care about the exact matching pattern, just that it's matched with either of patterns. Currently this produces an error.

class C {}
class A : C {}
class B : C {}

switch (obj) {
  case A x:
  case B x:
     Console.WriteLine($"'{obj}' is of type '{nameof(A)}' or '{nameof(B)}'");
     break;
}

This proposal aims to make that possible.

Proposal

The real issue is that what is the type of x? As the resulting value will be of either types A or B, we can infer the most specific common type for x i.e. C.

So the above code will be roughly equivalent to the following:

C x = (C)(obj as A) ?? obj as B;
if (x != null) {
   Console.WriteLine($"'{obj}' is of type '{nameof(A)}' or '{nameof(B)}'");
}

Note: Currently ?? operator does not infer the most specific common type so we will need to upcast the result of either side.

Alternatives

Ported from dotnet/roslyn#6235

While this does not need any new syntax, yet it cannot be nested in recursive patterns. As an alternative we can introduce OR-patterns which is also usable in is expressions.

// is expression
obj is A x or B x

// switch statement
switch (obj) {
  case A x or B x:
     // ...
     break;
}
  
// as a nested pattern     
switch ((obj, 1)) {
  case (A x or B x, 1):
    break;  
}

Variations

In dotnet/roslyn#6235, an AND pattern is also proposed that matches a value against multiple patterns and succeeds only if none of patterns fail to match.

o is Interface1 i and Interface2 j

Drawbacks

Since that would be an operator, we will need to specify a precedence and possibly introduce "parenthesized patterns" which will add further complexities to the pattern syntax.

Note: this would not be applied to the main proposal as it is merely a relaxation of existing syntax.

@jnm2
Copy link
Contributor

jnm2 commented Feb 15, 2017

In the top example, if there's a chance that x could be typed as A | B rather than the first common type C, I'd prefer A | B. Meaning, you can only use members shared by both A and B because it could be either.

This would be consistent with intersection types where if x is A and x is B, x would be typed A & B. You could use the union of the properties and methods of A and B because it is both.

@jnm2
Copy link
Contributor

jnm2 commented Feb 15, 2017

Would using C now prevent us from using intersection types down the road?

@alrz
Copy link
Contributor Author

alrz commented Feb 15, 2017

Not sure but supposedly if an intersection of A and B is implicitly inherited from C it probably wouldn't be a breaking change.

@sharwell
Copy link
Member

I don't believe the use of C would prevent us from using intersection types down the road. However, you could end up with some strange edge cases surrounding interfaces. For example:

class A : List<int> {}
class B : Collection<int> {}

// CASE 1
switch (obj) {
  case A x:
  case B x:
     // Would x be object or IList<int>?
     break;
}

// CASE 2
switch (obj) {
  case A x:
  case IList<int> x:
     // Would x be object or IList<int>?
     break;
}

If you ever end up in a case where x could be an interface type instead of object, then intersection types could result in a breaking change down the road:

class A : IDisposable {
  void IDisposable.Dispose() { }
}
class B : IDisposable {
  void IDisposable.Dispose() { }
}

// CASE 3
switch (obj) {
  case A x:
  case B x:
    // Works for x as IDisposable, but not x as A|B
    x.Dispose()
    break;
}

@alrz
Copy link
Contributor Author

alrz commented Feb 15, 2017

Would x be object or IList?

I'm relying on #33 for the definition of "most specific common type". So the answer depends on the rules outlined in that proposal. Currently it doesn't consider interfaces at all.

Works for x as IDisposable, but not x as A|B

If intersections defined such that "as long as both types inherited from X, the resultant intersection type will be implicitly inherited from X", that would work. However, your example still requires #33 to also consider interfaces to begin with.

@YaakovDavis
Copy link

@alrz

The proposal is interesting mostly in scenarios where one wants to test for some, but not all of the derived types.

Say B, C & D derive from A.
If one wanted to test for either B, C or D, he could simple write

case A a

On the other hand, when testing only for B or C, this proposal becomes handy.

I suggest adding another derived type to your example above to highlight this aspect.

@svick
Copy link
Contributor

svick commented Mar 10, 2017

So this is basically a shorter and slightly more efficient way to write case C x when x is A || x is B?

@alrz
Copy link
Contributor Author

alrz commented Mar 10, 2017

@svick Consider the example from spec draft:

This would avoid the code duplicated in some case bodies,

Expr Simplify(Expr e)
{
  switch (e) {
    case Mult(Const(1), var x): 
    case Mult(var x, Const(1)):
        return Simplify(x);
    // ...
    case Add(Const(0), var x):
    case Add(var x, Const(0)):
        return Simplify(x);
  }
}

@alrz
Copy link
Contributor Author

alrz commented Mar 10, 2017

That example doesn't need the "most specific common type" though, "identical types" would be sufficient.

@gulshan
Copy link

gulshan commented Mar 21, 2017

What is "identical types" by the way?

@alrz
Copy link
Contributor Author

alrz commented Mar 21, 2017

It's a special case of a common type where x is of the the same type in every case clause, so the most specific common type would be the type of x itself.

@alrz
Copy link
Contributor Author

alrz commented Jan 7, 2020

Closing in favor of championed issue #1350

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

7 participants