Skip to content

ConsumeContextConverterGenerator can order an object arm before an interface arm (CS8510), masked by #593 #594

Description

@alexeyzimarev

Describe the bug
Since #592, ConsumeContextConverterGenerator orders switch arms by a specificity rank: the number of base classes plus all interfaces (GetSpecificity, L144–L149). The rank relies on a type having strictly more supertypes than any of its supertypes.

That does not hold for object versus an interface with no base interfaces. An interface converts to object, but object is not in its BaseType chain or in AllInterfaces, so both get rank 0. Ties keep discovery order, so an object arm discovered first is emitted before the interface arm and subsumes it (CS8510).

This is not reachable today: object as a message type currently produces global::object, which fails earlier with a syntax error (#593). It becomes a real build break as soon as #593 is fixed, so the two are best fixed together.

To Reproduce

  1. Apply a fix for ConsumeContextConverterGenerator emits invalid code for keyword message types (global::string, CS1041) #593 (for example, format type names without UseSpecialTypes).
  2. Use object as a message type before an interface that has no base interfaces:
    public interface IFoo;
    
    public static class Usages {
        public static void Any(IMessageConsumeContext<object> ctx) { }
    
        public static void Foo(IMessageConsumeContext<IFoo> ctx) { }
    }
  3. The generated converter is
    global::System.Object => new MessageConsumeContext<global::System.Object>(context),
    global::Foo.IFoo => new MessageConsumeContext<global::Foo.IFoo>(context), // CS8510

Verified with CSharpGeneratorDriver against dev at 9179885 with a temporary local fix for #593 applied: the arms come out in that order and the second one is reported as CS8510. Without that fix the same order is emitted, but the global::object syntax error is reported first.

Expected behavior
An object arm is always emitted last, after every other type.

Every type other than object converts to object, so the rank can count it for interfaces too. One way:

static int GetSpecificity(ITypeSymbol symbol) {
    // Interfaces convert to object although it is not among their base types
    var count = symbol.AllInterfaces.Length + (symbol.TypeKind == TypeKind.Interface ? 1 : 0);

    for (var baseType = symbol.BaseType; baseType != null; baseType = baseType.BaseType) count++;

    return count;
}

The strict ordering still holds: a class implementing I counts object, I and everything I counts, so it stays above I; a derived interface stays above its base interface; and object is the only type left at rank 0.

Checked locally with this change plus the #593 fix applied: the scenario above compiles with the IFoo arm emitted first, and the existing ConsumeContextConverterGeneratorTests still pass.

Additional context
Found while working on #589 / #592. Low priority on its own since object message types are unusual, but it should be covered by a test when #593 is fixed, otherwise that fix trades one build error for another in this scenario.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions