Skip to content

Conversation

DoctorVanGogh
Copy link
Contributor

This fixes #1420


Turned out it was more than a single place where RequiredMemberAttribute/SetsRequiredMemberAttribute were used. Centralized the logic and removed any NET7+ conditional constraints on source & tests.

Tests include a .NET6 Target Framework which has no inbuilt support for required.
Added the polyfill nuget package in there to allow use of required and have tests subsequently passing on NET6.

Copy link

codecov bot commented Aug 11, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 78.55%. Comparing base (ec4907f) to head (e68c1b8).
Report is 7 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1421      +/-   ##
===========================================
- Coverage    78.57%   78.55%   -0.03%     
===========================================
  Files          200      200              
  Lines         5713     5716       +3     
  Branches      1168     1168              
===========================================
+ Hits          4489     4490       +1     
- Misses         712      713       +1     
- Partials       512      513       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@DoctorVanGogh
Copy link
Contributor Author

Small addendum:
I've looked at ReflectionActivator.cs and I cannot make sense of its check for required members.

Even before 4547d16 a check was done on a type for a RequiredMemberAttribute. Types don't carry that attribute. Those attributes are on fields or properties.:

The required modifier can be applied to fields and properties ...1

I've tweaked this check slightly in DoctorVanGogh/Autofac@1608ec7. Should that be rolled into this PR?

Would it make sense to "improve" the analysis memoization a bit more? I've made it cache per-type in DoctorVanGogh/Autofac@6046e61.
Should this be extended for the added helper methods in ReflectionExtensions.cs?
Should that reuse the ReflectionCacheSet.Shared.Internal.HasRequiredMemberAttribute cache? That would then be a dual store with Type and Field|PropertyInfo keys where Type entries mean "it has any required member" and for others it means "this property/field is required".

🤔

Footnotes

  1. From https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required

@alistairjevans
Copy link
Member

If you decompile a binary with required properties, you will find that the types do carry the attribute, indicating that at least one property in the type is required. See spec (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation).

We use that as an optimisation to branch down the more expensive code path for the type only if necessary, so I'd leave that in please.

With that knowledge I suspect the memoisation holds as-is?

@DoctorVanGogh
Copy link
Contributor Author

Ah, hadn't actually checked the types themselves with a decompiler, only the properties. That check makes sense then.

As far as I'm concerned, this PR is good to go as-is then.

The memoization still could be tweaked to not just store composite results but cache intermediate analysis as well, but if you're doing that, you could also cache per-property/constructor analysis too and just stick it all into the helper methods.
But I think that would warrant its own PR then.

/// <em>only</em> requires an attribute with that specific type <em>name</em>, not that specific type <em>reference</em>.
/// </para>
/// <para>
/// This could very well be an internally defined custom polyfill attribute using that type name (for example
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to not list the names of other packages in our docs. I get that it's internal/private, but it may appear that we're "endorsing" the package, where I don't think we can really speak to whether this is the right answer for everyone.

Copy link
Member

Choose a reason for hiding this comment

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

Looks like this also appears below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'll strip this from the xmldocs. Was merely traing to retain the rationale for the non-reference checks.

Would sticking things inside a plain // ... code comment still be endorsing? Or should I just go with "for example imported via nuget or entirely self-supplied".

On that same note:
Two Test projects now have a PackageReference to that Required package (to get the required feature working in .NET6). Are those okay?

Copy link
Member

Choose a reason for hiding this comment

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

I'd probably just leave it generic with "... an internally defined custom polyfill attribute with the type name" and call it a day. We don't have to point to a specific implementation.

It may be worth actually putting our own polyfill in place in the test projects to show how you can do it without referencing a whole polyfill package, unless that's a lot of work. We don't have a lot of dependencies that aren't framework.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, removed the mentions and added the custom polyfill in f5346a7

Had to muzzle stylecop slightly to retain the polyfill's original copyright notice & license. If you have a more "structured" process to include third party notices, feel free to tweak that yourself ;)

Copy link
Member

Choose a reason for hiding this comment

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

It would have been nice to maybe create these from scratch instead of copying them out of an existing library. Part of the point is to show folks how to do this without references and without extra licensing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, considering the polyfill is purely functional and has to take that specific shape with those specific class names & preprocessor guards (okay, one could skip the nullable directives and warning toggles) and me knowing where I saw this, whatever I did would always be a "derivative" of a pre-existing version.
And if that's the case I might as well acknowledge where it came from... I'm not too fond of not-invented-here-syndrome, and considering the pre-existing polyfill is MIT licensed.... I'm even less tempted to pretend having done a clean room implementation ;)

Copy link
Member

Choose a reason for hiding this comment

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

@alistairjevans I'll leave this one for you. My thoughts:

  • Don't keep the preprocessor directives if we don't need them.
  • One class per file, even for the polyfills.
  • Possibly make them public rather than internal since they're in the test project anyway.
  • It may be arguably fundamentally different enough that it's unique but I'm not sure I care enough to argue it beyond that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • Possibly make them public rather than internal since they're in the test project anyway.

I'd strongly discourage that. (At least if the polyfill is supposed to stand as an example for how to do this). Imagine the scenario of someone (re)using that example with public attributes. They build a netstandard2 assembly with it. That assembly then gets consumed in a NET7+ project. Suddenly you have duplicate attribute definitions and a lot of compiler barfing.

Copy link
Member

@alistairjevans alistairjevans Aug 12, 2024

Choose a reason for hiding this comment

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

I'm not fussed about making the shims public; they're defined by the .NET Compiler as internal, so if we're looking to create proper shims, we should do the same. See my other note about them being the 'formal' spec for the attribute.

I would like to be clear though that this polyfill in this test project is absolutely not an example of how to polyfill the required attributes. I'd really hope no-one ends up using an Autofac test project as an example of polyfilling in a distributed library.

DoctorVanGogh and others added 3 commits August 12, 2024 19:02
Co-authored-by: Travis Illig <tillig@paraesthesia.com>
Co-authored-by: Travis Illig <tillig@paraesthesia.com>
remove Required nuget package & mentions
Copy link
Member

@alistairjevans alistairjevans left a comment

Choose a reason for hiding this comment

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

Nice! This looks clean, thanks for making the licensing changes.

@alistairjevans alistairjevans merged commit 488294e into autofac:develop Aug 14, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Autofac does not correctly process required members for NetFramework (or any version of .NET < 7.0)
3 participants