Skip to content

[Feature]: Add trimming and NativeAOT annotations so consumers stay warning-free #164

Description

@dennisdoomen

Background and motivation

Reflectify contains no trimming or AOT annotations at all: no [DynamicallyAccessedMembers], no [RequiresUnreferencedCode], no [UnconditionalSuppressMessage].

This matters more for Reflectify than for a normal library, because of how it is delivered. It is a content-only package: the source is compiled directly into the consumer's assembly. So when a consumer publishes with PublishTrimmed or NativeAOT, the trim analyzer walks Reflectify's code as if the consumer wrote it, and reports warnings against the consumer's own project. The consumer cannot suppress them at the package boundary and cannot fix them without editing generated source.

Every hierarchy walk in Reflector is a warning site. For example:

var allProperties = typeToReflect.GetProperties(flags);

and

return type
    .GetMethods(flags)
    .SingleOrDefault(m => m.Name == methodName && HasSameParameters(parameterTypes, m));

Both take a Type with no annotation, so the analyzer cannot prove the members survive trimming and emits IL2070-class warnings. Same for GetMethod("Equals", ...) in OverridesEquals, the "<Clone>$" and "PrintMembers" lookups in the record detection, and every GetCustomAttributes call.

Annotating the API would let consumers who are trim-clean stay trim-clean. Because the parameters are Type, the annotations mostly land on the public extension methods and flow inward.

PolySharp is already a dependency and can generate DynamicallyAccessedMembersAttribute, RequiresUnreferencedCodeAttribute and UnconditionalSuppressMessageAttribute for the older targets, so the multi-targeting story is already solved.

Alternative Concerns

  • Annotate everything with [DynamicallyAccessedMembers]. The correct fix, and it preserves trimming for consumers. The cost is that the annotations are viral: a caller passing an unannotated Type just moves the warning up one level. That is arguably fine, since it moves the warning to where the type is actually known.
  • Mark the reflection-heavy methods [RequiresUnreferencedCode] instead. Much less work and honest about what the code does, but it makes the whole library unusable warning-free in trimmed apps, which is a worse outcome than annotating.
  • Do nothing and document that Reflectify is not trim-safe. Cheapest, and defensible for a reflection library, but it effectively rules out NativeAOT consumers, which is a growing constituency.
  • Add a trimming test. Whatever is chosen, a small PublishTrimmed / PublishAot smoke project in the build would prevent regressions and prove the annotations actually work. That could be a first step on its own, since it would quantify how many warnings there are today.

Worth noting that some methods, such as the record detection heuristics, genuinely cannot be made trim-safe, since they look up members by string name that the compiler generates. Those are honest [RequiresUnreferencedCode] or suppression candidates.

Are you willing help with a pull-request?

No

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions