csharpstandard icon indicating copy to clipboard operation
csharpstandard copied to clipboard

13.8.3 The switch statement - edit loses base type text

Open Nigel-Ecma opened this issue 9 years ago • 12 comments

In revising the paragraph defining governing type:

The governing type of a switch statement … Otherwise, exactly one user-defined implicit conversion operator (§13.4) shall exist from the type of the switch expression or a base type of this type to one of the following possible governing types …

into a bulleted list which handles the inclusion of nullable value types the Proposal has lost the base type text above:

… or a base type of this type …

producing the bullet:

• Otherwise, exactly one user-defined implicit conversion shall exist from the type of the switch expression to one of the following possible governing types …

The reason for this deletion is not immediately apparent, and it is quite possible this is a regression.

Proposal:

Assuming this is a regression, add the lost phrase into the Proposal's bulleted item.

Nigel-Ecma avatar Mar 08 '16 01:03 Nigel-Ecma

We think we should probably reject both changes in that bullet, leaving:

Otherwise, exactly one user-defined implicit conversion operator (§12.5) shall exist from the type of the switch expression or a base type of this type to one of the following possible governing types: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or, a nullable value type corresponding to one of those types.

jskeet avatar Mar 15 '16 20:03 jskeet

// NOTE: For (2) we use an approach similar to native compiler's approach, but call into the common code for analyzing user defined implicit conversions.
// NOTE:    (a) Compute the set of types D from which user-defined conversion operators should be considered by considering only the source type.
// NOTE:    (b) Instead of computing applicable user defined implicit conversions U from the source type to a specific target type,
// NOTE:        we compute these from the source type to ANY target type.
// NOTE:    (c) From the conversions in U, select the most specific of them that targets a valid switch governing type

// SPEC VIOLATION: Because we use the same strategy for computing the most specific conversion, as the Dev10 compiler did (in fact
// SPEC VIOLATION: we share the code), we inherit any spec deviances in that analysis. Specifically, the analysis only considers
// SPEC VIOLATION: which conversion has the least amount of lifting, where a conversion may be considered to be in unlifted form,
// SPEC VIOLATION: half-lifted form (only the argument type or return type is lifted) or fully lifted form. The most specific computation
// SPEC VIOLATION: looks for a unique conversion that is least lifted. The spec, on the other hand, requires that the conversion
// SPEC VIOLATION: be *unique*, not merely most use the least amount of lifting among the applicable conversions.

// SPEC VIOLATION: This introduces a SPEC VIOLATION for the following tests in the native compiler:

// NOTE:    // See test SwitchTests.CS0166_AggregateTypeWithMultipleImplicitConversions_07
// NOTE:    struct Conv
// NOTE:    {
// NOTE:        public static implicit operator int (Conv C) { return 1; }
// NOTE:        public static implicit operator int (Conv? C2) { return 0; }
// NOTE:        public static int Main()
// NOTE:        {
// NOTE:            Conv? D = new Conv();
// NOTE:            switch(D)
// NOTE:            {   ...

// SPEC VIOLATION: Native compiler allows the above code to compile
// SPEC VIOLATION: even though there are two user-defined implicit conversions:
// SPEC VIOLATION: 1) To int type (applicable in normal form): public static implicit operator int (Conv? C2)
// SPEC VIOLATION: 2) To int? type (applicable in lifted form): public static implicit operator int (Conv C)

// NOTE:    // See also test SwitchTests.TODO
// NOTE:    struct Conv
// NOTE:    {
// NOTE:        public static implicit operator int? (Conv C) { return 1; }
// NOTE:        public static implicit operator string (Conv? C2) { return 0; }
// NOTE:        public static int Main()
// NOTE:        {
// NOTE:            Conv? D = new Conv();
// NOTE:            switch(D)
// NOTE:            {   ...

// SPEC VIOLATION: Native compiler allows the above code to compile too
// SPEC VIOLATION: even though there are two user-defined implicit conversions:
// SPEC VIOLATION: 1) To string type (applicable in normal form): public static implicit operator string (Conv? C2)
// SPEC VIOLATION: 2) To int? type (applicable in half-lifted form): public static implicit operator int? (Conv C)

// SPEC VIOLATION: This occurs because the native compiler compares the applicable conversions to find one with the least amount
// SPEC VIOLATION: of lifting, ignoring whether the return types are the same or not.
// SPEC VIOLATION: We do the same to maintain compatibility with the native compiler.

// (a) Compute the set of types D from which user-defined conversion operators should be considered by considering only the source type.
var d = ArrayBuilder<NamedTypeSymbol>.GetInstance();
ComputeUserDefinedImplicitConversionTypeSet(source, t: null, d: d, useSiteDiagnostics: ref useSiteDiagnostics);

// (b) Instead of computing applicable user defined implicit conversions U from the source type to a specific target type,
//     we compute these from the source type to ANY target type. We will filter out those that are valid switch governing
//     types later.
var ubuild = ArrayBuilder<UserDefinedConversionAnalysis>.GetInstance();
ComputeApplicableUserDefinedImplicitConversionSet(null, source, target: null, d: d, u: ubuild, useSiteDiagnostics: ref useSiteDiagnostics, allowAnyTarget: true);
d.Free();
ImmutableArray<UserDefinedConversionAnalysis> u = ubuild.ToImmutableAndFree();

// (c) Find that conversion with the least amount of lifting
int? best = MostSpecificConversionOperator(conv => conv.ToType.IsValidSwitchGoverningType(isTargetTypeOfUserDefinedOp: true), u);
if (best != null)
{
    return UserDefinedConversionResult.Valid(u, best.Value);
}

gafter avatar Mar 15 '16 21:03 gafter

Neal has taken the job of formalizing what the compiler actually does as specese.

gafter avatar Mar 15 '16 21:03 gafter

struct S
{
    public static implicit operator int?(S x)
    {
        return 0;
    }

    public static implicit operator long(S x)
    {
        return 0;
    }

    static void Main()
    {
        S? x = null;
        switch (x) // No error
        {
        }
    }
}

VladimirReshetnikov avatar Mar 15 '16 21:03 VladimirReshetnikov

The reason for deleting the possibility of conversion from a base type is that is already considered a conversion from the type itself. So that part of the change in the WD is fine, I think.

The rest of the changes can be deferred to a later spec revision.

gafter avatar Jul 20 '16 21:07 gafter

All comments prior to this one were before we removed section 7. I've updated the title only.

jskeet avatar Mar 26 '18 08:03 jskeet

I've labeled this as a bug for now, but it might be clarity really - I haven't delved into the details of what actually still needs changing here. (And it may be subsumed by other switch changes anyway.)

jskeet avatar Jan 08 '21 10:01 jskeet

Assigning to @gafter to investigate before the next meeting.

jskeet avatar Apr 15 '21 14:04 jskeet

C# 7 introduces pattern-matching, by which any type can be used as an input to a switch statement. If the previous language version would have converted the switch expression to some type, then it is so converted (otherwise it isn't). Rather than trying to patch up the C# 6 version of these rules and then rewriting them again for C# 7, I suggest we make the correction when writing up the changes for pattern-matching.

gafter avatar Jun 30 '21 18:06 gafter

I will figure out what we need to do for this.

gafter avatar Jun 05 '23 21:06 gafter

@gafter: Ping for this one too? (I'm trying to take stock of what we really need to do for C# 7...)

jskeet avatar Aug 07 '23 15:08 jskeet

Meeting 2023-09-06: Defer to C# 8.

jskeet avatar Sep 06 '23 20:09 jskeet