-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Learn from calls to Equals methods in NullableWalker #36722
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
90b2210
45e0cd8
24e504e
f361dda
86084e1
cdadb79
3b565c0
afa0647
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2164,8 +2164,6 @@ protected override void AfterLeftChildHasBeenVisited(BoundBinaryOperator binary) | |
|
|
||
| if (operandComparedToNull != null) | ||
| { | ||
| operandComparedToNull = SkipReferenceConversions(operandComparedToNull); | ||
|
|
||
| // Set all nested conditional slots. For example in a?.b?.c we'll set a, b, and c. | ||
| bool nonNullCase = op != BinaryOperatorKind.Equal; // true represents WhenTrue | ||
| splitAndLearnFromNonNullTest(operandComparedToNull, whenTrue: nonNullCase); | ||
|
|
@@ -2813,8 +2811,11 @@ private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiv | |
| method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); | ||
| } | ||
|
|
||
| method = VisitArguments(node, node.Arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, | ||
| node.Expanded, node.InvokedAsExtensionMethod, method).method; | ||
| ImmutableArray<VisitArgumentResult> results; | ||
| (method, results) = VisitArguments(node, node.Arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, | ||
| node.Expanded, node.InvokedAsExtensionMethod, method); | ||
|
|
||
| LearnFromEqualsMethod(method, node, receiverType, results); | ||
|
|
||
| if (method.MethodKind == MethodKind.LocalFunction) | ||
| { | ||
|
|
@@ -2825,6 +2826,104 @@ private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiv | |
| SetResult(node, GetReturnTypeWithState(method), method.ReturnTypeWithAnnotations); | ||
| } | ||
|
|
||
| private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray<VisitArgumentResult> results) | ||
| { | ||
| // easy out | ||
| var parameterCount = method.ParameterCount; | ||
| if ((parameterCount != 1 && parameterCount != 2) | ||
| || method.MethodKind != MethodKind.Ordinary | ||
| || method.ReturnType.SpecialType != SpecialType.System_Boolean | ||
| || (method.Name != SpecialMembers.GetDescriptor(SpecialMember.System_Object__Equals).Name | ||
| && method.Name != SpecialMembers.GetDescriptor(SpecialMember.System_Object__ReferenceEquals).Name)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var arguments = node.Arguments; | ||
|
|
||
| var isStaticEqualsMethod = method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__EqualsObjectObject)) | ||
| || method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__ReferenceEquals)); | ||
| if (isStaticEqualsMethod || | ||
| isWellKnownEqualityMethodOrImplementation(compilation, method, WellKnownMember.System_Collections_Generic_IEqualityComparer_T__Equals)) | ||
| { | ||
| Debug.Assert(arguments.Length == 2); | ||
| learnFromEqualsMethodArguments(arguments[0], results[0].RValueType, arguments[1], results[1].RValueType); | ||
| return; | ||
| } | ||
|
|
||
| var isObjectEqualsMethodOrOverride = method.GetLeastOverriddenMethod(accessingTypeOpt: null) | ||
| .Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__Equals)); | ||
| if (isObjectEqualsMethodOrOverride || | ||
| isWellKnownEqualityMethodOrImplementation(compilation, method, WellKnownMember.System_IEquatable_T__Equals)) | ||
| { | ||
| Debug.Assert(arguments.Length == 1); | ||
| learnFromEqualsMethodArguments(node.ReceiverOpt, receiverType, arguments[0], results[0].RValueType); | ||
| return; | ||
| } | ||
|
|
||
| static bool isWellKnownEqualityMethodOrImplementation(CSharpCompilation compilation, MethodSymbol method, WellKnownMember wellKnownMember) | ||
| { | ||
| var wellKnownMethod = compilation.GetWellKnownTypeMember(wellKnownMember); | ||
| if (wellKnownMethod is null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| var wellKnownType = wellKnownMethod.ContainingType; | ||
| var parameterType = method.Parameters[0].TypeWithAnnotations; | ||
| var constructedType = wellKnownType.Construct(ImmutableArray.Create(parameterType)); | ||
|
|
||
| Symbol constructedMethod = null; | ||
| foreach (var member in constructedType.GetMembers(WellKnownMemberNames.ObjectEquals)) | ||
| { | ||
| if (member.OriginalDefinition.Equals(wellKnownMethod)) | ||
| { | ||
| constructedMethod = member; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| Debug.Assert(constructedMethod != null, "the original definition is present but the constructed method isn't present"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: empty line below #Resolved |
||
|
|
||
| // FindImplementationForInterfaceMember doesn't check if this method is itself the interface method we're looking for | ||
| if (constructedMethod.Equals(method)) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| var implementationMethod = method.ContainingType.FindImplementationForInterfaceMember(constructedMethod); | ||
| return method.Equals(implementationMethod); | ||
| } | ||
|
|
||
| void learnFromEqualsMethodArguments(BoundExpression left, TypeWithState leftType, BoundExpression right, TypeWithState rightType) | ||
| { | ||
| // comparing anything to a null literal gives maybe-null when true and not-null when false | ||
| // comparing a maybe-null to a not-null gives us not-null when true, nothing learned when false | ||
| if (left.ConstantValue?.IsNull == true) | ||
| { | ||
| Split(); | ||
| LearnFromNullTest(right, ref StateWhenTrue); | ||
| LearnFromNonNullTest(right, ref StateWhenFalse); | ||
| } | ||
| else if (right.ConstantValue?.IsNull == true) | ||
| { | ||
| Split(); | ||
| LearnFromNullTest(left, ref StateWhenTrue); | ||
| LearnFromNonNullTest(left, ref StateWhenFalse); | ||
| } | ||
| else if (leftType.MayBeNull && rightType.IsNotNull) | ||
| { | ||
| Split(); | ||
| LearnFromNonNullTest(left, ref StateWhenTrue); | ||
| } | ||
| else if (rightType.MayBeNull && leftType.IsNotNull) | ||
| { | ||
| Split(); | ||
| LearnFromNonNullTest(right, ref StateWhenTrue); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private TypeWithState VisitCallReceiver(BoundCall node) | ||
| { | ||
| var receiverOpt = node.ReceiverOpt; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1705,7 +1705,7 @@ private static Location GetInterfaceLocation(Symbol interfaceMember, TypeSymbol | |
| snt = implementingType as SourceMemberContainerTypeSymbol; | ||
| } | ||
|
|
||
| return snt?.GetImplementsLocation(@interface) ?? implementingType.Locations[0]; | ||
| return snt?.GetImplementsLocation(@interface) ?? implementingType.Locations.FirstOrNone(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is this Linq? #Resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extension: internal static Location FirstOrNone(this ImmutableArray<Location> items)
{
return items.IsEmpty ? Location.None : items[0];
}
``` #Resolved |
||
| } | ||
|
|
||
| private static bool ReportAnyMismatchedConstraints(MethodSymbol interfaceMethod, TypeSymbol implementingType, MethodSymbol implicitImpl, DiagnosticBag diagnostics) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI @jcouv I found removing this conversion-stripping step did not affect anything. #Closed