-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Use ParamCollectionAttribute to mark params collections in metadata #71747
Use ParamCollectionAttribute to mark params collections in metadata #71747
Conversation
Corresponding speclet update is dotnet/csharplang#7850
@RikkiGibson, @333fred, @dotnet/roslyn-compiler Please review |
@RikkiGibson, @333fred, @dotnet/roslyn-compiler Please review |
@@ -124,6 +124,7 @@ private void EnsureAttributesExist(TypeCompilationState compilationState) | |||
} | |||
|
|||
ParameterHelpers.EnsureRefKindAttributesExist(moduleBuilder, Parameters); | |||
// Not emitting ParamCollectionAttribute/ParamArrayAttribute for these methods it is not a SynthesizedDelegateInvokeMethod |
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.
// Not emitting ParamCollectionAttribute/ParamArrayAttribute for these methods it is not a SynthesizedDelegateInvokeMethod | |
// Not emitting ParamCollectionAttribute/ParamArrayAttribute for these methods because it is not a SynthesizedDelegateInvokeMethod | |
``` #Closed |
@333fred, @dotnet/roslyn-compiler For the second review |
1 similar comment
@333fred, @dotnet/roslyn-compiler For the second review |
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.
Done review pass
@@ -13,6 +13,8 @@ internal partial class LocalRewriter | |||
{ | |||
public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationExpression node) | |||
{ | |||
Debug.Assert(!node.Type.IsAnonymousType); // Missing EnsureParamCollectionAttributeExists call? |
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.
Is this supposed to be a prototype comment? #Resolved
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.
Is this supposed to be a prototype comment?
No
result |= IsParamsValues.Collection; | ||
} | ||
|
||
_lazyIsParams = result; |
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.
The normal pattern we use for these is using ThreadSafeFlagsOperation.Set
. I would prefer if we did that instead. #Resolved
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.
The normal pattern we use for these is using ThreadSafeFlagsOperation.Set. I would prefer if we did that instead.
I do not think it is warranted in this case, we are not changing different bits concurrently. The value is initialized once. It used to be ThreeState
, now it is four state. Once initialized, no bits are changing.
Collection = 4, | ||
} | ||
|
||
private IsParamsValues _lazyIsParams; |
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.
If we're going to add a new flags (it looks like PackedFlags
is at its size limit), should we consider just adding a second PackedFlags
int
now, and putting these bits in there? It should have the same effect on type size, and is more consistent with the standard way we do this here. #Resolved
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.
I do not intend to merge this value into PackedFlags
and the change in the field's type doesn't increase memory used. It used to be byte backed enum and it remains byte backed enum, only the set of possible values changed. It used to be a "monolithic" value and it remains a "monolithic" value. The bit nature of the enum is simply to make it easier to work with the value, it is not like parts of the value are changing concurrently with other parts of the value. I could easily enumerate all possible combinations , encode them as regular values and work with them as such. However the code would be more complicated then.
public sealed override bool IsParams => (_parameterSyntaxKind & ParameterSyntaxKind.ParamsParameter) != 0; | ||
public sealed override bool IsParamArray => (_parameterSyntaxKind & ParameterSyntaxKind.ParamsParameter) != 0 && this.parameterType.IsSZArray(); | ||
|
||
public sealed override bool IsParamCollection => (_parameterSyntaxKind & ParameterSyntaxKind.ParamsParameter) != 0 && !this.parameterType.IsSZArray(); |
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.
Do we need to be more precise for this and actually check that the parameter type is a valid params
collection type? We validate that for IsParamArray
, and I'm a bit worried that the symmetry with naming here implies that if IsParamsCollection
is true, the parameter is a valid params
collection. #Resolved
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.
Do we need to be more precise for this and actually check that the parameter type is a valid
params
collection type?
No we do not need that, and that would actually be incorrect. If there is a params
modifier in code, one of the properties should return true. The property is not meant to indicate validity of the modifier, just its presence. Therefore, what is not a valid params
array is considered a params
collection. It used to be that original IsParams
property was true even for non-array types.
@@ -263,6 +264,14 @@ private static void EnsureRefKindAttributesExist(CSharpCompilation compilation, | |||
} | |||
} | |||
|
|||
internal static void EnsureParamCollectionAttributeExistsAndModifyCompilation(CSharpCompilation compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag diagnostics) | |||
{ | |||
if (parameters.FirstOrDefault(static (p) => p.IsParamCollection) is { } parameter) |
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.
Minor nit: it would probably be more efficient to use LastOrDefault
so we start the search from the last parameter, rather than the first, as that will be the right parameter in 99% of cases, and I don't think it matters if we report errors on the second params
parameter instead of the first if a user specified multiple in error. #Resolved
} | ||
"; | ||
|
||
private static void VerifyParamAttribute(ParameterSymbol parameter, bool isParamArray = false, bool isParamCollection = false) |
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.
Small nit: would be slightly clearer for tests if this was named VerifyParamsAndAttribute
, so that when I reading the validators I understand that this verifies both the standard params properties and attribute presence. #Resolved
} | ||
} | ||
"""; | ||
CreateCompilationWithIL(src, il).VerifyDiagnostics(); |
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.
Nit: VerifyEmitDiagnostics
#Resolved
} | ||
} | ||
"""; | ||
CreateCompilation(src).VerifyDiagnostics(); |
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.
Nit: VerifyEmitDiagnostics
#Resolved
comp = CreateCompilationWithIL("", il); | ||
|
||
test1 = comp.GetMember<MethodSymbol>("Params.Test1").Parameters.Last(); | ||
test2 = comp.GetMember<MethodSymbol>("Params.Test2").Parameters.Last(); | ||
|
||
Assert.Empty(test1.GetAttributes()); | ||
Assert.Empty(test2.GetAttributes()); | ||
|
||
VerifyParamAttribute(test1, isParamCollection: true); | ||
VerifyParamAttribute(test2, isParamArray: true); | ||
|
||
AssertEx.Equal("System.Runtime.CompilerServices.ParamCollectionAttribute", test1.GetCustomAttributesToEmit(null).Single().ToString()); | ||
AssertEx.Equal("System.ParamArrayAttribute", test2.GetCustomAttributesToEmit(null).Single().ToString()); | ||
|
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.
I'm not sure if I'm missing something, but this seems to be exactly the same as the first part of the test, minus 2 calls to VerifyParamAttribute
on test1
and test2
? #Resolved
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.
I'm not sure if I'm missing something, but this seems to be exactly the same as the first part of the test, minus 2 calls to
VerifyParamAttribute
ontest1
andtest2
?
Are you referring to the to different sets of asserts performed on "equal" compilations above?
Since PEParameterSymbol._lazyIsParams
can be initialized by two different APIs, the IsParamArray/IsParamCollection
properties and GetAttributes
, I am asserting that result doesn't depend on the order in which the APIs are used first.
ba5ee92
into
dotnet:features/ParamsCollections
Corresponding speclet update is dotnet/csharplang#7850