VB: Recognize and check an unmanaged constraint#75665
Conversation
|
@dotnet/roslyn-compiler Please review |
|
@dotnet/roslyn-compiler Please review |
| if (_diagnostics.Add(diagnosticInfo) && diagnosticInfo?.Severity == DiagnosticSeverity.Error) | ||
| { | ||
| RecordPresenceOfAnError(); | ||
| } |
There was a problem hiding this comment.
This is repeated several times. Consider extracting a helper method. #Resolved
| /// <summary> | ||
| /// True if this type or some containing type has type parameters. | ||
| /// </summary> | ||
| public bool IsGenericType { get; } |
| /// without looking at its fields and Unknown otherwise. | ||
| /// Also returns whether or not the given type is generic. | ||
| /// </summary> | ||
| public static (ThreeState isManaged, bool hasGenerics) IsManagedTypeHelper(INamedTypeSymbolInternal type) |
There was a problem hiding this comment.
Consider use a top-level class and make this an extension method.
I prefer to keep the code as is. I do not foresee us using this helper much in the future. Therefore, in my opinion there is no good reason to "pollute" name lookup binding space with it. If we could use Default Interface Implementations feature in our code, I would make this method a member of INamedTypeSymbolInternal.
| End Function | ||
|
|
||
| ' <summary> | ||
| ' IsManagedType is simple for most named types: |
| End If | ||
|
|
||
| useSiteInfo.Add(field.GetUseSiteInfo()) | ||
| Dim fieldType As TypeSymbol = field.Type.GetTupleUnderlyingTypeOrSelf() |
There was a problem hiding this comment.
Are we testing a case where this call makes a difference?
I cannot think of a situation when it would make an observable difference in behavior. That is not to say that that it is impossible to run into one in some edge error scenario. Given the difference in the way C# and VB compilers represent tuple symbols, I prefer to be conservative and explicitly unwrap tuple types.
| Dim namedTypeSymbol = TryCast(typeSymbol, NamedTypeSymbol) | ||
| Return namedTypeSymbol IsNot Nothing AndAlso | ||
| namedTypeSymbol.Name = "UnmanagedType" AndAlso | ||
| namedTypeSymbol.Arity = 0 AndAlso |
There was a problem hiding this comment.
Indeed, added a test and opened an issue to follow up - #75681
What does "ForTypeDeclaration" mean here? (It looks like the resulting binder is more specific than for the containing type.) Should this method be named "ForMemberDeclaration" instead? And would it make sense to pass the Refers to: src/Compilers/VisualBasic/Portable/Symbols/Source/SourceEventSymbol.vb:741 in a55f298. [](commit_id = a55f298, deletion_comment = False) |
Same questions as in Should this method be named "ForMemberDeclaration" instead? And would it make sense to pass the Refers to: src/Compilers/VisualBasic/Portable/Symbols/Source/SourcePropertySymbol.vb:1020 in a55f298. [](commit_id = a55f298, deletion_comment = False) |
This is an existing method that new code is not using. I do not intend to rename it or change its behavior. In reply to: 2450165726 Refers to: src/Compilers/VisualBasic/Portable/Symbols/Source/SourceEventSymbol.vb:741 in a55f298. [](commit_id = a55f298, deletion_comment = False) |
Same response In reply to: 2450168231 Refers to: src/Compilers/VisualBasic/Portable/Symbols/Source/SourcePropertySymbol.vb:1020 in a55f298. [](commit_id = a55f298, deletion_comment = False) |
| o.M(New Program()) | ||
| ~ | ||
| ]]></expected>) | ||
| End Sub |
There was a problem hiding this comment.
Are we testing type parameters?
Type parameters are covered in UnmanagedCheck_TypeParameter and indirectly on several other tests.
| Dim s5 = compilation.GetTypeByMetadataName("S5`1") | ||
| Assert.Equal(ManagedKind.Managed, s1.GetManagedKind(Nothing)) | ||
| Assert.Equal(ManagedKind.Unmanaged, s2.GetManagedKind(Nothing)) | ||
| Assert.Equal(ManagedKind.UnmanagedWithGenerics, s5.GetManagedKind(Nothing)) |
|
@333fred, @dotnet/roslyn-compiler For the second review. |
1 similar comment
|
@333fred, @dotnet/roslyn-compiler For the second review. |
333fred
left a comment
There was a problem hiding this comment.
Generally lgtm. Couple of small comments.
src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PETypeParameterSymbol.vb
Outdated
Show resolved
Hide resolved
| Friend Overrides ReadOnly Property HasUnmanagedTypeConstraint As Boolean | ||
| Get | ||
| EnsureAllConstraintsAreResolved() | ||
| Return CType(Volatile.Read(_lazyHasIsUnmanagedConstraint), ThreeState).Value() |
There was a problem hiding this comment.
I don't think we actually need the Volatile.Read; the memory barrier from ImmutableInterlocked.InterlockedInitialize in ResolveConstraints should prevent writes to _lazyHasIsUnmanagedConstraint from being reordered after writes to _lazyConstraintType, and EnsureAllConstaintsAreResolved won't skip calling ResolveConstraints unless this thread has already observed that _lazyConstraintType has been initialized. And if the ordering in that method ever changes (ie, the write to _lazyConstraintType is moved before the write to _lazyHasIsUnmanagedConstraint), then this Volatile.Read won't actually help anyways. #Resolved
There was a problem hiding this comment.
I prefer to keep the Volatile.Read. The "distance" between this operation and the interlocked exchange is several method calls and some of them are virtual calls. In one of the methods we check value of _lazyConstraintTypes without any memory barrier operation in the same body. Assuming that that read and this _lazyHasIsUnmanagedConstraint can be reordered, we might see the old value of _lazyHasIsUnmanagedConstraint here while the _lazyConstraintTypes check in EnsureAllConstraintsAreResolved sees the new value of _lazyConstraintTypes. If that is too paranoid, that is fine with me, Volatile.Read won't make things worse.
There was a problem hiding this comment.
Assuming that that read and this
_lazyHasIsUnmanagedConstraintcan be reordered, we might see the old value of_lazyHasIsUnmanagedConstrainthere while the_lazyConstraintTypescheck inEnsureAllConstraintsAreResolvedsees the new value of_lazyConstraintTypes.
The issue with this reasoning is that that is not what Volatile.Read does. Volatile.Read prevents any reads or writes after the read from being ordered before this read. It does not prevent reads that occur before this read from being reordered to after this read. If that is your concern, you would need Thread.MemoryBarrier() in between the method call and the read of _lazyHasIsUnmanagedConstraint.
There was a problem hiding this comment.
I remain unconvinced. The documentation says: "Reads the value of the specified field." That is exactly what I am going after. The call reads the specified address. I am not concerned about what happens before or after the call. I am concerned about that specific read happening exactly when the call is executed. Not before the call, not after the call.
There was a problem hiding this comment.
Alright, after some extensive offline conversation here, I've opened #75759 to follow up on the race condition here; given that it's present in C# version as well, and has been present for a while, I'm going to approve for now, but we'll be following up on this next week to discuss a better long-term approach for the compiler.
src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PETypeParameterSymbol.vb
Show resolved
Hide resolved
| VisualBasic15_5 = 1505 | ||
| VisualBasic16 = 1600 | ||
| VisualBasic16_9 = 1609 | ||
| VisualBasic17_13 = 1713 |
There was a problem hiding this comment.
Please add the new language version and feature in:
There was a problem hiding this comment.
Is there an explanation as to what this enables VB to do?
No description provided.