Skip to content

Preset Macro Improvements#91

Merged
soareschen merged 27 commits intomainfrom
preset-improvements
May 3, 2025
Merged

Preset Macro Improvements#91
soareschen merged 27 commits intomainfrom
preset-improvements

Conversation

@soareschen
Copy link
Collaborator

@soareschen soareschen commented May 2, 2025

Summary

This PR brings a number of improvements to the CGP Presets feature, and adds better support on OOP-like inheritance on presets.

Inheritance

Presets can now inherit from each other a short hand : ParentPreset notation. Behind the scene, the cgp_preset! macro is re-expanded multiple times to call ParentPreset::with_components! to inherit the component delegation from the parent preset.

Example:

pub mod preset_a {
    #[cgp::re_export_imports]
    mod preset {
        use cgp::prelude::*;

        use crate::components::*;

        cgp_preset! {
            PresetA {
                FooComponent: FooProvider,
                BarComponent: BarProvider,
            }
        }
    }
}

pub mod preset_b {
    #[cgp::re_export_imports]
    mod preset {
        use cgp::prelude::*;

        use crate::components::*;
        use super::super::preset_a::PresetA;

        cgp_preset! {
            PresetB: PresetA {
                BazComponent: BazProvider,
            }
        }
    }
}

In the above example, PresetB will delegate FooComponent and BarComponent to PresetA::Provider.

Overrides

When inheriting presets, the override keyword can be specified in the child preset entry to override/exclude the component from the parent preset. This allows a preset to be customized with minimal code duplication, by specifying only the components to be overridden. This can also be used to resolve the diamond inheritance problem, with the child preset overriding any conflicting presets from the parents.

Example:

pub mod preset_a {
    #[cgp::re_export_imports]
    mod preset {
        use cgp::prelude::*;

        use crate::components::*;

        cgp_preset! {
            PresetA {
                FooComponent: FooProvider,
                BarComponent: BarProvider,
            }
        }
    }
}

pub mod preset_b {
    #[cgp::re_export_imports]
    mod preset {
        use cgp::prelude::*;

        use crate::components::*;
        use super::super::preset_a::PresetA;

        cgp_preset! {
            PresetB: PresetA {
                override BarComponent: CustomProvider,
                BazComponent: BazProvider,
            }
        }
    }
}

In the above example, PresetB overrides the BarComponent from PresetA, and replaces it with CustomProvider.

Limitations of Override

Note that due to limitations of Rust macros, the override would only work based on exact identifier matches as seen by the preset macros, not by the original name or the import path.

Hence, the override may behave in unexpected ways, if it involves re-naming, or if multiple modules define components with the same name identifier.

Generic Presets

The cgp_preset! macro can now work with presets containing generic parameters. It is now also possible to include generic parameters inside the preset delegate entries, similar to delegate_components!.

Minimal Preset Re-exports (Breaking Changes)

The re-exports from preset modules have been reduced to only expose the component name identifiers. Before, the preset bulk re-exports everything that was imported from the module that contains cgp_preset!, which could pollute the import scope in the child presets.

With this changes, the original Preset::re_exports module is made private, with a new Preset::components module that re-exports only the component name identifiers. Note that the original Preset::re_exports is still required, as the macro cannot keep track of whether a component name identifier originates from the child preset, or from one of the parent presets. Hence, we use Preset::re_exports to combine everything first, then build Preset::components to re-export from Preset::re_exports.

Removal of DelegatesToPreset Trait (Breaking Changes)

We have removed the derivation of DelegatesToPreset trait in cgp_preset!. Originally, this trait could be used to check whether a child provider has delegated all of the preset's components to the given preset provider. However, challenges arise when either the preset or the entries contain generic parameters, with the macro generating code with incorrect generic bindings.

we have removed the trait to simplify the implementation of cgp_preset!. Furthermore, there isn't much need for the DelegatesToPreset anymore, with the new IsProviderFor trait helping to improve debugging of CGP-related errors.

Examples

For more examples of how the new presets work, check out the test examples in the cgp-test crate.

Note that preset is an advanced concept in CGP that is yet to be formally introduced in our book. More updates will be coming very soon.

@soareschen soareschen marked this pull request as draft May 2, 2025 20:54
@soareschen soareschen marked this pull request as ready for review May 3, 2025 19:15
@soareschen soareschen merged commit 2922afb into main May 3, 2025
5 checks passed
@soareschen soareschen deleted the preset-improvements branch May 3, 2025 19:51
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.

1 participant