Adding IsConst modreq on ref readonly signatures#19658
Adding IsConst modreq on ref readonly signatures#19658VSadov merged 6 commits intodotnet:features/readonly-reffrom OmarTawfik:isconst-modreqs
Conversation
There was a problem hiding this comment.
Comparing symbols by full name is probably not be the best way to do it. Looking up a better alternative. #Closed
There was a problem hiding this comment.
Consider a similar scenario like IsVolatileModfifierType. Can we re-use that approach here? #Resolved
There was a problem hiding this comment.
Unfortunately, no. Unless there is something I'm missing, IsConst is not a special library type :(
In reply to: 117829472 [](ancestors = 117829472)
There was a problem hiding this comment.
Perhaps the same approach as SymbolFactory.GetSystemTypeSymbol. #Resolved
There was a problem hiding this comment.
There was a problem hiding this comment.
This seems simple enough to fix now vs. needing a PROTOTYPE comment to fix later. #Resolved
There was a problem hiding this comment.
Comparing symbols by full name is probably not be the best way to do it. Looking up a better alternative.
Given that this is not a SpecialType, I think name comparison is the only right way to do that. If performance is a concern we can compare type name and namespace names separately, then calling ToDisplayString won't be needed. #Closed
There was a problem hiding this comment.
If going with name comparing, should definitely compare namespace/type name parts separately. I think ToDisplay will concat them every time
In reply to: 118063143 [](ancestors = 118063143)
There was a problem hiding this comment.
There was a problem hiding this comment.
From what I tracked down, it ends up doing a lookup in NamespaceOrTypeSymbol based on string comparison as well. Will separate the comparison to namespace/type pair to improve performance.
In reply to: 117866143 [](ancestors = 117866143)
There was a problem hiding this comment.
DecodeModifiersOrThrow [](start = 34, length = 22)
This was refactored since it shares the same behavior as DecodeModifiersOrThrow(), making use of the additional parameter. #Closed
|
cc @VSadov @dotnet/roslyn-compiler #Closed |
| } | ||
| else if (_refKind == RefKind.RefReadOnly && (this.IsVirtual || this.IsAbstract)) | ||
| { | ||
| var isConst = withTypeParamsBinder.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_IsConst, diagnostics, syntax); |
There was a problem hiding this comment.
What if the type is missing? Consider that custom mscorlibs may not have this type. #ByDesign
There was a problem hiding this comment.
Yes. I've a test that specifically looks for that. Check ProperErrorsArePropagatedIfMscorlibIsConstIsNotAvailable in IsConstModifierTests.cs.
The method Binder.GetWellKnownType() will report such errors to the passed diagnostics bag.
In reply to: 117828511 [](ancestors = 117828511)
There was a problem hiding this comment.
Consider the name isConstType vs. isConst. The former is a bit more descriptive and seems more consistent with other names in the method. The latter can be misread as describing the mutable state of a value. #Resolved
|
|
||
| if (shouldPlaceIsConstModifier && refKind == RefKind.RefReadOnly && (owner.IsVirtual || owner.IsAbstract)) | ||
| { | ||
| var isConst = context.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_IsConst, declarationDiagnostics, syntax); |
There was a problem hiding this comment.
What if the type is missing? consider custom mscorlib may not have this type. #ByDesign
| } | ||
| else if (_refKind == RefKind.RefReadOnly && (this.IsVirtual || this.IsAbstract)) | ||
| { | ||
| var isConst = bodyBinder.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_IsConst, diagnostics, syntax); |
|
|
||
| Friend Overrides Function IsConstModifierType(moduleSymbol As PEModuleSymbol, type As TypeSymbol) As Boolean | ||
| ' VB doesn't deal with ref-readonly parameters or return-types. | ||
| Return False |
There was a problem hiding this comment.
Don't understand why we are returning False here. Even if VB doesn't deal with these typse returning False here still seems wrong. If the function isn't implemented it should throw, not return the (potentially) wrong answer. #ByDesign
There was a problem hiding this comment.
This means that if VB MetadataDecoder tries to read a symbol with a modreq of type IsConst, it will return False meaning it doesn't support it, and the symbol will be marked appropriately. Check IsVolatileModifierType() in the same file.
In reply to: 117828980 [](ancestors = 117828980)
There was a problem hiding this comment.
If that's the case then we should consider renaming both of these methods. The name simply does not match up to the actual implementation. Consider this code
var isConst = IsConstModifierType(type);99% of developers who look at that line are going to interpret this as determining if type is the const modifier. In reality it's detecting if a) it's the const modifier and b) the present language supports it. The 1% who will understand the behavior are those who realize the name of the method is wrong.
#Resolved
There was a problem hiding this comment.
There was a problem hiding this comment.
Consider a similar scenario like IsVolatileModfifierType. Can we re-use that approach here? #Resolved
| } | ||
|
|
||
| [Fact] | ||
| public void ProperErrorsArePropagatedIfMscorlibIsConstIsNotAvailable() |
There was a problem hiding this comment.
ProperErrorsArePropagatedIfMscorlibIsConstIsNotAvailable [](start = 20, length = 56)
Note to self: duplicate tests for other symbol types and the same error. #Closed
| _lazyParameters = CustomModifierUtils.CopyParameterCustomModifiers(overriddenOrImplementedProperty.Parameters, _lazyParameters, alsoCopyParamsModifier: isOverride); | ||
| } | ||
| } | ||
| else if (_refKind == RefKind.RefReadOnly && (this.IsVirtual || this.IsAbstract)) |
There was a problem hiding this comment.
does this work with interface implementations? explicit/implicit? #Resolved
There was a problem hiding this comment.
I think we need some tests for interface implementations
In reply to: 117863982 [](ancestors = 117863982)
| binder, this, syntax.ParameterList, out arglistToken, | ||
| allowRefOrOut: true, | ||
| allowThis: false, | ||
| shouldPlaceIsConstModifier: false, |
There was a problem hiding this comment.
I think we want "true" here. Otherwise old compiler may convert methods with regular "ref" to delegate types with "ref readonly". #Resolved
There was a problem hiding this comment.
I think we want "true" here. Otherwise old compiler may convert methods with regular "ref" to delegate types with "ref readonly".
Please add VB test for this scenario. #Closed
|
I am done with a review pass. Mostly good. I had some comments, that I think are actionable. In reply to: 303181737 [](ancestors = 303181737) |
| { | ||
| // This cannot be placed on CustomModifiers, just RefCustomModifiers | ||
| throw new UnsupportedSignatureContent(); | ||
| } |
There was a problem hiding this comment.
Is isConstFound needed or could the check above be replaced by else if (info.CustomModifiers.Length > 0)? Then the delegate to DecodeModifiersOrThrow can be simplified to avoid the closure class. Similar comment for isVolatileFound in DecodeFieldSignature. #Resolved
There was a problem hiding this comment.
Will remove the need for capturing, but we still need that isConstFound value since we allow optional modifiers, just not required ones.
In reply to: 117907098 [](ancestors = 117907098)
| End Sub | ||
|
|
||
| <Fact> | ||
| Public Sub NonExtensibleReadOnlySignaturesAreRead() |
There was a problem hiding this comment.
Perhaps Virtual rather than Extensible, here and below. #Resolved
| public int this[ref readonly int x] => x; | ||
| }"; | ||
|
|
||
| var options = TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All); |
There was a problem hiding this comment.
Is MetadataImportOptions.All needed here, or in tests below? #Resolved
| out _lazyParameters, alsoCopyParamsModifier: true); | ||
| } | ||
| } | ||
| else if (_refKind == RefKind.RefReadOnly && (this.IsVirtual || this.IsAbstract)) |
There was a problem hiding this comment.
Are we including IsConst on interface methods? #Resolved
There was a problem hiding this comment.
Yes: virtual method parameters, returns, interface methods in any position and delegates. #Resolved
There was a problem hiding this comment.
Parameters handle both parameters and returns. Will add tests for interfaces and delegates.
In reply to: 118018517 [](ancestors = 118018517)
There was a problem hiding this comment.
Will add tests to both implicit and explicit interface implementations
In reply to: 118016179 [](ancestors = 118016179)
There was a problem hiding this comment.
This seems simple enough to fix now vs. needing a PROTOTYPE comment to fix later. #Resolved
| bool allowRefOrOut, | ||
| bool allowThis) | ||
| bool allowThis, | ||
| bool shouldPlaceIsConstModifier) |
There was a problem hiding this comment.
Consider shoudEmit vs. shouldPlace. #Resolved
There was a problem hiding this comment.
Consider shoudEmit vs. shouldPlace.
Perhaps simply "addIsConstModifier". #Closed
| out _lazyParameters, alsoCopyParamsModifier: true); | ||
| } | ||
| } | ||
| else if (_refKind == RefKind.RefReadOnly && (this.IsVirtual || this.IsAbstract)) |
There was a problem hiding this comment.
Yes: virtual method parameters, returns, interface methods in any position and delegates. #Resolved
| var isConst = IsConstModifierType(type); | ||
| isConstFound |= isConst; | ||
| return isConst; | ||
| }); |
There was a problem hiding this comment.
This lambda is capturing hence is adding two allocations to each call to DecodeModifiersOrThrow. Is this a sensitive enough area of the code that we should consider a non-allocating solution? #Resolved
There was a problem hiding this comment.
Will move to a version that has less allocations, also adding an extra overload for the less-traversed path.
In reply to: 118019729 [](ancestors = 118019729)
|
|
||
| Friend Overrides Function IsConstModifierType(moduleSymbol As PEModuleSymbol, type As TypeSymbol) As Boolean | ||
| ' VB doesn't deal with ref-readonly parameters or return-types. | ||
| Return False |
There was a problem hiding this comment.
If that's the case then we should consider renaming both of these methods. The name simply does not match up to the actual implementation. Consider this code
var isConst = IsConstModifierType(type);99% of developers who look at that line are going to interpret this as determining if type is the const modifier. In reality it's detecting if a) it's the const modifier and b) the present language supports it. The 1% who will understand the behavior are those who realize the name of the method is wrong.
#Resolved
| private ImmutableArray<ModifierInfo<TypeSymbol>> DecodeModifiersOrThrow( | ||
| ref BlobReader signatureReader, | ||
| out SignatureTypeCode typeCode, | ||
| Func<TypeSymbol, bool> acceptRequiredModifier = null) |
There was a problem hiding this comment.
Func<TypeSymbol, bool> acceptRequiredModifier = null [](start = 12, length = 52)
This feels too complicated. I think we can simply pas a boolean whether IsConst modifier should be accepted as a mod_req or not. #Closed
There was a problem hiding this comment.
Rather than making changes to this function, I would instead modify DecodeParameterOrThrow to specially handle IsConst modifier in the manner DecodeFieldSignature used to do that for IsVolatile and would keep this function and DecodeFieldSignature unchanged.
In reply to: 118052362 [](ancestors = 118052362)
There was a problem hiding this comment.
Will try to introduce in the next iteration a simpler version that doesn't use capturing lambdas. I don't like to duplicate the same logic in 3 places (DecodeModifiers, DecodeParameters, DecodeFields).
In reply to: 118055994 [](ancestors = 118055994,118052362)
| info.RefCustomModifiers = info.CustomModifiers; | ||
| info.CustomModifiers = DecodeModifiersOrThrow(ref signatureReader, out typeCode); | ||
| } | ||
| else if (isConstFound) |
There was a problem hiding this comment.
else if (isConstFound) [](start = 12, length = 22)
We could simply check for presence of any required modifiers in info.CustomModifiers. #Closed
There was a problem hiding this comment.
Will try to refactor it so that we don't need to do the extra enumeration.
In reply to: 118054332 [](ancestors = 118054332)
| // See if there is a Volatile modifier. | ||
| var isVolatileFound = false; | ||
| SignatureTypeCode typeCode; | ||
| ArrayBuilder<ModifierInfo<TypeSymbol>> customModifierBuilder = null; |
There was a problem hiding this comment.
ArrayBuilder<ModifierInfo> customModifierBuilder = null; [](start = 16, length = 68)
I would revert changes in this function, it doesn't look like we want any behavior change here. #Closed
There was a problem hiding this comment.
Will try to introduce in the next iteration a simpler version that doesn't use capturing lambdas. I don't like to duplicate the same logic in 3 places (DecodeModifiers, DecodeParameters, DecodeFields).
In reply to: 118054960 [](ancestors = 118054960)
| { | ||
| this.Visit(method.GetReturnValueAttributes(Context)); | ||
| this.Visit(method.ReturnValueCustomModifiers); | ||
| this.Visit(method.RefCustomModifiers); |
There was a problem hiding this comment.
this.Visit(method.RefCustomModifiers); [](start = 12, length = 38)
It feels like RefCustomModifiers should be visited before ReturnValueCustomModifiers. Also, it looks like there are two more places in ReferenceIndexerBase.cs that should be adjusted in a similar way. #Closed
There was a problem hiding this comment.
It looks like there is one more place in ReferenceDependencyWalker.cs as well.
In reply to: 118057254 [](ancestors = 118057254)
| Debug.Assert((marshalling != null || !parameterDefinition.MarshallingDescriptor.IsDefaultOrEmpty) == parameterDefinition.IsMarshalledExplicitly); | ||
|
|
||
| this.Visit(parameterDefinition.GetAttributes(Context)); | ||
| this.Visit(parameterDefinition.RefCustomModifiers); |
There was a problem hiding this comment.
this.Visit(parameterDefinition.RefCustomModifiers); [](start = 12, length = 51)
It looks like there is one more place in ReferenceDependencyWalker.cs that should be adjusted in a similar way. And in public virtual void Visit(IParameterTypeInformation parameterTypeInformation) in this type as well. #Closed
| foreach (ParameterSymbol param in property.Parameters) | ||
| { | ||
| if (param.RefKind != RefKind.None) | ||
| if (param.RefKind == RefKind.Ref || param.RefKind == RefKind.Out) |
There was a problem hiding this comment.
if (param.RefKind == RefKind.Ref || param.RefKind == RefKind.Out) [](start = 16, length = 65)
Did you add a test backing this change? Could you point to the test(s)? #Closed
There was a problem hiding this comment.
Adding IsConstModReqIsConsumedInRefCustomModifiersPosition_Indexers_ReturnTypes. This change basically allows to pass ref-readonly arguments to indexer methods. Passing ordinary arguments is the same to properties as ref-readonly arguments.
In reply to: 118061963 [](ancestors = 118061963)
There was a problem hiding this comment.
Adding
IsConstModReqIsConsumedInRefCustomModifiersPosition_Indexers_ReturnTypes. This change basically allows to pass ref-readonly arguments to indexer methods. Passing ordinary arguments is the same to properties as ref-readonly arguments.
I couldn't find the test, what file it should be in?
In reply to: 118609191 [](ancestors = 118609191,118061963)
There was a problem hiding this comment.
File IsConstModifierTests.cs in C# emit tests project. You can look forIsConstModReqIsConsumedInRefCustomModifiersPosition_Code_Indexers_ReturnTypes now.
In reply to: 119789809 [](ancestors = 119789809,118609191,118061963)
There was a problem hiding this comment.
Correction: IsConstModReqIsConsumedInRefCustomModifiersPosition_Code_Indexers_Parameters*
In reply to: 120218968 [](ancestors = 120218968,119789809,118609191,118061963)
| End Sub | ||
|
|
||
| <Fact> | ||
| Public Sub NonExtensibleReadOnlySignaturesAreRead() |
There was a problem hiding this comment.
Public Sub NonExtensibleReadOnlySignaturesAreRead() [](start = 8, length = 51)
Consider moving all these tests into a new file dedicated to ReadonlyRef feature. #Closed
| Dim reference = CreateCSharpCompilation(" | ||
| public class TestRef | ||
| { | ||
| public void M(ref readonly int x) |
There was a problem hiding this comment.
public void M(ref readonly int x) [](start = 4, length = 33)
This test covers ref readonly parameter. Do we have a similar test for return? #Closed
| Class Test | ||
| Shared Sub Main() | ||
| Dim obj = New TestRef() | ||
| Dim value = obj.M() |
There was a problem hiding this comment.
Dim value = [](start = 8, length = 12)
Consider removing the local, leaving gust the method call. #Closed
| public class TestRef | ||
| { | ||
| private int value = 0; | ||
| public virtual ref readonly int P => ref value; |
There was a problem hiding this comment.
public virtual ref readonly int P => ref value; [](start = 4, length = 47)
Do we have a test for a non-virtual property? #Closed
Success scenarios should be executed and runtime behavior should be verified. Applies to all similar tests. #Closed Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:2421 in 42a8f4c. [](commit_id = 42a8f4c, deletion_comment = False) |
Consider asserting that this method implements the interface. Applies to all similar tests. #Closed Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:2564 in 42a8f4c. [](commit_id = 42a8f4c, deletion_comment = False) |
I think for all "copy modifiers" tests it would be interesting to test flavor when compilation define their own IsConst type. Then we actually can observe the copy happening vs. we just added modifiers based on ref readonly usage. #Closed Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:2708 in 42a8f4c. [](commit_id = 42a8f4c, deletion_comment = False) |
Similarly should test scenarios when both compilations define their own IsConst type. #Closed Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:3257 in 42a8f4c. [](commit_id = 42a8f4c, deletion_comment = False) |
| destinationMethod.ReturnsByRef ? constructedSourceMethod.RefCustomModifiers : ImmutableArray<CustomModifier>.Empty); | ||
| customModifiers = CustomModifiersTuple.Create( | ||
| constructedSourceMethod.ReturnTypeCustomModifiers, | ||
| destinationMethod.ReturnsByRef || destinationMethod.ReturnsByRefReadonly ? constructedSourceMethod.RefCustomModifiers : ImmutableArray<CustomModifier>.Empty); |
There was a problem hiding this comment.
destinationMethod.ReturnsByRef || destinationMethod.ReturnsByRefReadonly [](start = 16, length = 73)
Could we simply check if RefKind is None? #Closed
|
Done with review pass. #Closed |
| signatureBinder, this, syntax.ParameterList, out arglistToken, | ||
| allowRefOrOut: true, | ||
| allowThis: true, | ||
| addIsConstModifier: IsVirtual || IsAbstract, |
There was a problem hiding this comment.
IsVirtual || IsAbst [](start = 36, length = 19)
This should probably include checks for override and explicit implementation, those are virtual (in metadata terms) as well and the difference will be observable for cases when the target method cannot be found, etc. #Closed
| { | ||
| var parameterSyntaxOpt = GetParameterListSyntax(syntax); | ||
| var parameters = MakeParameters(binder, this, parameterSyntaxOpt, diagnostics); | ||
| var parameters = MakeParameters(binder, this, parameterSyntaxOpt, diagnostics, addIsConstModifier: IsVirtual || IsAbstract); |
There was a problem hiding this comment.
IsVirtual || IsAbstract [](start = 111, length = 23)
Same comment as for methods. #Closed
This is not an explicit implementation. comment misplaced? If the method is virtual, we don't create explicit implementations. Only for non-virtuals. I modified the other tests. In reply to: 305697978 [](ancestors = 305697978) Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:2530 in 42a8f4c. [](commit_id = 42a8f4c, deletion_comment = False) |
Please check the tests starting with In reply to: 305699094 [](ancestors = 305699094) Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:2708 in 42a8f4c. [](commit_id = 42a8f4c, deletion_comment = False) |
|
@AlekseyTs comments addressed. #Closed |
| if (_refKind == RefKind.RefReadOnly) | ||
| { | ||
| var isConstType = binder.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_IsConst, diagnostics, syntax.ReturnType); | ||
| _refCustomModifiers = ImmutableArray.Create(CSharpCustomModifier.CreateRequired(isConstType)); |
There was a problem hiding this comment.
_refCustomModifiers = ImmutableArray.Create(CSharpCustomModifier.CreateRequired(isConstType)); [](start = 20, length = 94)
It feels like we could simply reuse modifiers from the invoke. #Closed
| { | ||
| private readonly RefKind refKind; | ||
| private readonly RefKind _refKind; | ||
| private readonly ImmutableArray<CustomModifier> _refCustomModifiers; |
There was a problem hiding this comment.
private readonly ImmutableArray _refCustomModifiers; [](start = 12, length = 68)
Consider replacing two these members with a reference to invoke, then implementation of RefKind and RefCustomModifiers could simply delegate to the invoke. #Closed
I noticed that you cloned the entire unit-test in order to test this scenario against compilation reference. This feel like an overkill to me, both scenarios could easily be tested right here: This isn't a blocker, but it causes an unnecessary code duplication and work duplication while tests are executed. #Closed Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:38 in b32dc89. [](commit_id = b32dc89, deletion_comment = False) |
Please add a call (applies to all similar tests) |
|
Done with review pass. #Closed |
|
@OmarTawfik - I have no actionable comments on this. Except - Can we wait with merging this until after the ref-extension are merged in and there is a build that contains them? This would be the first change that intentionally breaks "compatibility" with old compilers. It would be nice to have a builds before/after - just for comparison reasons. |
Tried to isolate the root cause. If a test fails because of the difference between metadata/image references, then one of them should succeed. In reply to: 307460674 [](ancestors = 307460674) Refers to: src/Compilers/CSharp/Test/Emit/Emit/IsConstModifierTests.cs:38 in b32dc89. [](commit_id = b32dc89, deletion_comment = False) |
|
@AlekseyTs done with latest set of comments. |
Contributes to #17287