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

Explicit preparation phase without STATE #333

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 0 additions & 1 deletion crates/cli/.gitignore
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you check if this mr builds on top of current master. A little confused seeing these changes again.

This file was deleted.

6 changes: 6 additions & 0 deletions crates/cli/.gitignore
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you check if this mr builds on top of current master. A little confused seeing these changes again.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/target
Cargo.lock
/.vscode
/.idea
/tmp
expand.rs
1 change: 0 additions & 1 deletion crates/macros/.gitignore

This file was deleted.

6 changes: 6 additions & 0 deletions crates/macros/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/target
Cargo.lock
/.vscode
/.idea
/tmp
expand.rs
28 changes: 19 additions & 9 deletions crates/macros/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ pub struct AttrArgs {
flags: Option<Expr>,
}

pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream> {
let args = AttrArgs::from_list(&args)
.map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))?;

fn prepare(args: AttrArgs, mut input: ItemStruct) -> Result<(TokenStream, Class)> {
let mut parent = None;
let mut interfaces = vec![];
let mut properties = HashMap::new();
Expand Down Expand Up @@ -132,6 +129,23 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream>
..Default::default()
};

Ok((
quote! {
#input

::ext_php_rs::class_derives!(#ident);
},
class,
))
}

pub fn parser(args: AttributeArgs, input: ItemStruct) -> Result<TokenStream> {
let args = AttrArgs::from_list(&args)
.map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))?;

let ident = &input.ident.clone();
let (tokens, class) = prepare(args, input)?;

let mut state = STATE.lock();

if state.built_module {
Expand All @@ -144,11 +158,7 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream>

state.classes.insert(ident.to_string(), class);

Ok(quote! {
#input

::ext_php_rs::class_derives!(#ident);
})
Ok(tokens)
}

#[derive(Debug)]
Expand Down
12 changes: 8 additions & 4 deletions crates/macros/src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ pub struct Constant {
pub value: String,
}

fn prepare(input: ItemConst) -> Result<TokenStream> {
Ok(quote! {
#[allow(dead_code)]
#input
})
}

pub fn parser(input: ItemConst) -> Result<TokenStream> {
let mut state = STATE.lock();

Expand All @@ -28,10 +35,7 @@ pub fn parser(input: ItemConst) -> Result<TokenStream> {
value: input.expr.to_token_stream().to_string(),
});

Ok(quote! {
#[allow(dead_code)]
#input
})
prepare(input)
}

impl Constant {
Expand Down
38 changes: 25 additions & 13 deletions crates/macros/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,7 @@ pub struct Function {
pub output: Option<(String, bool)>,
}

pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Function)> {
let attr_args = match AttrArgs::from_list(&args) {
Ok(args) => args,
Err(e) => bail!("Unable to parse attribute arguments: {:?}", e),
};

fn prepare(attr_args: AttrArgs, input: ItemFn) -> Result<(TokenStream, Function)> {
let ItemFn { sig, .. } = &input;
let Signature {
ident,
Expand Down Expand Up @@ -89,24 +84,41 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi
}
};

let mut state = STATE.lock();

if state.built_module && !attr_args.ignore_module {
bail!("The `#[php_module]` macro must be called last to ensure functions are registered. To ignore this error, pass the `ignore_module` option into this attribute invocation: `#[php_function(ignore_module)]`");
}
let name = if let Some(name) = attr_args.name {
name
} else {
ident.to_string()
};

let function = Function {
name: attr_args.name.unwrap_or_else(|| ident.to_string()),
name,
docs: get_docs(&input.attrs),
ident: internal_ident.to_string(),
args,
optional,
output: return_type,
};

Ok((func, function))
}

pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<TokenStream> {
let attr_args = match AttrArgs::from_list(&args) {
Ok(args) => args,
Err(e) => bail!("Unable to parse attribute arguments: {:?}", e),
};
let ignore_module = attr_args.ignore_module;
let (func, function) = prepare(attr_args, input)?;

let mut state = STATE.lock();

if state.built_module && !ignore_module {
bail!("The `#[php_module]` macro must be called last to ensure functions are registered. To ignore this error, pass the `ignore_module` option into this attribute invocation: `#[php_function(ignore_module)]`");
}

state.functions.push(function.clone());

Ok((func, function))
Ok(func)
}

fn build_args(
Expand Down
45 changes: 26 additions & 19 deletions crates/macros/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use quote::quote;
use std::collections::HashMap;
use syn::{Attribute, AttributeArgs, ItemImpl, Lit, Meta, NestedMeta};

use crate::class::Class;
use crate::helpers::get_docs;
use crate::{
class::{Property, PropertyAttr},
Expand Down Expand Up @@ -94,31 +95,14 @@ pub enum PropAttrTy {
Setter,
}

pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
let args = AttrArgs::from_list(&args)
.map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))?;

// note: this takes a mutable argument as it mutates a class
fn prepare(args: AttrArgs, input: ItemImpl, class: &mut Class) -> Result<TokenStream> {
let ItemImpl { self_ty, items, .. } = input;
let class_name = self_ty.to_token_stream().to_string();

if input.trait_.is_some() {
bail!("This macro cannot be used on trait implementations.");
}

let mut state = crate::STATE.lock();

if state.startup_function.is_some() {
bail!(
"Impls must be declared before you declare your startup function and module function."
);
}

let class = state.classes.get_mut(&class_name).ok_or_else(|| {
anyhow!(
"You must use `#[php_class]` on the struct before using this attribute on the impl."
)
})?;

let tokens = items
.into_iter()
.map(|item| {
Expand Down Expand Up @@ -178,6 +162,29 @@ pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
Ok(output)
}

pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
let args = AttrArgs::from_list(&args)
.map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))?;

let mut state = crate::STATE.lock();

if state.startup_function.is_some() {
bail!(
"Impls must be declared before you declare your startup function and module function."
);
}

let class_name = input.self_ty.to_token_stream().to_string();

let class = state.classes.get_mut(&class_name).ok_or_else(|| {
anyhow!(
"You must use `#[php_class]` on the struct before using this attribute on the impl."
)
})?;

prepare(args, input, class)
}

pub fn parse_attribute(attr: &Attribute) -> Result<Option<ParsedAttribute>> {
let name = attr.path.to_token_stream().to_string();
let meta = attr
Expand Down
2 changes: 1 addition & 1 deletion crates/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);

match function::parser(args, input) {
Ok((parsed, _)) => parsed,
Ok(parsed) => parsed,
Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(),
}
.into()
Expand Down
92 changes: 53 additions & 39 deletions crates/macros/src/module.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::sync::MutexGuard;

use anyhow::{anyhow, bail, Result};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
Expand All @@ -11,56 +9,36 @@ use crate::{
startup_function, State, STATE,
};

pub fn parser(input: ItemFn) -> Result<TokenStream> {
fn prepare<'a>(
input: ItemFn,
functions: &[Function],
startup_fn: Option<&TokenStream>,
startup_function_name: Option<&str>,
classes: impl Iterator<Item = &'a Class>,
describe: &TokenStream,
) -> Result<TokenStream> {
let ItemFn { sig, block, .. } = input;
let Signature { output, inputs, .. } = sig;
let stmts = &block.stmts;

let mut state = STATE.lock();

if state.built_module {
bail!("You may only define a module with the `#[php_module]` attribute once.");
}

state.built_module = true;

// Generate startup function if one hasn't already been tagged with the macro.
let startup_fn = if (!state.classes.is_empty() || !state.constants.is_empty())
&& state.startup_function.is_none()
{
drop(state);

let parsed = syn::parse2(quote! {
fn php_module_startup() {}
})
.map_err(|_| anyhow!("Unable to generate PHP module startup function."))?;
let startup = startup_function::parser(None, parsed)?;

state = STATE.lock();
Some(startup)
} else {
None
};

let functions = state
.functions
let functions = functions
.iter()
.map(|func| func.get_builder())
.collect::<Vec<_>>();
let startup = state.startup_function.as_ref().map(|ident| {
let startup = startup_function_name.as_ref().map(|ident| {
let ident = Ident::new(ident, Span::call_site());
quote! {
.startup_function(#ident)
}
});
let registered_classes_impls = state
.classes
.values()
let registered_classes_impls = classes
.map(generate_registered_class_impl)
.collect::<Result<Vec<_>>>()?;
let describe_fn = generate_stubs(&state);
let describe_fn = generate_stubs(describe);

let result = quote! {
Ok(quote! {
#(#registered_classes_impls)*

#startup_fn
Expand Down Expand Up @@ -90,8 +68,46 @@ pub fn parser(input: ItemFn) -> Result<TokenStream> {
}

#describe_fn
})
}

fn prepare_startup() -> Result<TokenStream> {
let parsed = syn::parse2(quote! {
fn php_module_startup() {}
})
.map_err(|_| anyhow!("Unable to generate PHP module startup function."))?;
startup_function::parser(None, parsed)
}

pub fn parser(input: ItemFn) -> Result<TokenStream> {
let mut state = STATE.lock();

if state.built_module {
bail!("You may only define a module with the `#[php_module]` attribute once.");
}

state.built_module = true;

// Generate startup function if one hasn't already been tagged with the macro.
let startup_fn = if (!state.classes.is_empty() || !state.constants.is_empty())
&& state.startup_function.is_none()
{
drop(state);
let prepared = prepare_startup()?;
state = STATE.lock();
Some(prepared)
} else {
None
};
Ok(result)

prepare(
input,
&state.functions,
startup_fn.as_ref(),
state.startup_function.as_deref(),
state.classes.values(),
&state.describe(),
)
}

/// Generates an implementation for `RegisteredClass` on the given class.
Expand Down Expand Up @@ -151,9 +167,7 @@ pub trait Describe {
fn describe(&self) -> TokenStream;
}

fn generate_stubs(state: &MutexGuard<State>) -> TokenStream {
let module = state.describe();

fn generate_stubs(module: &TokenStream) -> TokenStream {
quote! {
#[cfg(debug_assertions)]
#[no_mangle]
Expand Down
Loading
Loading