Skip to content
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
21 changes: 9 additions & 12 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,19 +231,16 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<S
// Add macOS SDK path for system headers (stdlib.h, etc.)
// Required for libclang 19+ with preserve_none calling convention support
#[cfg(target_os = "macos")]
if let Ok(sdk_path) = std::process::Command::new("xcrun")
.args(["--show-sdk-path"])
.output()
&& sdk_path.status.success()
{
if let Ok(sdk_path) = std::process::Command::new("xcrun")
.args(["--show-sdk-path"])
.output()
{
if sdk_path.status.success() {
let path = String::from_utf8_lossy(&sdk_path.stdout);
let path = path.trim();
bindgen = bindgen
.clang_arg(format!("-isysroot{}", path))
.clang_arg(format!("-I{}/usr/include", path));
}
}
let path = String::from_utf8_lossy(&sdk_path.stdout);
let path = path.trim();
bindgen = bindgen
.clang_arg(format!("-isysroot{path}"))
.clang_arg(format!("-I{path}/usr/include"));
}

bindgen = bindgen
Expand Down
6 changes: 6 additions & 0 deletions crates/macros/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ fn generate_registered_class_impl(
use ::ext_php_rs::internal::class::PhpClassImpl;
::ext_php_rs::internal::class::PhpClassImplCollector::<Self>::default().get_constants()
}

#[inline]
fn interface_implementations() -> ::std::vec::Vec<::ext_php_rs::class::ClassEntryInfo> {
// TODO: Implement cross-crate interface discovery
::std::vec::Vec::new()
}
}
}
}
65 changes: 65 additions & 0 deletions crates/macros/src/impl_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Implementation for the `#[php_impl_interface]` macro.
//!
//! This macro allows classes to implement PHP interfaces by implementing Rust
//! traits that are marked with `#[php_interface]`.

use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::ItemImpl;

use crate::prelude::*;

const INTERNAL_INTERFACE_NAME_PREFIX: &str = "PhpInterface";

/// Parses a trait impl block and generates the interface implementation
/// registration.
///
/// # Arguments
///
/// * `input` - The trait impl block (e.g., `impl SomeTrait for SomeStruct { ...
/// }`)
///
/// # Generated Code
///
/// The macro generates:
/// 1. The original trait impl block (passed through unchanged)
/// 2. An implementation of `PhpInterfaceImpl` that registers the interface
pub fn parser(input: &ItemImpl) -> Result<TokenStream> {
// Extract the trait being implemented
let Some((_, trait_path, _)) = &input.trait_ else {
bail!(input => "`#[php_impl_interface]` can only be used on trait implementations (e.g., `impl SomeTrait for SomeStruct`)");
};

// Get the last segment of the trait path (the trait name)
let trait_ident = match trait_path.segments.last() {
Some(segment) => &segment.ident,
None => {
bail!(trait_path => "Invalid trait path");
}
};

// Get the struct type being implemented
let struct_ty = &input.self_ty;

// Generate the internal interface struct name (e.g., PhpInterfaceSomeTrait)
let interface_struct_name = format_ident!("{}{}", INTERNAL_INTERFACE_NAME_PREFIX, trait_ident);

Ok(quote! {
// Pass through the original trait implementation
#input

// Implement PhpInterfaceImpl for the struct to register the interface
impl ::ext_php_rs::internal::class::PhpInterfaceImpl<#struct_ty>
for ::ext_php_rs::internal::class::PhpInterfaceImplCollector<#struct_ty>
{
fn get_interfaces(self) -> ::std::vec::Vec<::ext_php_rs::class::ClassEntryInfo> {
vec![
(
|| <#interface_struct_name as ::ext_php_rs::class::RegisteredClass>::get_metadata().ce(),
<#interface_struct_name as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME
)
]
}
}
})
}
60 changes: 59 additions & 1 deletion crates/macros/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use darling::FromAttributes;
use darling::util::Flag;
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{Expr, Ident, ItemTrait, Path, TraitItem, TraitItemConst, TraitItemFn};
use syn::{Expr, Ident, ItemTrait, Path, TraitItem, TraitItemConst, TraitItemFn, TypeParamBound};

use crate::impl_::{FnBuilder, MethodModifier};
use crate::parsing::{PhpRename, RenameRule, Visibility};
Expand Down Expand Up @@ -45,11 +45,36 @@ trait Parse<'a, T> {
fn parse(&'a mut self) -> Result<T>;
}

/// Represents a supertrait that should be converted to an interface extension.
/// These are automatically detected from Rust trait bounds (e.g., `trait Foo:
/// Bar`).
struct SupertraitInterface {
/// The name of the supertrait's PHP interface struct (e.g.,
/// `PhpInterfaceBar`)
interface_struct_name: Ident,
}

impl ToTokens for SupertraitInterface {
fn to_tokens(&self, tokens: &mut TokenStream) {
let interface_struct_name = &self.interface_struct_name;
quote! {
(
|| <#interface_struct_name as ::ext_php_rs::class::RegisteredClass>::get_metadata().ce(),
<#interface_struct_name as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME
)
}
.to_tokens(tokens);
}
}

struct InterfaceData<'a> {
ident: &'a Ident,
name: String,
path: Path,
/// Extends from `#[php(extends(...))]` attributes
extends: Vec<ClassEntryAttribute>,
/// Extends from Rust trait bounds (supertraits)
supertrait_extends: Vec<SupertraitInterface>,
constructor: Option<Function<'a>>,
methods: Vec<FnBuilder>,
constants: Vec<Constant<'a>>,
Expand All @@ -62,6 +87,7 @@ impl ToTokens for InterfaceData<'_> {
let interface_name = format_ident!("{INTERNAL_INTERFACE_NAME_PREFIX}{}", self.ident);
let name = &self.name;
let implements = &self.extends;
let supertrait_implements = &self.supertrait_extends;
let methods_sig = &self.methods;
let constants = &self.constants;
let docs = &self.docs;
Expand All @@ -86,8 +112,10 @@ impl ToTokens for InterfaceData<'_> {

const FLAGS: ::ext_php_rs::flags::ClassFlags = ::ext_php_rs::flags::ClassFlags::Interface;

// Interface inheritance from both explicit #[php(extends(...))] and Rust trait bounds
const IMPLEMENTS: &'static [::ext_php_rs::class::ClassEntryInfo] = &[
#(#implements,)*
#(#supertrait_implements,)*
];

const DOC_COMMENTS: &'static [&'static str] = &[
Expand Down Expand Up @@ -202,11 +230,16 @@ impl<'a> Parse<'a, InterfaceData<'a>> for ItemTrait {
let interface_name = format_ident!("{INTERNAL_INTERFACE_NAME_PREFIX}{ident}");
let ts = quote! { #interface_name };
let path: Path = syn::parse2(ts)?;

// Parse supertraits to automatically generate interface inheritance
let supertrait_extends = parse_supertraits(&self.supertraits);

let mut data = InterfaceData {
ident,
name,
path,
extends: attrs.extends,
supertrait_extends,
constructor: None,
methods: Vec::default(),
constants: Vec::default(),
Expand Down Expand Up @@ -234,6 +267,31 @@ impl<'a> Parse<'a, InterfaceData<'a>> for ItemTrait {
}
}

/// Parses the supertraits of a trait definition and converts them to interface
/// extensions. For a trait like `trait Foo: Bar + Baz`, this will generate
/// references to `PhpInterfaceBar` and `PhpInterfaceBaz`.
fn parse_supertraits(
supertraits: &syn::punctuated::Punctuated<TypeParamBound, syn::token::Plus>,
) -> Vec<SupertraitInterface> {
supertraits
.iter()
.filter_map(|bound| {
if let TypeParamBound::Trait(trait_bound) = bound {
// Get the last segment of the trait path (the trait name)
let trait_name = trait_bound.path.segments.last()?;
// Generate the PHP interface struct name
let interface_struct_name =
format_ident!("{}{}", INTERNAL_INTERFACE_NAME_PREFIX, trait_name.ident);
Some(SupertraitInterface {
interface_struct_name,
})
} else {
None
}
})
.collect()
}

#[derive(FromAttributes, Default, Debug)]
#[darling(default, attributes(php), forward_attrs(doc))]
pub struct PhpFunctionInterfaceAttribute {
Expand Down
Loading