Set IsClosedTypeAttribute.DerivedTypes#84350
Conversation
|
@eiriktsarpalis for visibility |
There was a problem hiding this comment.
Pull request overview
This PR updates Roslyn’s compiler-emitted metadata for System.Runtime.CompilerServices.IsClosedTypeAttribute to include the new DerivedTypes named argument when available, and adjusts related well-known-member plumbing and tests to recognize the additional member.
Changes:
- Emit
IsClosedTypeAttribute(DerivedTypes = …)from the C# compiler when theDerivedTypesproperty andSystem.Typeare available. - Add a new
WellKnownMemberentry forIsClosedTypeAttribute.DerivedTypesand update “missing platform member” test allowlists (C# + VB). - Expand closed-classes tests to validate the emitted metadata encoding for
DerivedTypesacross multiple scenarios (including malformed/missing members).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs | Emits IsClosedTypeAttribute with a DerivedTypes named argument when possible. |
| src/Compilers/Core/Portable/WellKnownMember.cs | Adds a new well-known member enum value for IsClosedTypeAttribute__DerivedTypes. |
| src/Compilers/Core/Portable/WellKnownMembers.cs | Adds descriptor + name entry for the DerivedTypes well-known property. |
| src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs | Updates the test definition of IsClosedTypeAttribute to include DerivedTypes. |
| src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs | Updates the test allowlist to treat DerivedTypes as not-yet-in-platform. |
| src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb | Updates VB allowlists to include IsClosedTypeAttribute__DerivedTypes. |
| src/Compilers/CSharp/Test/CSharp15/ClosedClassesTests.cs | Updates IL baselines and adds/extends coverage validating DerivedTypes metadata emission. |
| AddSynthesizedAttribute( | ||
| ref attributes, | ||
| compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_IsClosedTypeAttribute__ctor)); | ||
| compilation.TrySynthesizeAttribute( | ||
| WellKnownMember.System_Runtime_CompilerServices_IsClosedTypeAttribute__ctor, | ||
| namedArguments: namedArguments)); |
There was a problem hiding this comment.
It's not obvious to me how to generate a use site error which is specific to DerivedTypes property, in order to exercise the scenario, except perhaps via the property type, which seems not straightforward to exercise.
There was a problem hiding this comment.
Looking at the impl of TrySynthesizeAttribute, I think my null-check on the derivedTypesProperty was already enough to expect the property to not cause us to fail synthesis.
It doesn't seem wrong to check for use site error though? I'll think about it a bit more in the morning.
There was a problem hiding this comment.
@RikkiGibson If that helps in your investigation, any member can be made "bad" with unsupported CompilerFeatureRequiredFeatures attribute applied to it.
| var derivedTypesConstant = new TypedConstant( | ||
| arrayOfSystemType, | ||
| CandidateClosedSubtypeDefinitions.SelectAsArray( | ||
| subtype => new TypedConstant(systemType, TypedConstantKind.Type, subtype.GetUnboundGenericTypeOrSelf()))); |
There was a problem hiding this comment.
nit: consider using overload of SelectAsArray taking an extra argument, and passing static lambda
| var peModule = (PEModuleSymbol)module; | ||
| var peType = (PENamedTypeSymbol)module.GlobalNamespace.GetMember<NamedTypeSymbol>("C"); | ||
| AssertEx.SetEqual([ | ||
| "System.Runtime.CompilerServices.IsClosedTypeAttribute(DerivedTypes = {typeof(D1), typeof(D2)})" |
There was a problem hiding this comment.
Is this going to throw when loaded by reflection? Wouldn't it be better to have a well-known member for the setter instead of the property to match how reflection works?
There was a problem hiding this comment.
If IL verification passes, I think we should be good.
There was a problem hiding this comment.
I tried to match the pattern established by e.g. WellKnownMember.System_Diagnostics_DebuggerDisplayAttribute__Type. In this case the property is the only thing mentioned in the attribute blob, the setter is essentially ignored.
But, I went ahead and added an expected shape validation of the DerivedTypes property, which will complain if the setter is missing.
|
|
||
| ImmutableArray<KeyValuePair<WellKnownMember, TypedConstant>> namedArguments; | ||
| // Note: some definitions of IsClosedTypeAttribute may be missing DerivedTypes property. | ||
| if (systemType is not null and not ErrorTypeSymbol && derivedTypesProperty != null && !derivedTypesProperty.HasUseSiteError) |
There was a problem hiding this comment.
This feels somewhat wrong. If property is there, but we are missing a reference for System.Type, we silently won't emit the property. Perhaps, when we are checking for a presence of the System_Runtime_CompilerServices_IsClosedTypeAttribute__ctor, we should also check that System.Type is "good" (and report an error otherwise) when the property is found.
And, BTW, in general, a type check for ErrorTypeSymbol doesn't detect all "bad" scenarios.
There was a problem hiding this comment.
I'm using Binder.GetWellKnownType() now to get the System.Type symbol and expecting it to report an error if the System.Type is bad
Consider going with the rule: "If the property is present, it should be good, or an error is reported." |
| var arrayOfSystemType = ArrayTypeSymbol.CreateSZArray(systemType.ContainingAssembly, TypeWithAnnotations.Create(systemType, NullableAnnotation.NotAnnotated)); | ||
| var derivedTypesConstant = new TypedConstant( | ||
| arrayOfSystemType, | ||
| CandidateClosedSubtypeDefinitions.SelectAsArray( |
There was a problem hiding this comment.
I think it's possible that the order will diverge when it comes to certain nested types scenarios.
There was a problem hiding this comment.
Basically, the order of types in DerivedTypes property will be deterministic, but, the order might be different than CandidateClosedSubtypeDefinitions returns for the same closed type.
This would come down to difference in the order that the TypeDef table is populated versus the order the source module is traversed in order to discover the subtypes when producing the DerivedTypes argument.
| } | ||
| else | ||
| { | ||
| namedArguments = []; |
| comp.MakeTypeMissing(WellKnownType.System_Type); | ||
|
|
||
| var verifier = CompileAndVerify(comp, symbolValidator: verifyMetadata, verify: Verification.Skipped); | ||
| verifier.VerifyDiagnostics(); |
| } | ||
| """; | ||
|
|
||
| var comp = CreateCompilation([source, isClosedTypeAttribute], targetFramework: TargetFramework.Net100); |
There was a problem hiding this comment.
I think we can still VerifyTypeIL when passing this. I didn't feel that such baselines were very "illustrative" though due to the attribute arguments being displayed as a hex blob.
So I mostly went with using metadata symbol helpers to dig the attribute arguments back out, and, I'm adding some runtime tests to dig through the attribute via reflection as well.
In general, we can run tests without this TargetFramework, it just requires us passing more source declarations, such as CompilerFeatureRequiredAttribute. See test Subtypes_Retargeting_01 for example.
There was a problem hiding this comment.
Sorry for the confusion. The IL verification that I was referring to is the PEVerify style verification. The verify: parameter of CompileAndVerify
There was a problem hiding this comment.
I changed the DerivedTypesMetadata tests which CompileAndVerify to use the default for verify: (i.e. enforce that verify passes).
|
Done with review pass (commit 4) |
| Binder.ReportUseSite(wellKnownDerivedTypesProperty, diagnostics, location); | ||
| _ = Binder.GetWellKnownType(compilation, WellKnownType.System_Type, diagnostics, location); | ||
|
|
||
| var isValid = wellKnownDerivedTypesProperty is | ||
| { | ||
| IsStatic: false, | ||
| GetMethod.DeclaredAccessibility: Accessibility.Public, | ||
| SetMethod.DeclaredAccessibility: Accessibility.Public, | ||
| RefKind: RefKind.None, | ||
| ParameterCount: 0, | ||
| Type: ArrayTypeSymbol { ElementType: var elementType } | ||
| } && elementType.Equals(compilation.GetWellKnownType(WellKnownType.System_Type), TypeCompareKind.AllIgnoreOptions); | ||
| if (!isValid) | ||
| { | ||
| diagnostics.Add(ErrorCode.ERR_ClosedBadDerivedTypesProperty, location); | ||
| } |
There was a problem hiding this comment.
I don't think systemType being an error type is worrisome here. The Binder.GetWellKnownType is supposed to report an error in that case, which is good enough.
I'll fix the code to avoid redundant lookup of the systemType though.
| var systemType = compilation.GetWellKnownType(WellKnownType.System_Type); | ||
| var derivedTypesProperty = compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_IsClosedTypeAttribute__DerivedTypes); | ||
|
|
||
| ImmutableArray<KeyValuePair<WellKnownMember, TypedConstant>> namedArguments; | ||
| if (derivedTypesProperty != null) |
There was a problem hiding this comment.
I don't agree with the comment, we will report a use site error in this case and I think it doesn't matter if we fail to synthesize the attribute.
I'll take a moment to actually add the test case though.
| var allDerivedTypesSymbols = isClosedTypeAttributeCtor.ContainingType.GetMembers("DerivedTypes"); | ||
| foreach (var derivedTypesSymbol in allDerivedTypesSymbols) | ||
| { | ||
| if (!derivedTypesSymbol.Equals(wellKnownDerivedTypesProperty, TypeCompareKind.AllIgnoreOptions)) | ||
| { | ||
| diagnostics.Add(ErrorCode.ERR_ClosedBadDerivedTypesProperty, location); | ||
| } | ||
| } |
There was a problem hiding this comment.
I don't think this matters.
Implement metadata format change from dotnet/runtime#129009
Notes:
Microsoft Reviewers: Open in CodeFlow