Skip to content

Champion: Target-typed switch expression (16.3, Core 3) #2389

Open
@gafter

Description

@gafter
Member
  • Proposal added (below)
    Discussed in LDM 2019-04-22
    Decision in LDM (Approved for C# 8)
    Finalized (done, rejected, inactive)
    Spec'ed

@333fred asked me

Given the following code, we cannot determine a best type for the switch expression and the code does not compile. Why can't we determine that the best type is A, given that's the target type of the expression?

class A
{
}

class B : A
{
}

class C : A
{
}

class D
{
    void M()
    {
        A a = null;
        a = a switch
        {
            B b => b,
            C c => c,
            _ => throw new System.Exception(),
        };
    }
}

Casting one branch to A fixes the code

My initial reaction was

We use the same inference algorithm for new []{ b, c } and () => { if (b) return b; else return c; } and c ? b : c and M(b, c) where T M<T>(T x, T y).  The general philosophy is that we avoid inferring a type that wasn't one of the input types.  What if they had a common interface but not a common base class?  What if they had two common interfaces?  We just don't do that. We use "target type" only for certain conversions.  We don't have a "conversion from array creation" or "conversion from conditional expression" and similarly we won't have a "conversion from switch expression".

However, my answer was not satisfactory to either @333fred or myself.

This is a proposal to "do that".

Proposal

Currently, a switch expression is specified to have a type that is the inferred common type of the expressions in the switch arms. It is (currently) required that there be such a common type.

Instead, I propose

  1. We attempt to infer a common type among the arms of the switch expression. If there is such a common type, that is the (natural) type of the switch expression.
  2. There is a switch expression conversion (a new implicit conversion-from-expression) from a switch expression to the type T if there is an implicit conversion from every arm's value expression to T.
  3. Let T be the type of the switch expression, either the target type of the switch expression conversion, or if it was not subject to such a conversion the expression's natural type. It is an error if T does not exist. It is an error if a switch arm's expression cannot be implicitly converted to T.

Activity

self-assigned this
on Apr 4, 2019
CyrusNajmabadi

CyrusNajmabadi commented on Apr 4, 2019

@CyrusNajmabadi
Member

I really like this. But i definitely beg of you to do the same for x ? y : z at the same time if you do this :)

YairHalberstadt

YairHalberstadt commented on Apr 4, 2019

@YairHalberstadt
Contributor

Is there any reason not to do this generally, for all expressions?

Thaina

Thaina commented on Apr 4, 2019

@Thaina

If I remember correctly this feature was already requested for x ? y : z along with return type of linq and should work with struct/nullable but I don't remember the issue about it

bool b;
var i = b ? 0 : null; // i become Nullable<int>
Joe4evr

Joe4evr commented on Apr 4, 2019

@Joe4evr
Contributor
DavidArno

DavidArno commented on Apr 4, 2019

@DavidArno

I like this idea, as long as it works with interfaces, not just inherited types:

interface I
{
}

class B : I
{
}

class C : I
{
}

class D
{
    void M()
    {
        I a = null;
        a = a switch
        {
            B b => b,
            C c => c,
            _ => throw new System.Exception(),
        };
    }
}
spydacarnage

spydacarnage commented on Apr 4, 2019

@spydacarnage
DavidArno

DavidArno commented on Apr 4, 2019

@DavidArno

An interesting point related to this was raised in #2387. To clarify, @gafter, when you talk of "target typed", are you referring to the type of the target of the expression? For example,

int? x = y switch {}

would this mean that if that switch were a "target typed switch", then the type of x, int?, would be used? Or would the type be inferred from the arms of the switch? In other words, would the following work:

class D
{
    void M()
    {
        A a = null;
        var a2 = a switch
        {
            B b => b,
            C c => c,
            _ => null,
        };
    }
}

where the type of the switch is inferred from a common type shared by B, C and null and thus the type of a2 is then inferred from the switch? Or would var not work here as the switch needs to know the type of a2 in advance?

alrz

alrz commented on Apr 4, 2019

@alrz
Member

the same for x ? y : z at the same time

Also useful in null-coalescing operator (from #877)

SyntaxNode n = expressionSyntax ?? statementSyntax;

Casting either of operands in any of these should be unnecessary.

CyrusNajmabadi

CyrusNajmabadi commented on Apr 4, 2019

@CyrusNajmabadi
Member

@alrz good point. I think the general idea of like to see investigated is that if we're in a position where there is some Target-type, and if you're using one of the 'branchy' expressions (i.e. switch-exoression, ?:, ??), and if that expression doesn't produce a common type, then Target-type the branches.

Both ?: and ?? can be thought of as abbreviations of the generalized switch-expression, so it makes sense to me to apply this to them. This would also hold for me in the future for any other construct like this

gafter

gafter commented on Apr 5, 2019

@gafter
MemberAuthor

@DavidArno That example would not work. Either there has to be a common type (as currently specified), or there has to be a target type. That example has neither.

@spydacarnage Yes, this would work in a return statement, on the right-hand-side of an assignment statement, as an argument to a method call (even if there are different overloads with different parameter types), and even for a switch expression that is inside another switch expression. There are lots of places where there is a target type.

spydacarnage

spydacarnage commented on Apr 5, 2019

@spydacarnage

@gafter That's excellent. I can't wait :-)

31 remaining items

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @DavidArno@gulshan@Thaina@Joe4evr@333fred

      Issue actions

        Champion: Target-typed switch expression (16.3, Core 3) · Issue #2389 · dotnet/csharplang