Skip to content

Unexpected nullability diagnostics for lambda return type inference #30964

@AlekseyTs

Description

@AlekseyTs
        [Fact]
        public void LambdaReturnValue_NestedNullability_Ref()
        {
            var source =
@"delegate ref V D<T, U, V>(ref T t, ref U u);
interface I<T> { }
interface IIn<in T> { }
interface IOut<out T> { }
class C
{
    static V F<T, U, V>(D<T, U, V> d) => throw null;
    static void G(bool b)
    {
        // I<object>
        F((ref I<object?> a1, ref I<object?> b1) => { if (b) return ref a1; return ref b1; });
        F((ref I<object?> a2, ref I<object> b2) => { if (b) return ref a2; return ref b2; });
        F((ref I<object> a3, ref I<object?> b3) => { if (b) return ref a3; return ref b3; });
        F((ref I<object> a4, ref I<object> b4) => { if (b) return ref a4; return ref b4; });
        // IIn<object>
        F((ref IIn<object?> c1, ref IIn<object?> d1) => { if (b) return ref c1; return ref d1; });
        F((ref IIn<object?> c2, ref IIn<object> d2) => { if (b) return ref c2; return ref d2; });
        F((ref IIn<object> c3, ref IIn<object?> d3) => { if (b) return ref c3; return ref d3; });
        F((ref IIn<object> c4, ref IIn<object> d4) => { if (b) return ref c4; return ref d4; });
        // IOut<object>
        F((ref IOut<object?> e1, ref IOut<object?> f1) => { if (b) return ref e1; return ref f1; });
        F((ref IOut<object?> e2, ref IOut<object> f2) => { if (b) return ref e2; return ref f2; });
        F((ref IOut<object> e3, ref IOut<object?> f3) => { if (b) return ref e3; return ref f3; });
        F((ref IOut<object> e4, ref IOut<object> f4) => { if (b) return ref e4; return ref f4; });
    }
}";
            var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
            // https://github.com/dotnet/roslyn/issues/30432: Report warnings for combinations of `IIn<object?>` and `IIn<object!>`
            // and combinations of  `IOut<object?>` and `IOut<object!>`.
            comp.VerifyDiagnostics(
                // (12,11): warning CS8622: Nullability of reference types in type of parameter 'a2' of 'lambda expression' doesn't match the target delegate 'D<I<object>, I<object>, I<object>>'.
                //         F((ref I<object?> a2, ref I<object> b2) => { if (b) return ref a2; return ref b2; });
                Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(ref I<object?> a2, ref I<object> b2) => { if (b) return ref a2; return ref b2; }").WithArguments("a2", "lambda expression", "D<I<object>, I<object>, I<object>>").WithLocation(12, 11),
                // (12,11): warning CS8622: Nullability of reference types in type of parameter 'b2' of 'lambda expression' doesn't match the target delegate 'D<I<object>, I<object>, I<object>>'.
                //         F((ref I<object?> a2, ref I<object> b2) => { if (b) return ref a2; return ref b2; });
                Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(ref I<object?> a2, ref I<object> b2) => { if (b) return ref a2; return ref b2; }").WithArguments("b2", "lambda expression", "D<I<object>, I<object>, I<object>>").WithLocation(12, 11),
                // (13,11): warning CS8622: Nullability of reference types in type of parameter 'a3' of 'lambda expression' doesn't match the target delegate 'D<I<object>, I<object>, I<object>>'.
                //         F((ref I<object> a3, ref I<object?> b3) => { if (b) return ref a3; return ref b3; });
                Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(ref I<object> a3, ref I<object?> b3) => { if (b) return ref a3; return ref b3; }").WithArguments("a3", "lambda expression", "D<I<object>, I<object>, I<object>>").WithLocation(13, 11),
                // (13,11): warning CS8622: Nullability of reference types in type of parameter 'b3' of 'lambda expression' doesn't match the target delegate 'D<I<object>, I<object>, I<object>>'.
                //         F((ref I<object> a3, ref I<object?> b3) => { if (b) return ref a3; return ref b3; });
                Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(ref I<object> a3, ref I<object?> b3) => { if (b) return ref a3; return ref b3; }").WithArguments("b3", "lambda expression", "D<I<object>, I<object>, I<object>>").WithLocation(13, 11));
            comp.VerifyTypes();
        }

Note that the diagnostic compiler reports is about nullability of parameter types, but those cannot have a mismatch because the types are explicitly specified in source code and flow into different and completely unrelated type parameters. Judging by the delegate types mentioned by the messages, the nullability information is lost in it and it is in fact set to oblivious (checked under debugger). It looks like there is a problem with inferring nullability for return type due to the conditional operator in the body of the lambdas and I would expect to see warnings about that instead.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions