This project is read-only.
1
Vote

Operator "true" and operator "false" not reflected in conversion

description

Consider class Foo which provides user-defined true and false operators as well as user-defined conversion to class Bar:
class Foo
{
    public static bool operator true(Foo foo) { return true; }
    public static bool operator false(Foo foo) { return false; }
    public static implicit operator Bar(Foo foo) { return new Bar(); }
}

class Bar { }

class TestClass
{
    public static void Main()
    {
        var foo = new Foo();
        if (foo) { }
        else { }
        Test(foo);
    }

    public static void Test(Bar bar) { }
}
Given the syntax tree tree and semantic model model corresponding to this program, I examine the invocation expression as follows:
var invocExpr = tree
    .GetCompilationUnitRoot()
    .DescendantNodes()
    .OfType<InvocationExpressionSyntax>()
    .First();
var expr = invocExpr.ArgumentList.Arguments.First().Expression;
var conversion = model.GetConversion(expr);
Console.WriteLine(conversion);
Console.WriteLine(conversion.MethodSymbol);
This outputs ImplicitUserDefined and Foo.implicit operator Bar(Foo). For the if statement, I do the following:
var ifStmt = tree
    .GetCompilationUnitRoot()
    .DescendantNodes()
    .OfType<IfStatementSyntax>()
    .First();
var condition = ifStmt.Condition;
var conversion = model.GetConversion(condition);
Console.WriteLine(conversion);
Console.WriteLine(conversion.MethodSymbol);
This yields Identity and null.

There does not seem to be any way to obtain the conversion information including resolved method symbol for the true/false operator. I would expect this information to be obtainable from the Conversion object.

comments

EricLippert wrote Sep 16, 2014 at 11:51 PM

When writing the binder we made an explicit decision to suppress the operator_true from the semantic model; see Binder_Statements.cs:
// Consider op_true to be compiler-generated so that it doesn't appear in the semantic model.
// UNDONE: If we decide to expose the operator in the semantic model, we'll have to remove the 
// WasCompilerGenerated flag (and possibly suppress the symbol in specific APIs).
return new BoundUnaryOperator(node, signature.Kind, resultOperand, ConstantValue.NotAvailable, signature.Method, resultKind, originalUserDefinedOperators, signature.ReturnType)
{
  WasCompilerGenerated = true
};
I have no memory of why we made this decision, but I regret it now. It would be great if we could have this information available, either in the Conversion object or via some other mechanism.

EricLippert wrote Sep 17, 2014 at 10:23 PM

It is possible to work around the problem, but vexing to have to do so.

Suppose we have an IfStatementSyntax and have obtained the condition and a semantic model. We can discover that there will be an operator-true invocation by noticing that the converted type of the condition is not bool. The converted type, oddly enough will be to the formal parameter type of the operator, which is all the information we need to find the operator. A sketch of a possible workaround follows:
            var convertedType = model.GetTypeInfo(condition).ConvertedType;
            if (convertedType == null)
                return null;

            if (convertedType.SpecialType == SpecialType.System_Boolean)
                return null;

            var declaringType = convertedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T ?
                ((INamedTypeSymbol)convertedType).TypeArguments[0] :
                convertedType;

            var op = declaringType
                .GetMembers()
                .OfType<IMethodSymbol>()
                .Where(m => m.MethodKind == MethodKind.UserDefinedOperator)
                .Where(m => m.Name == "op_True")
                .Where(m => m.Parameters[0].Type == convertedType)
                .FirstOrDefault();

            return op;