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

Remove ItemCollection from TagHelperDescriptorProviderContext #10720

Merged
merged 8 commits into from
Aug 12, 2024

Conversation

DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented Aug 8, 2024

Every TagHelperDescriptorProviderContext creates an ItemCollection to hold onto, at most, two objects: a Compilation and a target ISymbol. This is enormously wasteful because an ItemCollection internally creates a ConcurrentDictionary<object, object>. So, I've changed TagHelperDescriptorProviderContext to just hold onto a compilation and a target symbol and avoid the ItemCollection altogether.

I recommend reviewing commit-by-commit.

Also, I bumped into a long standing compiler bug in EventHandlerTagHelperDescriptorProvider that was recently filed by a customer: #10497. I opted to stay on target and not fix this issue, but I made it painfully obvious where the fix would go. 😄

Every single `TagHelperDescriptorProviderContext` created in Razor compiler or tooling code adds a valid compilation. So, adding the compilation to the `ItemsCollection` is pure overhead and no `ITagHelperDescriptorProvider` needs to check it for null.

I removed a single test that verified the behavior if a compilation was never set. Now that a compilation is required, this test is no longer needed.
This can be safely removed now that there are no more references.
- Enable nullability
- Mark all as sealed
- Use `Lazy<T>` for singleton `ITagHelperDescriptorProviders`
- Introduce `TagHelperDescriptorProviderBase`
- Inherit from `TagHelperDescriptorProviderBase`
- Remove unnecessary properties
- Remove unnecessary property setters

In addition, I spotted a bug in `EventHandlerTagDescriptorProvider` while addressing nullability warnings. The buggy code is right here:

https://github.com/dotnet/razor/blob/fb84ae5d9bb8132972c2c23cf209721161f81deb/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/EventHandlerTagHelperDescriptorProvider.cs#L70-L76

When reading the constructor arguments of an `EventHandlerAttribute` it appears that we swap the last two arguments of the 4-argument variant. The constructor is defined like so:

```C#
EventHandlerAttribute(string attributeName, Type eventArgsType, bool enableStopPropagation, bool enablePreventDefault);
```

Unfortunately, the compiler reads the third parameter as `enablePreventDefaeult` and the fourth parameter as `enableStopPropagation`.

This has been broken since support for the 4-argument constructor variant was added in dotnet@7635bba. Fixing this bug is tracked by dotnet#10497.
@DustinCampbell DustinCampbell requested review from a team as code owners August 8, 2024 20:26
Copy link
Contributor

@chsienki chsienki left a comment

Choose a reason for hiding this comment

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

Compiler side LGTM

@DustinCampbell DustinCampbell merged commit 3920563 into dotnet:main Aug 12, 2024
12 checks passed
@DustinCampbell DustinCampbell deleted the remove-item-collection branch August 12, 2024 14:49
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Aug 12, 2024
jaredpar pushed a commit that referenced this pull request Aug 12, 2024
Every `TagHelperDescriptorProviderContext` creates an `ItemCollection`
to hold onto, at most, two objects: a `Compilation` and a target
`ISymbol`. This is enormously wasteful because an `ItemCollection`
internally creates a `ConcurrentDictionary<object, object>`. So, I've
changed `TagHelperDescriptorProviderContext` to just hold onto a
compilation and a target symbol and avoid the `ItemCollection`
altogether.

I recommend reviewing commit-by-commit.

Also, I bumped into a long standing compiler bug in
`EventHandlerTagHelperDescriptorProvider` that was recently filed by a
customer: #10497. I opted to stay
on target and not fix this issue, but I made it painfully obvious where
the fix would go. 😄
DustinCampbell added a commit that referenced this pull request Aug 22, 2024
Because of a dare from @333fred, I'm currently on a crusade to remove
`ItemCollection` from the Razor Compiler completely. Previously, I
removed `ItemCollection` from `TagHelperDescriptorProviderContext`
(#10720). This time, I'm removing it from `CodeRenderingContext`.

It turns out that every `CodeRenderingContext` greedily creates an
`ItemCollection`, though there are only ever two things stored there:

1. A string to use for new lines in `CodeWriter`.
2. A string representing "suppress IDs", which is already specified in
`RazorCodeGenerationOptions`.

These two items were really present as a hook that compiler tests could
set. However, it's much easier and less wasteful to elevate both items
to `RazorCodeGenerationOptions` and make tests set the options directly.

I made a few other refactorings as part of this change:

- I merged several abstract base classes with their single default
concrete type:
  - `CodeRenderingContext` and `DefaultCodeRenderingContext`
  - `RazorCSharpDocument` and `DefaultRazorCSharpDocument`,
  - `RazorCodeGenerationOptions` and `DefaultRazorCodeGenerationOptions`
- `RazorCodeGenerationOptionsBuilder` and
`DefaultRazorCodeGenerationOptionsBuilder`.
- Reworked `RazorCodeGenerationOptions` and introduced
`RazorCodeGenerationOptionsFlags` to track its boolean fields.
- Cleaned up `RazorCSharpDocument` and unified its collections on
`ImmutableArray<>` rather than `IReadOnlyList<>`.
- Enabled nullability annotations for several types.
- Used more pooled collections in `CodeRenderingContext`.

Note: I was careful with my commit history, and it should be clean
enough to review commit-by-commit if that's your preference.
@dibarbet dibarbet modified the milestones: Next, 17.12 P2 Aug 26, 2024
DustinCampbell added a commit that referenced this pull request Sep 3, 2024
…#10828)

This fixes an issue that @tmat pointed out to me over email. In a recent
change (#10720), I added a call to `Assumed.Unreachable()` when
`[EventHandler]` tag helper discovery encounters an attribute with
invalid constructor attributes. However, throwing an exception during
tag helper discovery is usually the wrong approach. Normally, if Roslyn
symbols aren't in the proper shape during tag helper discovery, Razor
will simply not produce a tag helper. (We _do_ support diagnostics for
tag helpers, but those are usually reserved for warnings and errors that
are related to a tag helper's data that would make it unusable, such as
a name containing whitespace.)

It turns out that the "unreachable" condition wasn't actually all that
unreachable and @tmat was hitting it while working on hot reload tests.
So, I've changed the code to make the success conditions clearer, i.e.,
the attribute data must match one of the two constructor calls. And, I
changed the logic to simply skip `[EventHandler]` attributes that don't
meet the success conditions.
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.

4 participants