Skip to content

feat: Add constructor visability #542

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 8 additions & 2 deletions crates/macros/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,11 @@ impl<'a> Function<'a> {

/// Returns a constructor metadata object for this function. This doesn't
/// check if the function is a constructor, however.
pub fn constructor_meta(&self, class: &syn::Path) -> TokenStream {
pub fn constructor_meta(
&self,
class: &syn::Path,
visibility: Option<&Visibility>,
) -> TokenStream {
let ident = self.ident;
let (required, not_required) = self.args.split_args(self.optional.as_ref());
let required_args = required
Expand Down Expand Up @@ -339,6 +343,7 @@ impl<'a> Function<'a> {
}
});
let docs = &self.docs;
let flags = visibility.option_tokens();

quote! {
::ext_php_rs::class::ConstructorMeta {
Expand Down Expand Up @@ -368,7 +373,8 @@ impl<'a> Function<'a> {
#variadic
}
inner
}
},
flags: #flags
}
}
}
Expand Down
13 changes: 5 additions & 8 deletions crates/macros/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ struct ParsedImpl<'a> {
change_method_case: RenameRule,
change_constant_case: RenameRule,
functions: Vec<FnBuilder>,
constructor: Option<Function<'a>>,
constructor: Option<(Function<'a>, Option<Visibility>)>,
constants: Vec<Constant<'a>>,
}

Expand Down Expand Up @@ -212,7 +212,7 @@ impl<'a> ParsedImpl<'a> {
let mut modifiers: HashSet<MethodModifier> = HashSet::new();

if matches!(opts.ty, MethodTy::Constructor) {
if self.constructor.replace(func).is_some() {
if self.constructor.replace((func, opts.vis.into())).is_some() {
bail!(method => "Only one constructor can be provided per class.");
}
} else {
Expand Down Expand Up @@ -264,7 +264,7 @@ impl<'a> ParsedImpl<'a> {
let constructor = self
.constructor
.as_ref()
.map(|func| func.constructor_meta(self.path))
.map(|(func, vis)| func.constructor_meta(self.path, vis.as_ref()))
.option_tokens();
let constants = self.constants.iter().map(|c| {
let name = &c.name;
Expand Down Expand Up @@ -306,11 +306,8 @@ impl quote::ToTokens for FnBuilder {
let builder = &self.builder;
// TODO(cole_d): allow more flags via attributes
let mut flags = vec![];
flags.push(match self.vis {
Visibility::Public => quote! { ::ext_php_rs::flags::MethodFlags::Public },
Visibility::Protected => quote! { ::ext_php_rs::flags::MethodFlags::Protected },
Visibility::Private => quote! { ::ext_php_rs::flags::MethodFlags::Private },
});
let vis = &self.vis;
flags.push(quote! { #vis });
for flag in &self.modifiers {
flags.push(quote! { #flag });
}
Expand Down
4 changes: 2 additions & 2 deletions crates/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,8 +712,8 @@ fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2
/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
/// also sets the remaining parameters as optional, so all optional parameters
/// must be a variant of `Option<T>`.
/// - `#[php(public)]`, `#[php(protected)]` and `#[php(private)]` - Sets the
/// visibility of the method.
/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis =
/// "private")]` - Sets the visibility of the method.
/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
/// identifier, without renaming the Rust method name.
///
Expand Down
12 changes: 12 additions & 0 deletions crates/macros/src/parsing.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use convert_case::{Case, Casing};
use darling::FromMeta;
use quote::{quote, ToTokens};

const MAGIC_METHOD: [&str; 17] = [
"__construct",
Expand Down Expand Up @@ -31,6 +32,17 @@ pub enum Visibility {
Protected,
}

impl ToTokens for Visibility {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Visibility::Public => quote! { ::ext_php_rs::flags::MethodFlags::Public },
Visibility::Protected => quote! { ::ext_php_rs::flags::MethodFlags::Protected },
Visibility::Private => quote! { ::ext_php_rs::flags::MethodFlags::Private },
}
.to_tokens(tokens);
}
}

pub trait Rename {
fn rename(&self, rule: RenameRule) -> String;
}
Expand Down
2 changes: 1 addition & 1 deletion guide/src/macros/impl.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The rest of the options are passed as separate attributes:
- `#[php(optional = i)]` - Sets the first optional parameter. Note that this also sets
the remaining parameters as optional, so all optional parameters must be a
variant of `Option<T>`.
- `#[php(public)]`, `#[php(protected)]` and `#[php(private)]` - Sets the visibility of the
- `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis = "private")]` - Sets the visibility of the
method.
- `#[php(name = "method_name")]` - Renames the PHP method to a different identifier,
without renaming the Rust method name.
Expand Down
25 changes: 15 additions & 10 deletions src/builders/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,16 +238,21 @@ impl ClassBuilder {
"Class name in builder does not match class name in `impl RegisteredClass`."
);
self.object_override = Some(create_object::<T>);
self.method(
{
let mut func = FunctionBuilder::new("__construct", constructor::<T>);
if let Some(ConstructorMeta { build_fn, .. }) = T::constructor() {
func = build_fn(func);
}
func
},
MethodFlags::Public,
)

let (func, visibility) = if let Some(ConstructorMeta {
build_fn, flags, ..
}) = T::constructor()
{
let func = FunctionBuilder::new("__construct", constructor::<T>);
(build_fn(func), flags.unwrap_or(MethodFlags::Public))
} else {
(
FunctionBuilder::new("__construct", constructor::<T>),
MethodFlags::Public,
)
};

self.method(func, visibility)
}

/// Function to register the class with PHP. This function is called after
Expand Down
2 changes: 2 additions & 0 deletions src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ pub struct ConstructorMeta<T> {
/// Function called to build the constructor function. Usually adds
/// arguments.
pub build_fn: fn(FunctionBuilder) -> FunctionBuilder,
/// Add constructor modification
pub flags: Option<MethodFlags>,
}

/// Result returned from a constructor of a class.
Expand Down
8 changes: 8 additions & 0 deletions tests/src/integration/class/class.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@
assert_exception_thrown(fn() => $arrayAccess['foo']);
assert($arrayAccess[0] === true);
assert($arrayAccess[1] === false);

$classReflection = new ReflectionClass(TestClassMethodVisibility::class);
assert($classReflection->getMethod('__construct')->isPrivate());
assert($classReflection->getMethod('private')->isPrivate());
assert($classReflection->getMethod('protected')->isProtected());

$classReflection = new ReflectionClass(TestClassProtectedConstruct::class);
assert($classReflection->getMethod('__construct')->isProtected());
33 changes: 33 additions & 0 deletions tests/src/integration/class/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,45 @@ impl TestClassExtendsImpl {
}
}

#[php_class]
struct TestClassMethodVisibility;

#[php_impl]
impl TestClassMethodVisibility {
#[php(vis = "private")]
fn __construct() -> Self {
Self
}

#[php(vis = "private")]
fn private() -> u32 {
3
}

#[php(vis = "protected")]
fn protected() -> u32 {
3
}
}
#[php_class]
struct TestClassProtectedConstruct;

#[php_impl]
impl TestClassProtectedConstruct {
#[php(vis = "protected")]
fn __construct() -> Self {
Self
}
}

pub fn build_module(builder: ModuleBuilder) -> ModuleBuilder {
builder
.class::<TestClass>()
.class::<TestClassArrayAccess>()
.class::<TestClassExtends>()
.class::<TestClassExtendsImpl>()
.class::<TestClassMethodVisibility>()
.class::<TestClassProtectedConstruct>()
.function(wrap_function!(test_class))
.function(wrap_function!(throw_exception))
}
Expand Down
Loading