Skip to content

Set IsClosedTypeAttribute.DerivedTypes#84350

Open
RikkiGibson wants to merge 10 commits into
dotnet:mainfrom
RikkiGibson:cc-metadata
Open

Set IsClosedTypeAttribute.DerivedTypes#84350
RikkiGibson wants to merge 10 commits into
dotnet:mainfrom
RikkiGibson:cc-metadata

Conversation

@RikkiGibson

@RikkiGibson RikkiGibson commented Jul 1, 2026

Copy link
Copy Markdown
Member

Implement metadata format change from dotnet/runtime#129009

Notes:

  • Compiler doesn't use DerivedTypes property as a source of information at all and (in this PR at least) doesn't verify correctness of the attribute when importing types from metadata.
  • The DerivedTypes property does not need to exist, but, compiler will report an error if it doesn't have the expected shape. Perhaps we might eventually want to require the property to exist, but, for now we need to be compatible with frameworks where the property is missing.
Microsoft Reviewers: Open in CodeFlow
@RikkiGibson RikkiGibson marked this pull request as ready for review July 1, 2026 04:43
@RikkiGibson RikkiGibson requested a review from a team as a code owner July 1, 2026 04:43
Copilot AI review requested due to automatic review settings July 1, 2026 04:43
@RikkiGibson

Copy link
Copy Markdown
Member Author

@eiriktsarpalis for visibility

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the DerivedTypes property and System.Type are available.
  • Add a new WellKnownMember entry for IsClosedTypeAttribute.DerivedTypes and update “missing platform member” test allowlists (C# + VB).
  • Expand closed-classes tests to validate the emitted metadata encoding for DerivedTypes across 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.
Comment on lines +1829 to +1833
AddSynthesizedAttribute(
ref attributes,
compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_IsClosedTypeAttribute__ctor));
compilation.TrySynthesizeAttribute(
WellKnownMember.System_Runtime_CompilerServices_IsClosedTypeAttribute__ctor,
namedArguments: namedArguments));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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())));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)})"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@AlekseyTs AlekseyTs Jul 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If IL verification passes, I think we should be good.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and not ErrorTypeSymbol

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@AlekseyTs

Copy link
Copy Markdown
Contributor

There is no requirement for DerivedTypes property to exist or to be well-formed.

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CandidateClosedSubtypeDefinitions

Will this sequence order-wise always match what CandidateClosedSubtypeDefinitions would return for a type imported from metadata?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's possible that the order will diverge when it comes to certain nested types scenarios.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[]

default?

comp.MakeTypeMissing(WellKnownType.System_Type);

var verifier = CompileAndVerify(comp, symbolValidator: verifyMetadata, verify: Verification.Skipped);
verifier.VerifyDiagnostics();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifier.VerifyDiagnostics();

I would expect an error for this scenario

}
""";

var comp = CreateCompilation([source, isClosedTypeAttribute], targetFramework: TargetFramework.Net100);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TargetFramework.Net100

I guess we are skipping IL verification for many of the tests because we use this target framework. Why is it necessary?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confusion. The IL verification that I was referring to is the PEVerify style verification. The verify: parameter of CompileAndVerify

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the DerivedTypesMetadata tests which CompileAndVerify to use the default for verify: (i.e. enforce that verify passes).

@AlekseyTs

Copy link
Copy Markdown
Contributor

Done with review pass (commit 4)

Copilot AI review requested due to automatic review settings July 1, 2026 21:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Comment on lines +1989 to +2004
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);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs Outdated
Comment thread src/Compilers/CSharp/Test/CSharp15/ClosedClassesTests.cs Outdated
Comment thread src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs Outdated
Copilot AI review requested due to automatic review settings July 1, 2026 22:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Comment on lines +1807 to +1811
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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +2010 to +2017
var allDerivedTypesSymbols = isClosedTypeAttributeCtor.ContainingType.GetMembers("DerivedTypes");
foreach (var derivedTypesSymbol in allDerivedTypesSymbols)
{
if (!derivedTypesSymbol.Equals(wellKnownDerivedTypesProperty, TypeCompareKind.AllIgnoreOptions))
{
diagnostics.Add(ErrorCode.ERR_ClosedBadDerivedTypesProperty, location);
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this matters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

4 participants