Skip to content

Commit

Permalink
Update BoundCall method based on receiver nullability (#31000)
Browse files Browse the repository at this point in the history
  • Loading branch information
cston committed Nov 9, 2018
1 parent 302faec commit ea69e38
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 7 deletions.
30 changes: 23 additions & 7 deletions src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
Expand Up @@ -1951,19 +1951,26 @@ public override BoundNode VisitCall(BoundCall node)
// Note: we analyze even omitted calls
var method = node.Method;
var receiverOpt = node.ReceiverOpt;
TypeSymbolWithAnnotations receiverType = default;

if (receiverOpt != null && method.MethodKind != MethodKind.Constructor)
{
VisitRvalue(receiverOpt);
receiverType = VisitRvalueWithResult(receiverOpt);
// https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null
// after arguments have been visited, and only if the receiver has not changed.
CheckPossibleNullReceiver(receiverOpt);
// Update method based on inferred receiver type: see https://github.com/dotnet/roslyn/issues/29605.
}

// https://github.com/dotnet/roslyn/issues/29605 Can we handle some error cases?
// (Compare with CSharpOperationFactory.CreateBoundCallOperation.)
if (!node.HasErrors)
{
if (!receiverType.IsNull)
{
// Update method based on inferred receiver type.
method = (MethodSymbol)AsMemberOfResultType(receiverType, method);
}

ImmutableArray<RefKind> refKindsOpt = node.ArgumentRefKindsOpt;
(ImmutableArray<BoundExpression> arguments, ImmutableArray<Conversion> conversions) = RemoveArgumentConversions(node.Arguments, refKindsOpt);
ImmutableArray<int> argsToParamsOpt = node.ArgsToParamsOpt;
Expand Down Expand Up @@ -2797,9 +2804,9 @@ private TypeSymbolWithAnnotations GetAdjustedResult(TypeSymbolWithAnnotations ty
return type;
}

private Symbol AsMemberOfResultType(Symbol symbol)
private static Symbol AsMemberOfResultType(TypeSymbolWithAnnotations resultType, Symbol symbol)
{
var containingType = _resultType.TypeSymbol as NamedTypeSymbol;
var containingType = resultType.TypeSymbol as NamedTypeSymbol;
if ((object)containingType == null || containingType.IsErrorType())
{
return symbol;
Expand Down Expand Up @@ -2831,7 +2838,12 @@ private static Symbol AsMemberOfType(NamedTypeSymbol containingType, Symbol symb
{
return AsMemberOfTupleType((TupleTypeSymbol)containingType, symbol);
}
return symbolDef.SymbolAsMember(containingType);
var result = symbolDef.SymbolAsMember(containingType);
if (result is MethodSymbol resultMethod && resultMethod.IsGenericMethod)
{
return resultMethod.Construct(((MethodSymbol)symbol).TypeArguments);
}
return result;
}
containingType = containingType.BaseTypeNoUseSiteDiagnostics;
if ((object)containingType == null)
Expand All @@ -2840,7 +2852,6 @@ private static Symbol AsMemberOfType(NamedTypeSymbol containingType, Symbol symb
}
}
// https://github.com/dotnet/roslyn/issues/29967 Handle other cases such as interfaces.
Debug.Assert(symbolDefContainer.IsInterface);
return symbol;
}

Expand All @@ -2865,6 +2876,8 @@ private static Symbol AsMemberOfTupleType(TupleTypeSymbol tupleType, Symbol symb
return tupleType.GetTupleMemberSymbolForUnderlyingMember(((TuplePropertySymbol)symbol).UnderlyingProperty);
case SymbolKind.Event:
return tupleType.GetTupleMemberSymbolForUnderlyingMember(((TupleEventSymbol)symbol).UnderlyingEvent);
case SymbolKind.Method:
return tupleType.GetTupleMemberSymbolForUnderlyingMember(((TupleMethodSymbol)symbol).UnderlyingMethod);
default:
throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
}
Expand Down Expand Up @@ -3725,7 +3738,7 @@ private void VisitMemberAccess(BoundExpression receiverOpt, Symbol member, bool

if (!member.IsStatic)
{
member = AsMemberOfResultType(member);
member = AsMemberOfResultType(_resultType, member);
// https://github.com/dotnet/roslyn/issues/30598: For l-values, mark receiver as not null
// after RHS has been visited, and only if the receiver has not changed.
CheckPossibleNullReceiver(receiverOpt);
Expand Down Expand Up @@ -4234,13 +4247,16 @@ public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOpera
VisitRvalue(node.ReceiverOpt);
Debug.Assert(!IsConditionalState);
var receiverOpt = node.ReceiverOpt;
var @event = node.Event;
if (!node.Event.IsStatic)
{
@event = (EventSymbol)AsMemberOfResultType(_resultType, @event);
// https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null
// after arguments have been visited, and only if the receiver has not changed.
CheckPossibleNullReceiver(receiverOpt);
}
VisitRvalue(node.Argument);
// https://github.com/dotnet/roslyn/issues/31018: Check for delegate mismatch.
SetResult(node); // https://github.com/dotnet/roslyn/issues/29969 Review whether this is the correct result
return null;
}
Expand Down
Expand Up @@ -28022,6 +28022,66 @@ void Test1(Test? x1)
);
}

[WorkItem(31018, "https://github.com/dotnet/roslyn/issues/31018")]
[Fact]
public void EventAssignment()
{
var source =
@"#pragma warning disable 0067
using System;
class A<T> { }
class B
{
event Action<A<object?>> E;
static void M1()
{
var b1 = new B();
b1.E += F1; // 1
b1.E += F2; // 2
b1.E += F3;
b1.E += F4;
}
static void M2(Action<A<object>> f1, Action<A<object>?> f2, Action<A<object?>> f3, Action<A<object?>?> f4)
{
var b2 = new B();
b2.E += f1; // 3
b2.E += f2; // 4
b2.E += f3;
b2.E += f4;
}
static void M3()
{
var b3 = new B();
b3.E += (A<object> a) => { }; // 5
b3.E += (A<object>? a) => { }; // 6
b3.E += (A<object?> a) => { };
b3.E += (A<object?>? a) => { }; // 7
}
static void F1(A<object> a) { }
static void F2(A<object>? a) { }
static void F3(A<object?> a) { }
static void F4(A<object?>? a) { }
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
// https://github.com/dotnet/roslyn/issues/31018: Report warnings for // 3 and // 4.
comp.VerifyDiagnostics(
// (10,17): warning CS8622: Nullability of reference types in type of parameter 'a' of 'void B.F1(A<object> a)' doesn't match the target delegate 'Action<A<object?>>'.
// b1.E += F1; // 1
Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "F1").WithArguments("a", "void B.F1(A<object> a)", "System.Action<A<object?>>").WithLocation(10, 17),
// (11,17): warning CS8622: Nullability of reference types in type of parameter 'a' of 'void B.F2(A<object>? a)' doesn't match the target delegate 'Action<A<object?>>'.
// b1.E += F2; // 2
Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "F2").WithArguments("a", "void B.F2(A<object>? a)", "System.Action<A<object?>>").WithLocation(11, 17),
// (26,17): warning CS8622: Nullability of reference types in type of parameter 'a' of 'lambda expression' doesn't match the target delegate 'Action<A<object?>>'.
// b3.E += (A<object> a) => { }; // 5
Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(A<object> a) => { }").WithArguments("a", "lambda expression", "System.Action<A<object?>>").WithLocation(26, 17),
// (27,17): warning CS8622: Nullability of reference types in type of parameter 'a' of 'lambda expression' doesn't match the target delegate 'Action<A<object?>>'.
// b3.E += (A<object>? a) => { }; // 6
Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(A<object>? a) => { }").WithArguments("a", "lambda expression", "System.Action<A<object?>>").WithLocation(27, 17),
// (29,17): warning CS8622: Nullability of reference types in type of parameter 'a' of 'lambda expression' doesn't match the target delegate 'Action<A<object?>>'.
// b3.E += (A<object?>? a) => { }; // 7
Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(A<object?>? a) => { }").WithArguments("a", "lambda expression", "System.Action<A<object?>>").WithLocation(29, 17));
}

[Fact]
public void AsOperator_01()
{
Expand Down Expand Up @@ -36655,6 +36715,22 @@ static void M(string x, string? y)
Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "(y, y)").WithArguments("(string?, string?)", "(string, string?)", "item", "void List<(string, string?)>.Add((string, string?) item)").WithLocation(11, 13));
}

[Fact]
public void Tuple_Method()
{
var source =
@"class C
{
static void F(object? x, object? y)
{
if (x == null) return;
(x, y).ToString();
}
}";
var comp = CreateCompilation(source, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics();
}

[Fact]
public void Tuple_OtherMembers_01()
{
Expand Down Expand Up @@ -51950,5 +52026,211 @@ static void Main()
var comp = CreateCompilation(source2, new[] { ref1.WithEmbedInteropTypes(true), CSharpRef }, options: WithNonNullTypesTrue(TestOptions.ReleaseExe));
CompileAndVerify(comp, expectedOutput: "4");
}

[Fact]
public void UpdateFieldFromReceiverType()
{
var source =
@"class A<T> { }
class B<T>
{
internal T F = default!;
}
class Program
{
internal static B<T> Create<T>(T t) => throw null;
static void M1(object x, object? y)
{
var b1 = Create(x);
b1.F = x;
b1.F = y; // 1
}
static void M2(A<object?> x, A<object> y, A<object?> z)
{
var b2 = Create(x);
b2.F = y; // 2
b2.F = z;
}
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (13,16): warning CS8601: Possible null reference assignment.
// b1.F = y; // 1
Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "y").WithLocation(13, 16),
// (18,16): warning CS8619: Nullability of reference types in value of type 'A<object>' doesn't match target type 'A<object?>'.
// b2.F = y; // 2
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "y").WithArguments("A<object>", "A<object?>").WithLocation(18, 16));
}

[Fact]
public void UpdatePropertyFromReceiverType()
{
var source =
@"class A<T> { }
class B<T>
{
internal T P { get; set; } = default!;
}
class Program
{
internal static B<T> Create<T>(T t) => throw null;
static void M1(object x, object? y)
{
var b1 = Create(x);
b1.P = x;
b1.P = y; // 1
}
static void M2(A<object?> x, A<object> y, A<object?> z)
{
var b2 = Create(x);
b2.P = y; // 2
b2.P = z;
}
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (13,16): warning CS8601: Possible null reference assignment.
// b1.P = y; // 1
Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "y").WithLocation(13, 16),
// (18,16): warning CS8619: Nullability of reference types in value of type 'A<object>' doesn't match target type 'A<object?>'.
// b2.P = y; // 2
Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "y").WithArguments("A<object>", "A<object?>").WithLocation(18, 16));
}

[WorkItem(31018, "https://github.com/dotnet/roslyn/issues/31018")]
[Fact]
public void UpdateEventFromReceiverType()
{
var source =
@"#pragma warning disable 0067
delegate void D<T>(T t);
class C<T>
{
internal event D<T> E;
}
class Program
{
internal static C<T> Create<T>(T t) => throw null;
static void M1(object x, D<object> y, D<object?> z)
{
var c1 = Create(x);
c1.E += y;
c1.E += z; // 1
}
static void M2(object? x, D<object> y, D<object?> z)
{
var c2 = Create(x);
c2.E += y; // 2
c2.E += z;
}
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
// https://github.com/dotnet/roslyn/issues/31018: Report warnings.
comp.VerifyDiagnostics();
}

[WorkItem(29605, "https://github.com/dotnet/roslyn/issues/29605")]
[Fact]
public void UpdateMethodFromReceiverType_01()
{
var source =
@"class C<T>
{
internal void F(T t) { }
}
class Program
{
internal static C<T> Create<T>(T t) => throw null;
static void M(object x, object? y, string? z)
{
var c = Create(x);
c.F(x);
c.F(y); // 1
c.F(z); // 2
c.F(null); // 3
}
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (12,13): warning CS8604: Possible null reference argument for parameter 't' in 'void C<object>.F(object t)'.
// c.F(y); // 1
Diagnostic(ErrorCode.WRN_NullReferenceArgument, "y").WithArguments("t", "void C<object>.F(object t)").WithLocation(12, 13),
// (13,13): warning CS8604: Possible null reference argument for parameter 't' in 'void C<object>.F(object t)'.
// c.F(z); // 2
Diagnostic(ErrorCode.WRN_NullReferenceArgument, "z").WithArguments("t", "void C<object>.F(object t)").WithLocation(13, 13),
// (14,13): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter.
// c.F(null); // 3
Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(14, 13));
}

[WorkItem(29605, "https://github.com/dotnet/roslyn/issues/29605")]
[Fact]
public void UpdateMethodFromReceiverType_02()
{
var source =
@"class A<T> { }
class B<T>
{
internal void F<U>(U u) where U : A<T> { }
}
class Program
{
internal static B<T> Create<T>(T t) => throw null;
static void M1(object x, A<object> y, A<object?> z)
{
var b1 = Create(x);
b1.F(y);
b1.F(z); // 1
}
static void M2(object? x, A<object> y, A<object?> z)
{
var b2 = Create(x);
b2.F(y); // 2
b2.F(z);
}
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (13,9): warning CS8631: The type 'A<object?>' cannot be used as type parameter 'U' in the generic type or method 'B<object>.F<U>(U)'. Nullability of type argument 'A<object?>' doesn't match constraint type 'A<object>'.
// b1.F(z); // 1
Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, "b1.F").WithArguments("B<object>.F<U>(U)", "A<object>", "U", "A<object?>").WithLocation(13, 9),
// (18,9): warning CS8631: The type 'A<object>' cannot be used as type parameter 'U' in the generic type or method 'B<object?>.F<U>(U)'. Nullability of type argument 'A<object>' doesn't match constraint type 'A<object?>'.
// b2.F(y); // 2
Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, "b2.F").WithArguments("B<object?>.F<U>(U)", "A<object?>", "U", "A<object>").WithLocation(18, 9));
}

[WorkItem(29605, "https://github.com/dotnet/roslyn/issues/29605")]
[Fact]
public void UpdateMethodFromReceiverType_03()
{
var source =
@"class C<T>
{
internal static T F() => throw null;
}
class Program
{
internal static C<T> Create<T>(T t) => throw null;
static void M(object x)
{
var c1 = Create(x);
c1.F().ToString();
x = null;
var c2 = Create(x);
c2.F().ToString();
}
}";
var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue());
comp.VerifyDiagnostics(
// (11,9): error CS0176: Member 'C<object>.F()' cannot be accessed with an instance reference; qualify it with a type name instead
// c1.F().ToString();
Diagnostic(ErrorCode.ERR_ObjectProhibited, "c1.F").WithArguments("C<object>.F()").WithLocation(11, 9),
// (12,13): warning CS8600: Converting null literal or possible null value to non-nullable type.
// x = null;
Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(12, 13),
// (14,9): error CS0176: Member 'C<object>.F()' cannot be accessed with an instance reference; qualify it with a type name instead
// c2.F().ToString();
Diagnostic(ErrorCode.ERR_ObjectProhibited, "c2.F").WithArguments("C<object>.F()").WithLocation(14, 9));
}
}
}

0 comments on commit ea69e38

Please sign in to comment.