-
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
Generate readonlyattribute if it does not exist #18715
Generate readonlyattribute if it does not exist #18715
Conversation
@@ -609,7 +609,7 @@ IPlatformInvokeInformation PlatformInvokeData | |||
/// <summary> | |||
/// Custom attributes associated with the method's return value. | |||
/// </summary> | |||
IEnumerable<ICustomAttribute> ReturnValueAttributes { get; } | |||
IEnumerable<ICustomAttribute> GetReturnValueAttributes(CommonPEModuleBuilder moduleBuilder); |
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.
Consider passing an EmitContext
argument instead, for consistency with other Cci
interface methods. #Resolved
{ | ||
return _additionalTypes; | ||
Interlocked.CompareExchange(ref _embeddedAttributesConstructors, CreateEmbeddedAttributesIfNeeded(diagnostics), null); |
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.
Interlocked.CompareExchange [](start = 12, length = 27)
GetAdditionalTopLevelTypes
should be called once (or at most once per emit) so Interlocked.CompareExchange
shouldn't be necessary. #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.
Yes. the identity of the dictionary is unimportant. Even if we assign optimistically on multiple threads a dictionary with same contents. interlocked would not be needed. Simple assignment is sufficient.
In reply to: 113524192 [](ancestors = 113524192)
|
||
internal virtual SynthesizedAttributeData SynthesizeEmbeddedAttribute() | ||
{ | ||
// For modules, this attribute should be present. Only assemblies generate and embedd this type. |
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.
Typo here and method below: embed
#Resolved
private NamedTypeSymbol _baseType; | ||
private MissingNamespaceSymbol _namespace; | ||
private ImmutableArray<Symbol> _members; | ||
private ModuleSymbol _module; |
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.
readonly
for all fields. #Resolved
_module = compilation.SourceModule; | ||
|
||
_namespace = new MissingNamespaceSymbol(compilation.GlobalNamespace, nameParts[0]); | ||
for (int i = 1; i + 1 < nameParts.Length; i++) |
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.
_namespace = compilation.GlobalNamespace;
for (int i = 0; ...
``` #Resolved
@@ -264,7 +264,8 @@ internal enum WellKnownType | |||
|
|||
Microsoft_CodeAnalysis_Runtime_Instrumentation, | |||
|
|||
System_Runtime_CompilerServices_ReadOnlyAttribute, | |||
System_Runtime_CompilerServices_IsReadOnlyAttribute, | |||
Microsoft_CodeAnalysis_EmbeddedAttribute, |
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.
Microsoft_CodeAnalysis_EmbeddedAttribute [](start = 8, length = 40)
I am just started looking at this change, but the fact that we added this type to the well-known types list feels very surprising to me. Well-known types are not used to identify attribute applications, and, when we are embedding types we are using the types that we embed, which aren't well-known (they are hidden from everyone, but the compiler). #Closed
public SourceEmbeddedAttributeSymbol(WellKnownType wellKnownType, CSharpCompilation compilation) | ||
{ | ||
var nameParts = WellKnownTypes.GetMetadataName(wellKnownType).Split('.'); | ||
Debug.Assert(nameParts.Length > 1, "Attribute cannot be in global namespace?"); |
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 the assert necessary? #ByDesign
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'd say yes, since the code below operates on that fact.
In reply to: 113527748 [](ancestors = 113527748)
@@ -609,7 +609,7 @@ IPlatformInvokeInformation PlatformInvokeData | |||
/// <summary> | |||
/// Custom attributes associated with the method's return value. | |||
/// </summary> | |||
IEnumerable<ICustomAttribute> ReturnValueAttributes { get; } | |||
IEnumerable<ICustomAttribute> GetReturnValueAttributes(CommonPEModuleBuilder moduleBuilder); |
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.
IEnumerable GetReturnValueAttributes(CommonPEModuleBuilder moduleBuilder); [](start = 8, length = 92)
Consider passing an
EmitContext
argument instead, for consistency with otherCci
interface methods.
I agree with this suggestion, contracts in interfaces should use EmitContext. #Closed
<value>Do not use 'System.Runtime.CompilerServices.IsReadOnlyAttribute'. This is reserved for compiler usage.</value> | ||
</data> | ||
<data name="ERR_TypeReserved" xml:space="preserve"> | ||
<value>The type '{0}' is reserved to be used by the compiler.</value> |
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.
type [](start = 15, length = 4)
"type name" vs. "type" ? #Closed
@@ -1116,7 +1116,12 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio | |||
? ((AliasSymbol)symbol).GetAliasTarget(basesBeingResolved) | |||
: symbol; | |||
|
|||
if (WrongArity(symbol, arity, diagnose, options, out diagInfo)) | |||
// Check for external symbols marked with 'Microsoft.CodeAnalysis.Embedded' attribute | |||
if (!this.Compilation.SourceModule.Equals(unwrappedSymbol.ContainingModule) && unwrappedSymbol.IsHiddenByCodeAnalysisEmbeddedAttribute()) |
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.
!this.Compilation.SourceModule.Equals(unwrappedSymbol.ContainingModule) [](start = 16, length = 71)
Do we have tests covering significance of this condition? #Closed
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.
Curious - why "external" part makes a difference? I thought the embedded symbols are not bindable regardless of where defined.
In reply to: 113533730 [](ancestors = 113533730)
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.
We have tests that cover one condition. Adding more tests now.
edit: @VSadov I'm correcting this now. Lookup would fail if on embedded attributes defined somewhere else. If you define your own embedded attribute we would ignore it. UNLESS the compiler needs to generate one, then we will produce an error.
In reply to: 113549567 [](ancestors = 113549567,113533730)
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.
Could you point me to a test that demonstrates significance of the first condition?
In reply to: 114024707 [](ancestors = 114024707,113549567,113533730)
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.
Please check AttributeTests_Embedded.cs
:
ReferencingEmbeddedAttributesFromADifferentAssemblyFails_Internal
ReferencingEmbeddedAttributesFromADifferentAssemblyFails_Public
and
EmbeddedAttributeInSourceShouldTriggerAnErrorIfCompilerNeedsToGenerateOne
In reply to: 115050349 [](ancestors = 115050349,114024707,113549567,113533730)
@@ -30,6 +31,8 @@ public partial class CSharpCompilation | |||
/// </summary> | |||
private Symbol[] _lazyWellKnownTypeMembers; | |||
|
|||
internal bool NeedsGeneratedIsReadOnlyAttribute { get; private set; } |
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.
internal bool NeedsGeneratedIsReadOnlyAttribute { get; private set; } [](start = 8, length = 69)
It would be good to have a comment when one should expect this property to have actual value. We should probably have a way to "freeze" the value and a way to assert an attempt to set the value it after it was frozen and an attempt to read the value before it is frozen. #Closed
member: WellKnownMember.System_Runtime_CompilerServices_IsReadOnlyAttribute__ctor, | ||
diagnostics: DeclarationDiagnostics, | ||
location: readOnlySymbol.Locations.FirstOrDefault() ?? Location.None, | ||
isOptional: Options.OutputKind != OutputKind.NetModule); |
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.
isOptional: Options.OutputKind != OutputKind.NetModule [](start = 16, length = 54)
It feels like this doesn't give us the behavior we want. I think we want to embed the attribute only if the type cannot be found, the lack of the required constructor should result in an error rather than in an embedded attribute. #Closed
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 the attribute defined in current module and does not have right constructor, embedding another attribute would cause a conflict. Is that the reason for giving an error?
In reply to: 113537098 [](ancestors = 113537098)
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.
Good point. I think the lookup should be modified to give a different error if the type was found but it was lacking the correct signature
In reply to: 113554955 [](ancestors = 113554955,113537098)
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.
Good point. I think the lookup should be modified to give a different error if the type was found but it was lacking the correct signature
I think the existing error for a missing well-known member will work just fine.
In reply to: 114047541 [](ancestors = 114047541,113554955,113537098)
var attributeSymbol = Binder.GetWellKnownTypeMember( | ||
compilation: this, | ||
member: WellKnownMember.System_Runtime_CompilerServices_IsReadOnlyAttribute__ctor, | ||
diagnostics: DeclarationDiagnostics, |
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.
DeclarationDiagnostics [](start = 29, length = 22)
Rather than adding diagnostics to DeclarationDiagnostics directly, we probably should use Symbol.AddDeclarationDiagnostics API instead as some symbols customize where declaration diagnostics should go. #Closed
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.
Moving to a model where diagnosticsbag is defined by the syntax needing it whenever possible.
In reply to: 113538171 [](ancestors = 113538171)
@@ -415,10 +418,22 @@ internal SynthesizedAttributeData SynthesizeDebuggerStepThroughAttribute() | |||
return TrySynthesizeAttribute(WellKnownMember.System_Diagnostics_DebuggerStepThroughAttribute__ctor); | |||
} | |||
|
|||
internal SynthesizedAttributeData SynthesizeReadOnlyAttribute() | |||
internal void EnsureIsReadOnlyAttributeExists(Symbol readOnlySymbol) |
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.
EnsureIsReadOnlyAttributeExists [](start = 22, length = 31)
Please ensure we have tests for local functions. #Closed
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've a test for the positive case. Will add a test for the negative case as well.
In reply to: 113539714 [](ancestors = 113539714)
/// <summary> | ||
/// Lazily initiated HasEmbeddedAttribute | ||
/// </summary> | ||
private ThreeState _lazyHasEmbeddedAttribute = ThreeState.Unknown; |
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.
Should this be part of UncommonProperties
rather than a field directly on PENamedTypeSymbol
? #Closed
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.
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.
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.
UncommonProperties
are only instantiated for PENamedTypeSymbols
with uncommon attributes. The instances of UncommonProperties
are not shared. #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.
|
||
private void CreateEmbeddedAttributeIfNeeded(ref SynthesizedEmbeddedAttributeSymbol symbol, DiagnosticBag diagnostics, AttributeDescription description) | ||
{ | ||
if ((object)symbol == null) |
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 there still a case where symbol
can be null
? If not, remove the null
check and perhaps return symbol
rather than using a ref
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.
This is possibly going to be rewritten in Vlad's next PR, as there are more attributes to be generated. His feature will possibly need to generate some attributes that are generated in this PR, so the check will be necessary.
I see the future of embedded attributes going in one of two directions:
- keeping the current design, having each feature calling
CreateEmbeddedAttributeIfNeeded
on the attributes it needs (possibly overlapping), and thus the current design protects against that. - If the features that need embedded attributes grow in numbers, there might be a need for an independent type manager to hold/manage them.
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.
For now we are adding only IsByRefLike
, so there is no need for #2
just yet.
The current code seems ok to me for now.
{ | ||
factory.ExpressionStatement(baseConstructorCall), | ||
factory.Return(), | ||
}; |
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 point: factory.Block()
has overload with params
array so these could be inlined below. #Resolved
}"; | ||
|
||
CreateCompilationWithMscorlib(code, assemblyName: "testModule").VerifyEmitDiagnostics( | ||
// (4,18): error CS8413: The type name 'EmbeddedAttribute' is reserved to be used by the compiler. |
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.
Consider reporting the fully qualified type name so it's clear we're not reserving the type name regardless of namespace. #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.
Good point.
// error CS0518: Predefined type 'System.Attribute' is not defined or imported | ||
Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound).WithArguments("System.Attribute").WithLocation(1, 1), | ||
// error CS0518: Predefined type 'System.Attribute' is not defined or imported | ||
Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound).WithArguments("System.Attribute").WithLocation(1, 1)); |
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.
Duplicate error?
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.
No. because the compiler is trying to synthesize two attributes here (Embedded
and IsReadOnly
).
// error CS1729: 'Attribute' does not contain a constructor that takes 0 arguments | ||
Diagnostic(ErrorCode.ERR_BadCtorArgCount).WithArguments("System.Attribute", "0").WithLocation(1, 1), | ||
// error CS1729: 'Attribute' does not contain a constructor that takes 0 arguments | ||
Diagnostic(ErrorCode.ERR_BadCtorArgCount).WithArguments("System.Attribute", "0").WithLocation(1, 1)); |
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.
Duplicate error?
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.
No. because the compiler is trying to synthesize two attributes here (Embedded
and IsReadOnly
).
LGTM |
@cston @AlekseyTs addressed the latest set of comments. |
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.
Please wait for sign-off from me.
builder.AddRange(_additionalTypes); | ||
|
||
CreateEmbeddedAttributesIfNeeded(); | ||
diagnostics.AddRange(_lazyEmbeddedAttributesDiagnostics); |
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.
diagnostics.AddRange(_lazyEmbeddedAttributesDiagnostics); [](start = 12, length = 57)
This is likely to cause duplication of errors. We should avoid that, we specifically avoid reporting duplicate errors in PEModuleBuilder, see _reportedErrorTypesMap for example. Please revert to the previous approach.
{ | ||
private int value; | ||
|
||
public override ref readonly int Method(ref readonly int x) => ref value; |
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.
public override ref readonly int Method(ref readonly int x) => ref value; [](start = 3, length = 74)
The override scenarios should be tested inside IsReadOnlyAttribute type. This test could be valuable too, but it doesn't test most interesting scenario.
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.
Similar for explicit interface implementations. In these tests, test both scenarios when base type/interface is defined in the same assembly and when it is defined in a different assembly.
} | ||
|
||
[Fact] | ||
public void RefReadOnlyDefinitionsInsideUserDefinedIsReadOnlyAttribute_Class_CorrectParent() |
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.
RefReadOnlyDefinitionsInsideUserDefinedIsReadOnlyAttribute_Class_CorrectParent [](start = 20, length = 78)
Added tests for indexers, operators, properties, and overrides. There is already a test that verifies we produce errors if IsReadOnly was an interface, because it is lacking the constructor.
I don't see any tests added for explicit interface implementations. I believe I asked for them in the previous review pass.
Done with review pass. I believe the remaining issues that I track are summarized in the last three comments above. At the very least the requested tests should be added before the PR can be merged, I think. |
@AlekseyTs added the tests requested in your last comment in 40748ee About your other comment:
This contradicts with the point @cston pointed out. Basically, if I reverted, first call to |
Yes, I believe we should revert the behavior. There is no requirement for this API to produce the same errors on every call. In fact, I believe it is preferred that this API wouldn't produce errors due to the same reasons on the second and subsequent calls. |
@@ -703,6 +704,13 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, | |||
Debug.Assert(explicitInterfaceSpecifier != null); | |||
_explicitInterfaceType.CheckAllConstraints(conversions, new SourceLocation(explicitInterfaceSpecifier.Name), diagnostics); | |||
} | |||
|
|||
if (_refKind == RefKind.RefReadOnly) |
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 (_refKind == RefKind.RefReadOnly) [](start = 12, length = 36)
It feels like we should be moving similar checks for all source symbols into an override of AfterAddingTypeMembersChecks API. This should ensure that the checks won't cause circularity issues in the future due to some changes in the compiler's behavior.
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 don't think the general rule applies, because for some symbols like SourceConstructorMethod
we need the parameters to be computed first before we perform that check, and calling Parameters
in AfterAddingTypeMembersChecks
would call MethodChecks
in turn.
For SourcePropertySymbol
, it wasn't necessary since it computes parameters without calling MethodChecks
. Some symbols I believe don't even use this API like LocalFunctionSymbol
.
|
||
if (_refKind == RefKind.RefReadOnly) | ||
{ | ||
DeclaringCompilation.EnsureIsReadOnlyAttributeExists(diagnostics, _location, modifyCompilationForRefReadOnly: true); |
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.
_location [](start = 82, length = 9)
I didn't notice any location changes for errors produced by this call. Is location not changed in iteration 18, or do we have a test gap?
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.
We have tests covering these diagnostics, like IsReadOnlyAttributeExistsWithWrongConstructorSignature_Assembly
. However, Covering every permutation for every symbol type and error type and location (parameter vs return type) I believe should be left to the final test plan (lot's of moving pieces right now in this feature).
However, I've added 5 more tests for symbols for that specific error location for this PR.
@AlekseyTs I reverted the change and added a few more tests. Also answered the last comment about |
@AlekseyTs moved calls of |
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.
LGTM, modulo the failing tests and merge conflicts.
Things done in this PR:
GetAttributes()
andGetReturnTypeAttributes()
of various helpers through out the code base to accept a parameter of typePEModuleBuilder
instead ofCompilationState
.IsReadOnly
attribute on the compilation object.PEAssemblyBuilder
uses that fact and either generates that type during emitting, or gives an error if the user also defines that type in the same compilation.Microsoft.CodeAnalysis.EmbeddedAttribute
that both C# and VB compilers recognize and ignore types annotated by it during look up. All types embedded will have both this attribute andCompilerGeneratedAttribute
.cc @dotnet/roslyn-compiler @VSadov @AlekseyTs