Allow context types to delegate components directly #175
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR enables CGP context types to use
delegate_components!directly, without needing to make use of a separate provider struct for the context. That is, we can now do:instead of the original:
This significantly reduces the ergonomics of wiring for the concrete context, as we no longer need to think of having a separate
AppComponentsprovider to act as a type-level lookup table. Instead, the type-level lookup table is directly stored inApp.This should also slightly improve the compilation performance of compiling CGP code, as there is one less level of indirection for the trait solver to go through.
Direct implementation of consumer traits
A significant advantage that arise is that we can now implement a consumer trait on a concrete context directly. For example:
Previously, we would have to write:
which makes the code look much worse especially for newcomers.
Removal of
HasCgpProvidertraitThe consumer traits can be implemented directly, because we have now removed the
HasCgpProvidertrait from CGP. Instead of usingHasCgpProvider, the blanket implementation for the consumer trait now also usesDelegateComponentin the same way as the provider trait's blanket implementation.For example, the
HasNametrait earlier now has the following blanket implementation:While before this, the following was generated:
When
HasCgpProviderwas used, the blanket implementation of the consumer trait prevents any type that implementsHasCgpProviderto also implement the consumer trait. But with the new derivation, the context can still implement the consumer trait even if it implementsDelegateComponent, as long as the trait is not implemented for the keyNameGetterComponent. This means that as long as we don't have any generic implementation ofDelegateComponenton the context that might cover the component key, we would be able to implement the consumer trait directly.Backward Compatibility
Since there are already quite some existing code bases that use CGP, completely removing the context provider might cause significant breakage. Fortunately, we can work around the breakage by changing
#[cgp_context]to perform a bulkDelegateComponent<Name>for allNametype.For example, given:
The macro now generates:
With this, the bulk delegation essentially serves the same role as the use of
HasCgpProvider, which was generated before this change:Background
I'd like to provide a little bit of background of why
HasCgpProviderwas initially used in the blanket implementation, as compared to just useDelegateComponentlike the design here.Originally, I had the idea that multiple concrete contexts could share the same provider "table". For example:
In this design, we could reuse the same component wiring for different applications, without needing to reconfigure them every time.
However, there is always some cases that two concrete context would share almost the same wiring, except for a handful of customization. This eventually lead to the development of the preset feature, which provide the same functionality as what shared context providers are supposed to serve.
As a result, the context providers were largely an artifact from the early design of CGP. However, it lingered for a long time, as I was worried that changing it would cause too much breakage to existing code.
Recently, I thought about this issue again, and I realized that I could mostly preserve the backward compatibility by modifying
#[cgp_context]to generate a blanket implementation ofDelegateComponent. As a result, the backward compatibility problem is largely resolved, and I am now able to push this change for the next major release.