Fix GetHashCode on constructed types with annotated type arguments#35613
Fix GetHashCode on constructed types with annotated type arguments#35613AlekseyTs merged 1 commit intodotnet:masterfrom
Conversation
| // The implemented interface is Outer<T!>.Inner<U!>.Interface | ||
| internal class Derived6 : Outer<T>.Inner<U>.Interface | ||
| { | ||
| // The explicit interface is Outer<T>.Inner<U!>.Interface |
There was a problem hiding this comment.
Tagging @AlekseyTs for discussion.
This explicit interface uses the definition of Outer<T>, but I'm thinking it should use a fully constructed type instead: Outer<T!>.Inner<U!>.Interface as when the type is completely spelled out (in the implemented interface two lines above, or in the test ExplicitInterface_WithExplicitOuter below).
What do you think? #Closed
There was a problem hiding this comment.
This explicit interface uses the definition of Outer, but I'm thinking it should use a fully constructed type instead: Outer<T!>.Inner<U!>.Interface as when the type is completely spelled out (in the implemented interface two lines above, or in the test ExplicitInterface_WithExplicitOuter below).
I think this is something worth discussing. But I am not sure why Outer<T!>.Inner<U!> is a better choice and whether current annotation context should play a role the same way as it does when the name is spelled out.
In reply to: 282670985 [](ancestors = 282670985)
|
@AlekseyTs Could you take another look when you have some time? I debated testing scenarios where hashes should be different Still have not looked at VB. #Closed |
Testing inequality of hash code wouldn't be the right thing to do. There is no requirement for hash code to be different for not equal objects. #Closed |
| if (wasConstructedForAnnotations(type)) | ||
| { | ||
| // A type that uses its own type parameters as type arguments was constructed only for the purpose of adding annotations | ||
| // In that case we'll want to compute a hash that ignores such annotations and matches the object-hash from the definition |
There was a problem hiding this comment.
object-hash [](start = 107, length = 11)
I think we should simply say "hash code fro the definition", whatever that hash code might be. #Closed
| do | ||
| { | ||
| var typeArguments = type.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; | ||
| var typeParameters = type.ConstructedFrom.TypeParameters; |
There was a problem hiding this comment.
ConstructedFrom [](start = 46, length = 15)
I think this should be OriginalDefinition instead of ConstructedFrom. #Closed
|
|
||
| for (int i = 0; i < typeArguments.Length; i++) | ||
| { | ||
| if (!typeArguments[i].Type.OriginalDefinition.Equals(typeParameters[i], TypeCompareKind.CLRSignatureCompareOptions)) |
There was a problem hiding this comment.
TypeCompareKind.CLRSignatureCompareOptions [](start = 96, length = 42)
Passing TypeCompareKind here is not necessary, we are comparing type parameter symbols. #Closed
There was a problem hiding this comment.
|
|
||
| type = type.ContainingType; | ||
|
|
||
| } |
There was a problem hiding this comment.
} [](start = 16, length = 1)
It looks like there is an unnecessary empty line above. #Closed
| Assert.False(c.Equals(c2)); | ||
| Assert.True(c.Equals(c2, TypeCompareKind.CLRSignatureCompareOptions)); | ||
|
|
||
| Assert.True(c2.GetHashCode() == c.GetHashCode(), "hash codes differed even though types matched (ignoring nullability)"); |
There was a problem hiding this comment.
Assert.True [](start = 12, length = 11)
Can we use Assert.Equal instead? #Closed
There was a problem hiding this comment.
I chose to use Assert.True so that I could put a message as a comment.
In reply to: 283574566 [](ancestors = 283574566)
| Assert.True(type.IsDefinition); | ||
|
|
||
| var type2 = comp.GetMember<MethodSymbol>("C.M").ReturnType; | ||
| Assert.Equal("C<T>", type2.ToTestDisplayString()); |
There was a problem hiding this comment.
() [](start = 58, length = 2)
Consider using includeNonNullable: true to see the difference with oblivious. #Closed
| var c2 = cDefinition.Construct(ImmutableArray.Create(TypeWithAnnotations.Create(cDefinition.TypeParameters.Single(), NullableAnnotation.NotAnnotated))); | ||
| var i2 = c2.GetTypeMember("I"); | ||
| Assert.Equal("C<T>.I<U>", i2.ToTestDisplayString()); | ||
| Assert.True((object)i2.OriginalDefinition == iDefinition); |
There was a problem hiding this comment.
Assert.True [](start = 12, length = 11)
Please use Assert.Same(iDefinition, i2.OriginalDefinition) instead. #Closed
| Assert.Equal("C<T>.I<U>", i2constructed.ToTestDisplayString()); | ||
| AssertHashCodesMatch(iDefinition, i2constructed); | ||
|
|
||
| var i2b = i2.Construct(ImmutableArray.Create(TypeWithAnnotations.Create(i2.TypeParameters.Single(), NullableAnnotation.Annotated))); |
There was a problem hiding this comment.
var i2b = i2.Construct(ImmutableArray.Create(TypeWithAnnotations.Create(i2.TypeParameters.Single(), NullableAnnotation.Annotated))); [](start = 12, length = 132)
Consider also adding scenario when iDefinition is constructed this way. Consider also testing scenario when iDefinition.TypeParameters are used to construct i2 in the same manner. #Closed
There was a problem hiding this comment.
| "; | ||
|
|
||
| // https://github.com/dotnet/roslyn/issues/30677, https://github.com/dotnet/roslyn/issues/30673 - Expect no errors | ||
| // https://github.com/dotnet/roslyn/issues/30677 - Expect no errors |
There was a problem hiding this comment.
// #30677 - Expect no errors [](start = 12, length = 67)
It looks like this can be removed? #Closed
|
Done with review pass (iteration 2) #Closed |
|
|
||
| for (int i = 0; i < typeArguments.Length; i++) | ||
| { | ||
| if (!typeArguments[i].Type.OriginalDefinition.Equals(typeParameters[i], TypeCompareKind.CLRSignatureCompareOptions)) |
There was a problem hiding this comment.
typeParameters[i] [](start = 77, length = 17)
Consider calling equality of off that instead of typeArguments[i].Type.OriginalDefinition. I.e. switching sides, this way we know it will always call code for TypeParameters. #Closed
|
The iteration 3 LGTM, modulo the suggested tests and the remaining VB work. You might want to update the #35613 (comment) comment to reflect the current state of the PR more accurately. #Closed |
|
Thanks. Updated the original description to say that #30677 is now fixed. |
|
@AlekseyTs I'll stop by tomorrow for some tips. I spent some time today digging into the VB testing again, and I'm stumbling on the same issue I did a few weeks ago: I can't find how to make a substituted method with different type modifiers. The Here's what I have so far for VB side. #Closed |
|
For example: then in NamedTypeSymbol: |
|
@AlekseyTs I'll stop by to discuss.
|
af02a2e to
26f2ba9
Compare
|
@AlekseyTs I think this is ready for another look. Thanks #Closed |
| Dim typeParameters = type.OriginalDefinition.TypeParameters | ||
|
|
||
| For i = 0 To typeArguments.Length - 1 | ||
| If Not typeParameters(i).IsSameType(typeArguments(i).OriginalDefinition, TypeCompareKind.ConsiderEverything) Then |
There was a problem hiding this comment.
typeParameters(i).IsSameType(typeArguments(i).OriginalDefinition, TypeCompareKind.ConsiderEverything) [](start = 27, length = 101)
Why not use simple Equals ? #Closed
| _substitution = substitution | ||
| End Sub | ||
|
|
||
| Protected Shared Function WasConstructedForModifiers(type As NamedTypeSymbol) As Boolean |
There was a problem hiding this comment.
WasConstructedForModifiers [](start = 34, length = 26)
Can this be an instance method instead? #Closed
| Dim typeArguments = type.TypeArgumentsNoUseSiteDiagnostics | ||
| Dim typeParameters = type.OriginalDefinition.TypeParameters | ||
|
|
||
| For i = 0 To typeArguments.Length - 1 |
There was a problem hiding this comment.
For i = 0 To typeArguments.Length - 1 [](start = 16, length = 37)
It feels like it would be better to iterate through the chain of TypeSubstitutions instead. #Closed
| Return False ' different definition | ||
| End If | ||
|
|
||
| Return t1.ContainingSymbol.ContainingType.IsSameType(t2.ContainingSymbol.ContainingType, compareKind) |
There was a problem hiding this comment.
ContainingType [](start = 43, length = 14)
This doesn't look right. Why re we skipping through the container? Can't that just give us a null reference? #Closed
|
Done with review pass (iteration 5) #Closed |
| End Sub | ||
|
|
||
| <WorkItem(30673, "https://github.com/dotnet/roslyn/issues/30673")> | ||
| <Fact> |
There was a problem hiding this comment.
Fact [](start = 9, length = 4)
Test with SubstitutedErrorTypeSymbol too #Closed
| @@ -491,6 +509,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols | |||
|
|
|||
| Public Overrides Function GetHashCode() As Integer | |||
There was a problem hiding this comment.
GetHashCode [](start = 34, length = 11)
It looks like SubstitutedErrorType has similar issue. #Closed
There was a problem hiding this comment.
Continue Do [](start = 28, length = 11)
It looks like this is going to cause an infinite loop, we are not advancing. #Closed
There was a problem hiding this comment.
ContainingType [](start = 26, length = 14)
This doesn't look right, we should be comparing ContainingSymbol otherwise we will skip through a generic method. I guess methods would be equal if containing types are. #Closed
|
Done with review pass (iteration 7) #Closed |
|
Thanks @AlekseyTs |
Conflicts: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
|
Rebased/squashed on top of latest master and resolved conflict in test file. |
If you have
C<T~>(definition) and a typeC<T!>orC<T?>constructed from it, they should have the same value forGetHashCode(), namely the object-hash of the definition.Fixes #30673
If this looks okay, then I'll extend this PR to also cover VB.
Fixes #30677
Tagging @AlekseyTs