Skip to content

Fix GetHashCode on constructed types with annotated type arguments#35613

Merged
AlekseyTs merged 1 commit intodotnet:masterfrom
jcouv:hash-code
Jun 26, 2019
Merged

Fix GetHashCode on constructed types with annotated type arguments#35613
AlekseyTs merged 1 commit intodotnet:masterfrom
jcouv:hash-code

Conversation

@jcouv
Copy link
Copy Markdown
Member

@jcouv jcouv commented May 9, 2019

If you have C<T~> (definition) and a type C<T!> or C<T?> constructed from it, they should have the same value for GetHashCode(), 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

@jcouv jcouv added this to the 16.2.P1 milestone May 9, 2019
@jcouv jcouv requested a review from a team as a code owner May 9, 2019 21:18
@jcouv jcouv self-assigned this May 9, 2019
// 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
Copy link
Copy Markdown
Member Author

@jcouv jcouv May 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #35619


In reply to: 283579930 [](ancestors = 283579930,282670985)

@jcouv jcouv added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label May 9, 2019
@jcouv jcouv removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label May 10, 2019
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 10, 2019

@AlekseyTs Could you take another look when you have some time?

I debated testing scenarios where hashes should be different C<string> vs. C<T>, as the hash codes should probably be different but that's not deterministic... What do you think?

Still have not looked at VB. #Closed

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented May 10, 2019

I debated testing scenarios where hashes should be different C vs. C, as the hash codes should probably be different but that's not deterministic... What do you think?

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
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeCompareKind.CLRSignatureCompareOptions [](start = 96, length = 42)

Passing TypeCompareKind here is not necessary, we are comparing type parameter symbols. #Closed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is assuming that both sides are definitions


In reply to: 283573830 [](ancestors = 283573830)


type = type.ContainingType;

}
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} [](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)");
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.True [](start = 12, length = 11)

Can we use Assert.Equal instead? #Closed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

() [](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);
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)));
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll stop by tomorrow to understand better.


In reply to: 283579218 [](ancestors = 283579218)

";

// 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
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// #30677 - Expect no errors [](start = 12, length = 67)

It looks like this can be removed? #Closed

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented May 13, 2019

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))
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented May 14, 2019

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

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented May 14, 2019

Thanks. Updated the original description to say that #30677 is now fixed.
I'll work on VB now and push to this same PR if that's ok. #Closed

@jcouv jcouv added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label May 14, 2019
@jinujoseph jinujoseph modified the milestones: 16.2.P1, 16.2 Jun 9, 2019
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Jun 19, 2019

@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 MethodSymbol.Construct in VB only takes type arguments (without modifiers).

Here's what I have so far for VB side. #Closed

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Jun 19, 2019

For example:
In TypeSubstitution

        Public Shared Function Create(
            targetGenericDefinition As Symbol,
            params As ImmutableArray(Of TypeParameterSymbol),
            args As ImmutableArray(Of TypeWithModifiers),
            Optional allowAlphaRenamedTypeParametersAsArguments As Boolean = False
        ) As TypeSubstitution

then in NamedTypeSymbol:

        Friend Function Construct(substitution As TypeSubstitution) As NamedTypeSymbol
``` #Closed

@jcouv jcouv removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Jun 21, 2019
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Jun 21, 2019

@AlekseyTs I'll stop by to discuss.
The VB test differs from the C# test in two regards:

  1. in VB, Construct can only be applied to definitions
  2. in VB, there may be a bug in comparisons with IsSameType ignoring custom modifiers #Closed

Comment thread src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs Outdated
@jcouv jcouv force-pushed the hash-code branch 2 times, most recently from af02a2e to 26f2ba9 Compare June 22, 2019 00:44
@jcouv jcouv modified the milestones: 16.2, 16.2.P4 Jun 22, 2019
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Jun 24, 2019

@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
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Jun 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Jun 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Jun 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Jun 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Jun 24, 2019

Done with review pass (iteration 5) #Closed

End Sub

<WorkItem(30673, "https://github.com/dotnet/roslyn/issues/30673")>
<Fact>
Copy link
Copy Markdown
Member Author

@jcouv jcouv Jun 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Jun 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetHashCode [](start = 34, length = 11)

It looks like SubstitutedErrorType has similar issue. #Closed

Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Jun 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continue Do [](start = 28, length = 11)

It looks like this is going to cause an infinite loop, we are not advancing. #Closed

Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Jun 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Jun 25, 2019

Done with review pass (iteration 7) #Closed

Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (iteration 8)

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Jun 25, 2019

Thanks @AlekseyTs
@dotnet/roslyn-compiler for a second review. Thanks

Copy link
Copy Markdown
Member

@chsienki chsienki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Conflicts:
	src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Jun 25, 2019

Rebased/squashed on top of latest master and resolved conflict in test file.

@AlekseyTs AlekseyTs merged commit 8ae86d7 into dotnet:master Jun 26, 2019
@jcouv jcouv deleted the hash-code branch June 26, 2019 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

4 participants