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

Add "impl bridges" to Component derive macro #669

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions core/tests/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
mod example_app;

use self::example_app::{ExampleApp, ExampleConfig};
use abscissa_core::{component, Component, FrameworkError, FrameworkErrorKind::ComponentError};
use abscissa_core::{
component, Component, FrameworkError, FrameworkErrorKind::ComponentError, Shutdown,
};
use std::sync::atomic::{AtomicBool, Ordering};

/// ID for `FoobarComponent` (example component #1)
const FOOBAR_COMPONENT_ID: component::Id = component::Id::new("component::FoobarComponent");
Expand All @@ -30,10 +33,27 @@ impl FoobarComponent {

/// Example component #2
#[derive(Component, Debug, Default)]
pub struct BazComponent {}
#[component(application = "ExampleApp", after_config, before_shutdown)]
pub struct BazComponent {
pub after_config_run: bool,
pub before_shutdown_run: AtomicBool,
}

impl BazComponent {
fn after_config(&mut self, _config: &ExampleConfig) -> Result<(), FrameworkError> {
self.after_config_run = true;
Ok(())
}

fn before_shutdown(&self, _kind: Shutdown) -> Result<(), FrameworkError> {
self.before_shutdown_run.store(true, Ordering::Relaxed);
Ok(())
}
}

/// Example component #3
#[derive(Component, Debug, Default)]
#[component(application = "self::example_app::ExampleApp")]
#[component(inject = "init_foobar(component::FoobarComponent)")]
#[component(inject = "init_baz(component::BazComponent)")]
pub struct QuuxComponent {
Expand Down Expand Up @@ -148,3 +168,20 @@ fn dependency_injection() {
let quux = registry.get_downcast_ref::<QuuxComponent>().unwrap();
assert_eq!(quux.foobar_state.as_ref().unwrap(), "original foobar state");
}

#[test]
fn impl_bridges() {
let mut registry = component::Registry::default();
let components = init_components();

registry.register(components).unwrap();
registry.after_config(&ExampleConfig::default()).unwrap();

let baz = registry.get_downcast_ref::<BazComponent>().unwrap();
assert!(baz.after_config_run);

registry
.shutdown(&ExampleApp::default(), Shutdown::Graceful)
.unwrap();
assert!(baz.before_shutdown_run.load(Ordering::Relaxed));
}
122 changes: 102 additions & 20 deletions derive/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,65 @@

use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{DeriveInput, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
use syn::{parse_str, DeriveInput, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Path};
use synstructure::Structure;

/// Custom derive for `abscissa_core::component::Component`
pub fn derive_component(s: Structure<'_>) -> TokenStream {
let attrs = ComponentAttributes::from_derive_input(s.ast());
let name = &s.ast().ident;
let abscissa_core = attrs.abscissa_core_crate();
let after_config = attrs.after_config();
let before_shutdown = attrs.before_shutdown();
let dependency_methods = attrs.dependency_methods();

s.gen_impl(quote! {
gen impl<A> Component<A> for @Self
where
A: #abscissa_core::Application
{
#[doc = "Identifier for this component"]
fn id(&self) -> #abscissa_core::component::Id {
// TODO(tarcieri): use `core::any::type_name` here when stable
#abscissa_core::component::Id::new(concat!(module_path!(), "::", stringify!(#name)))
}
let body = quote! {
#[doc = "Identifier for this component"]
fn id(&self) -> #abscissa_core::component::Id {
// TODO(tarcieri): use `core::any::type_name` here when stable
#abscissa_core::component::Id::new(concat!(module_path!(), "::", stringify!(#name)))
}

#[doc = "Version of this component"]
fn version(&self) -> #abscissa_core::Version {
#abscissa_core::Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
}
#[doc = "Version of this component"]
fn version(&self) -> #abscissa_core::Version {
#abscissa_core::Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
}

#dependency_methods
#dependency_methods

#after_config

#before_shutdown
};

s.gen_impl(match attrs.application {
Some(application) => {
quote! {
gen impl Component<#application> for @Self {
#body
}
}
}
None => quote! {
gen impl<A> Component<A> for @Self
where
A: #abscissa_core::Application
{
#body
}
},
})
}

/// Parsed `#[component(...)]` attribute fields
#[derive(Debug)]
struct ComponentAttributes {
after_config: bool,

application: Option<Path>,

before_shutdown: bool,

/// Special attribute used by `abscissa_core` to `derive(Component)`.
///
/// Workaround for using custom derive on traits defined in the same crate:
Expand All @@ -49,6 +74,9 @@ struct ComponentAttributes {
impl ComponentAttributes {
/// Parse component attributes from custom derive input.
pub fn from_derive_input(input: &DeriveInput) -> Self {
let mut after_config = false;
let mut application = None;
let mut before_shutdown = false;
let mut core = false;
let mut inject = Vec::new();

Expand All @@ -61,8 +89,21 @@ impl ComponentAttributes {
Meta::List(MetaList { nested, .. }) => {
for meta in &nested {
match meta {
NestedMeta::Meta(Meta::Path(path)) if path.is_ident("core") => {
core = true
NestedMeta::Meta(Meta::Path(path)) => match path.get_ident() {
Some(id) if id == "after_config" => after_config = true,
Some(id) if id == "before_shutdown" => before_shutdown = true,
Some(id) if id == "core" => core = true,
_ => panic!("malformed `component` attribute: {:?}", meta),
},
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(lit_str),
..
})) if path.is_ident("application") => {
match parse_str(&lit_str.value()) {
Ok(path) => application = Some(path),
Err(_) => panic!("malformed `component` attribute: {:?}", meta),
}
}
NestedMeta::Meta(Meta::NameValue { .. }) => {
inject.push(InjectAttribute::from_nested_meta(meta))
Expand All @@ -75,7 +116,13 @@ impl ComponentAttributes {
};
}

Self { core, inject }
Self {
after_config,
application,
before_shutdown,
core,
inject,
}
}

/// Ident for the `abscissa_core` crate.
Expand All @@ -88,12 +135,47 @@ impl ComponentAttributes {
Ident::new(crate_name, Span::call_site())
}

pub fn after_config(&self) -> TokenStream {
let abscissa_core = self.abscissa_core_crate();

match (self.after_config, &self.application) {
(false, _) => quote!(),
(true, None) => quote! {
fn after_config(&mut self, config: &A::Cfg) -> Result<(), FrameworkError> {
self.after_config::<A>(config)
}
},
(true, Some(application)) => quote! {
fn after_config(&mut self, config: &<#application as #abscissa_core::Application>::Cfg) -> Result<(), FrameworkError> {
self.after_config(config)
}
},
}
}

pub fn before_shutdown(&self) -> TokenStream {
if !self.before_shutdown {
return quote!();
}

quote! {
fn before_shutdown(&self, kind: Shutdown) -> Result<(), FrameworkError> {
self.before_shutdown(kind)
}
}
}

/// Generate `Component::dependencies()` and `register_dependencies()`
pub fn dependency_methods(&self) -> TokenStream {
if self.inject.is_empty() {
return quote!();
}

let component = match &self.application {
Some(application) => quote! { Component<#application> },
None => quote! { Component<A> },
};

let abscissa_core = self.abscissa_core_crate();
let ids = self
.inject
Expand All @@ -111,7 +193,7 @@ impl ComponentAttributes {
fn register_dependency(
&mut self,
handle: #abscissa_core::component::Handle,
dependency: &mut dyn Component<A>,
dependency: &mut dyn #component,
) -> Result<(), FrameworkError> {
match dependency.id().as_ref() {
#(#match_arms),*
Expand Down