Skip to content
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

Implement DAMT.*WithInherited #109608

Merged
merged 4 commits into from
Nov 11, 2024
Merged

Conversation

MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Nov 7, 2024

Fixes #88512.

Add support for NonPublicConstructorsWithInherited et al in the shape that was approved by API review.

We want a bit for each of these new inherited options because e.g. if I want a combination of non-public fields (including inherited) and public constructors (but not inherited), this shouldn't mean to inherit all constructors. So we need an extra bit for each "inherited" possibility.

We also set the existing bit just for convenience so that bit operations still work nicely (i.e. it would be sufficient to do NonPublicConstructorsWithInherited = 0x4000, but NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000 makes working with the enums easier because there's still the subset relationship and we can bit test without special casing.

The original proposal had two bits less (just "inherit constructors" and "inherit nested types") but the API review extended it to the more granular "inherit public constructors" and "inherit private constructors". Solely because the problem with .All is that it's too broad and we could have the same problem with "inherit constructors" being too broad in the future.

Cc @dotnet/illink @dotnet/ilc-contrib @eerhardt

@dotnet-issue-labeler dotnet-issue-labeler bot added area-Tools-ILLink .NET linker development as well as trimming analyzers new-api-needs-documentation labels Nov 7, 2024
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

1 similar comment
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

/// <summary>
/// Specifies all non-public constructors, including those inherited from base classes.
/// </summary>
NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000,
Copy link
Member

Choose a reason for hiding this comment

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

I haven't seen any discussion on the actual numerical values used here.
Why don't we add a "flag" which indicates "inherited"? Is it because there would be possible values which don't have any semantic meaning?
Alternatively we would not need to declare the flag, just use numeric values which effectively use the flag. In reality here we're introducing a new flag-like numeric value for each kind of metadata, which doesn't seem necessary. It consumes lot of available bits in this enum.

Copy link
Member Author

Choose a reason for hiding this comment

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

It was discussed here: https://www.youtube.com/watch?v=uyXehmcAkHc&t=0h9m31s.

We want a bit for each of these because e.g. if I want a combination of non-public fields (including inherited) and public constructors (but not inherited), this shouldn't mean to inherit all constructors. So we need an extra bit for each "inherited" possibility.

We also set the existing bit just for convenience so that bit operations still work nicely (i.e. it would be sufficient to do NonPublicConstructorsWithInherited = 0x4000, but NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000 makes working with the enums easier because there's still the subset relationship and we can bit test without special casing.

The original proposal had two bits less (just "inherit constructors" and "inherit nested types") but the API review extended it to the more granular "inherit public constructors" and "inherit private constructors". Solely because the problem with .All is that it's too broad and we could have the same problem with "inherit constructors" being too broad in the future.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks a lot - makes sense.
Nit: Would be good to write this down somewhere in the issue or this PR - I have to admit I didn't watch the video.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've moved the explanation to the PR description so that it's at the top.


namespace System.Diagnostics.CodeAnalysis
{
public static class DynamicallyAccessedMemberTypesEx
Copy link
Member

Choose a reason for hiding this comment

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

Just curious why we need to add this? Is it that we don't have live flow of bits between the libraries and the trimmer code base here? If that is the case, migh be worth adding a comment so that eventually we delete this.

Copy link
Member Author

Choose a reason for hiding this comment

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

ILLink is needed to build the framework so we can't build it with the live framework.

Copy link
Member

@sbomer sbomer left a comment

Choose a reason for hiding this comment

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

Nice, thank you!

@@ -34,13 +34,15 @@ public static IEnumerable<TypeSystemEntity> GetDynamicallyAccessedMembers(this T

if (memberTypes.HasFlag(DynamicallyAccessedMemberTypes.NonPublicConstructors))
{
foreach (var c in typeDefinition.GetConstructorsOnType(filter: null, bindingFlags: BindingFlags.NonPublic))
bool withInherited = !declaredOnly && memberTypes.HasFlag(DynamicallyAccessedMemberTypesEx.NonPublicConstructorsWithInherited);
foreach (var c in typeDefinition.ApplyIncludeInherited(t => t.GetConstructorsOnType(filter: null, bindingFlags: BindingFlags.NonPublic), withInherited))
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we can't make GetConstructorsOnType (and similar) walk up the base types, based on the binding flags passed in? (Is it just that we're trying to keep GetConstructorsOnType aligned with the reflection semantics?)

Copy link
Member Author

Choose a reason for hiding this comment

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

I spent a good part of the day trying to figure out how to do this :)

The problem is that all of these APIs take BindingFlags, which is a public enum with documented meaning of all the flags. So:

  • Private means: do not recurse in bases.
  • DeclaredOnly also has a meaning for publics (it means do not recurse for publics)

I had an extra bool that says "please recurse into bases", but the extra argument then conflicts with the default reflection semantics and with whatever BindingFlags specify. This didn't feel particularly readable.

Then I considered just not using BindingFlags here, but we do read random BindingFlags using dataflow and just send them over to here so we need some translation to exist. We also sometimes inject DeclaredOnly by ourselves.

All in all what I have here felt like the cleanest because WithInherited behavior handling can be restricted to this one method instead of complicating the surface area everywhere.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense, thanks for the explanation!

@MichalStrehovsky
Copy link
Member Author

/ba-g timeout in an unrelated wasm leg

@MichalStrehovsky MichalStrehovsky merged commit f7334fa into dotnet:main Nov 11, 2024
154 of 159 checks passed
@MichalStrehovsky MichalStrehovsky deleted the fix88512 branch November 11, 2024 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Tools-ILLink .NET linker development as well as trimming analyzers new-api-needs-documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add DynamicallyAccessedMemberTypes.AllMethods/.AllFields/.AllEvents/etc
3 participants