Skip to content

Introduce 'RenderTreeBuilder.AddComponentParameter()'#46562

Merged
MackinnonBuck merged 4 commits into
mainfrom
mbuck/rendertreebuilder-addcomponentparameter
Feb 22, 2023
Merged

Introduce 'RenderTreeBuilder.AddComponentParameter()'#46562
MackinnonBuck merged 4 commits into
mainfrom
mbuck/rendertreebuilder-addcomponentparameter

Conversation

@MackinnonBuck
Copy link
Copy Markdown
Member

Introduce RenderTreeBuilder.AddComponentParameter()

Fixes an issue where user-defined implicit conversion operators may cause undesirable RenderTreeBuilder.AddAttribute() overloads to be resolved.

Description

Suppose you have defined the following class:

public class Foo
{
    public string Value { get; set; } = string.Empty;
    public static implicit operator string(Foo foo) => foo.Value;
}

Now let's say you have a Blazor component MyComponent that has a parameter of type Foo. The generated RenderTreeBuilder.AddAttribute(...) call to set the parameter value will look something like this:

// ...
__builder.AddAttribute(1, "Foo", _foo); // '_foo' is of type 'Foo' here

We want the AddAttribute(int sequence, string name, object? value) overload to be resolved, but since Foo defines an implicit conversion to string, the AddAttribute(int sequence, string name, string value) overload gets resolved instead. As a result, the framework later tries to set the component parameter of type Foo to a value of type string, and an exception is thrown.

To solve this problem, this PR adds an AddComponentParameter(int sequence, string name, object? value) method to RenderTreeBuilder. There is only one overload to choose from, so we don't experience the same problem as we did with AddAttribute(). Also, since component parameter values always get boxed eventually (to be added to a RenderTreeFrame), we're not losing anything by performing the boxing earlier.

A corresponding change will have to be made in the Razor compiler so that generated code makes use of the new AddComponentParameter() method.

Validation

  • Manual
  • Automated tests

Contributes to #18042

@MackinnonBuck MackinnonBuck requested a review from a team as a code owner February 9, 2023 22:50
@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Feb 9, 2023
@MackinnonBuck
Copy link
Copy Markdown
Member Author

Note to reviewers: This PR will be easier to review by looking at individual commits 🙂

Comment thread src/Components/Components/src/Rendering/RenderTreeBuilder.cs
@MackinnonBuck
Copy link
Copy Markdown
Member Author

We'll also want to do an API review of this once we've confirmed this is the general strategy we'll be taking.

@MackinnonBuck MackinnonBuck changed the title Introduce 'RenderTreeBuilder.AddComponentParameter()` Introduce 'RenderTreeBuilder.AddComponentParameter()' Feb 9, 2023
@SteveSandersonMS
Copy link
Copy Markdown
Member

This looks good to me. Nice job, @MackinnonBuck!

One thing I'd still like to check: do you think we still have adequate unit test coverage for the case where existing RCLs continue to use AddAttribute for component parameters? I'm not saying we need 100% the same level of coverage as for AddComponentParameter, but we do want to have a realistic chance of being notified if some future change to AddAttribute was going to break back-compat with being used for component parameters. We only need to test that AddAttribute does still result in the expected RenderTreeFrame structure for each of the main component-related scenarios.

It's quite possible this coverage is already handled in this PR by virtue of not modifying certain existing tests. But that's very difficult to establish by reading a diff which only shows the things you have modified :) So is this something you could clarify?

/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
/// <param name="name">The name of the attribute.</param>
/// <param name="value">The value of the attribute.</param>
public void AddComponentParameter(int sequence, string name, object? value)
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.

How does this approach work when the parameters are splatted?

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.

That calls AddMultipleAttributes(int sequence, IEnumerable<KeyValuePair<string, object>>? attributes) which doesn't have the same overload resolution issue.

If you're suggesting adding a AddMultipleComponentParameters method we could do but it would have no difference in behavior. I agree the aesthetics of still having to use AddMultipleAttributes for components are a bit strange, but if RenderTreeBuilder is only intended as a compiler target it's less of a priority.

@SteveSandersonMS
Copy link
Copy Markdown
Member

Oh one other idea: would it technically be valid to mark the AddAttribute(int, string, object) overload as obsolete? I haven't dug into this in detail but at first glance it seems like the object overload wouldn't make much sense for HTML elements, unless it results in calling ToString on the value automatically.

Perhaps that's too dramatic and too soon, especially given how we never actually intend to remove the method since we never want to break older RCLs.

@javiercn
Copy link
Copy Markdown
Member

Is there a way in which we can solve this without requiring a compiler change?

I am a bit worried that this is a lot of work to support a niche scenario like implicit type conversions. It seems more trouble than it is worth.

@SteveSandersonMS
Copy link
Copy Markdown
Member

SteveSandersonMS commented Feb 10, 2023

Is it a lot of work? It looks like the PR here is already at a production standard, so what remains is the Razor compiler change. It should be a really trivial change to the actual compiler logic. The only nontrivial bit is that a lot of test baselines will get updated. And there's no time constraint, as the runtime will retain back-compat so the Razor compiler change can be done any time during the 8.0 timeline.

Unless I'm missing something of course!

@MackinnonBuck
Copy link
Copy Markdown
Member Author

@SteveSandersonMS: One thing I'd still like to check: do you think we still have adequate unit test coverage for the case where existing RCLs continue to use AddAttribute for component parameters?

I think we do - we still have all the AddAttribute_Component_SomethingSomething tests that fully validate each overload of AddAttribute when applied to a component frame.

@MackinnonBuck
Copy link
Copy Markdown
Member Author

Regarding marking AddAttribute(int, string, object) obsolete - I wonder if it might be fine to wait on this for now. If there does turn out to be some reason why a developer is boxing the attribute's value parameter (maybe via some custom abstraction), the obsoleteness of that method might get in the way?

@SteveSandersonMS
Copy link
Copy Markdown
Member

Regarding marking AddAttribute(int, string, object) obsolete - I wonder if it might be fine to wait on this for now

Totally fine. It would have been an unnecessary extra point of friction for us to obsolete it without a clear need.

Copy link
Copy Markdown
Member

@SteveSandersonMS SteveSandersonMS left a comment

Choose a reason for hiding this comment

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

I'm fine with this based on the discussion here.

Before merging, do you think it's worth trying to get some kind of signal from the compiler team that they will likely be able to make the corresponding change on their side within the next couple of months? If for some reason they had a problem with this that might force us to re-think. But if they are unconcerned we could be pretty confident.

@MackinnonBuck
Copy link
Copy Markdown
Member Author

@jaredpar Do you think the Razor Compiler team can make the changes corresponding to those in this PR?
This would involve generating calls to RenderTreeBuilder.AddComponentParameter(int, string, object) in place of any calls to RenderTreeBuilder.AddAttribute(...) when adding attributes to a Razor component.

@MackinnonBuck
Copy link
Copy Markdown
Member Author

API review issue: #46575

@davidwengier
Copy link
Copy Markdown
Member

Random drive-by thought: If there is going to need to be a compiler change anyway, couldn't the compiler change to add (object?) in front of the third parameter of AddAttribute and this PR is then unnecessary?

@jaredpar
Copy link
Copy Markdown
Member

@davidwengier had similar thoughts. I think understanding why that would or wouldn't work would help us prioritize.

@chsienki, @333fred, @jjonescz

@chsienki
Copy link
Copy Markdown
Member

Yep had the same thought also. Given the want to obsolete the existing method though, I wasn't sure if there were other issues with it that this API fixes.

@MackinnonBuck
Copy link
Copy Markdown
Member Author

@jaredpar, @davidwengier, @chsienki,

The (object?) cast was something we considered, and it would work in the sense that it solves the exact issue at hand. However, doing so would prevent us from making certain types of improvements to this area in the future. For example, let's say we wanted to introduce a new, generic AddComponentParameter<T>(...) method that completely avoids boxing the component parameter value. We wouldn't be able to do something like this if we erase the type of the value argument.

Even in the current AddAttribute() implementation, each overload has specialized logic that checks whether we're dealing with a component parameter or an element attribute. Arguably, we should have had a separate AddComponentParameter() method from the start to allow us to take advantage of the differences between how components and elements are treated. So, this change is also a step toward long-term correctness in addition to fixing the bug linked in the PR.

cc @SteveSandersonMS

@ghost
Copy link
Copy Markdown

ghost commented Feb 21, 2023

Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime.
To make sure no breaking changes are introduced, please remove the pending ci rerun label to kick off a new CI run.

@ghost ghost added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Feb 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants