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
24 changes: 19 additions & 5 deletions src/builders/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ use crate::{
zend_fastcall,
};

type ConstantEntry = (String, Box<dyn FnOnce() -> Result<Zval>>, DocComments);
/// A constant entry: (name, `value_closure`, docs, `stub_value`)
type ConstantEntry = (
String,
Box<dyn FnOnce() -> Result<Zval>>,
DocComments,
String,
);
type PropertyDefault = Option<Box<dyn FnOnce() -> Result<Zval>>>;

/// Builder for registering a class in PHP.
Expand Down Expand Up @@ -140,8 +146,11 @@ impl ClassBuilder {
value: impl IntoZval + 'static,
docs: DocComments,
) -> Result<Self> {
// Convert to Zval first to get stub value
let zval = value.into_zval(true)?;
let stub = crate::convert::zval_to_stub(&zval);
self.constants
.push((name.into(), Box::new(|| value.into_zval(true)), docs));
.push((name.into(), Box::new(|| Ok(zval)), docs, stub));
Ok(self)
}

Expand All @@ -166,9 +175,14 @@ impl ClassBuilder {
value: &'static dyn IntoZvalDyn,
docs: DocComments,
) -> Result<Self> {
let stub = value.stub_value();
let value = Rc::new(value);
self.constants
.push((name.into(), Box::new(move || value.as_zval(true)), docs));
self.constants.push((
name.into(),
Box::new(move || value.as_zval(true)),
docs,
stub,
));
Ok(self)
}

Expand Down Expand Up @@ -375,7 +389,7 @@ impl ClassBuilder {
}
}

for (name, value, _) in self.constants {
for (name, value, _, _) in self.constants {
let value = Box::into_raw(Box::new(value()?));
unsafe {
zend_declare_class_constant(
Expand Down
30 changes: 30 additions & 0 deletions src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ use crate::ffi::{

/// Implemented on types which can be registered as a constant in PHP.
pub trait IntoConst: Debug {
/// Returns the PHP stub representation of this constant value.
///
/// This is used when generating PHP stub files for IDE autocompletion.
/// The returned string should be a valid PHP literal (e.g., `"hello"`,
/// `42`, `true`).
fn stub_value(&self) -> String;

/// Registers a global module constant in PHP, with the value as the content
/// of self. This function _must_ be called in the module startup
/// function, which is called after the module is initialized. The
Expand Down Expand Up @@ -89,6 +96,10 @@ pub trait IntoConst: Debug {
}

impl IntoConst for String {
fn stub_value(&self) -> String {
self.as_str().stub_value()
}

fn register_constant_flags(
&self,
name: &str,
Expand All @@ -101,6 +112,17 @@ impl IntoConst for String {
}

impl IntoConst for &str {
fn stub_value(&self) -> String {
// Escape special characters for PHP string literal
let escaped = self
.replace('\\', "\\\\")
.replace('\'', "\\'")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("'{escaped}'")
}

fn register_constant_flags(
&self,
name: &str,
Expand Down Expand Up @@ -133,6 +155,10 @@ impl IntoConst for &str {
}

impl IntoConst for bool {
fn stub_value(&self) -> String {
if *self { "true" } else { "false" }.to_string()
}

fn register_constant_flags(
&self,
name: &str,
Expand Down Expand Up @@ -169,6 +195,10 @@ impl IntoConst for bool {
macro_rules! into_const_num {
($type: ty, $fn: expr) => {
impl IntoConst for $type {
fn stub_value(&self) -> String {
self.to_string()
}

fn register_constant_flags(
&self,
name: &str,
Expand Down
79 changes: 79 additions & 0 deletions src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,85 @@ pub trait IntoZvalDyn {

/// Returns the PHP type of the type.
fn get_type(&self) -> DataType;

/// Returns the PHP stub representation of this value.
///
/// This is used when generating PHP stub files for IDE autocompletion.
/// The returned string should be a valid PHP literal.
fn stub_value(&self) -> String {
// Default implementation - convert to zval and format
match self.as_zval(false) {
Ok(zval) => zval_to_stub(&zval),
Err(_) => "null".to_string(),
}
}
}

/// Converts a Zval to its PHP stub representation.
#[must_use]
#[allow(clippy::match_same_arms)]
pub fn zval_to_stub(zval: &Zval) -> String {
use crate::flags::DataType;

match zval.get_type() {
DataType::Null | DataType::Undef => "null".to_string(),
DataType::True => "true".to_string(),
DataType::False => "false".to_string(),
DataType::Long => zval
.long()
.map_or_else(|| "null".to_string(), |v| v.to_string()),
DataType::Double => zval
.double()
.map_or_else(|| "null".to_string(), |v| v.to_string()),
DataType::String => {
if let Some(s) = zval.str() {
let escaped = s
.replace('\\', "\\\\")
.replace('\'', "\\'")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("'{escaped}'")
} else {
"null".to_string()
}
}
DataType::Array => {
#[allow(clippy::explicit_iter_loop)]
if let Some(arr) = zval.array() {
// Check if array has sequential numeric keys starting from 0
let is_sequential = arr.iter().enumerate().all(|(i, (key, _))| {
matches!(key, crate::types::ArrayKey::Long(idx) if i64::try_from(i).is_ok_and(|ii| idx == ii))
});

let mut parts = Vec::new();
for (key, val) in arr.iter() {
let val_str = zval_to_stub(val);
if is_sequential {
parts.push(val_str);
} else {
match key {
crate::types::ArrayKey::Long(idx) => {
parts.push(format!("{idx} => {val_str}"));
}
crate::types::ArrayKey::String(key) => {
let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
parts.push(format!("'{key_escaped}' => {val_str}"));
}
crate::types::ArrayKey::Str(key) => {
let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
parts.push(format!("'{key_escaped}' => {val_str}"));
}
}
}
}
format!("[{}]", parts.join(", "))
} else {
"[]".to_string()
}
}
_ => "null".to_string(),
}
}

impl<T: IntoZval + Clone> IntoZvalDyn for T {
Expand Down
17 changes: 10 additions & 7 deletions src/describe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,11 @@ impl From<ClassBuilder> for Class {
constants: val
.constants
.into_iter()
.map(|(name, _, docs)| (name, docs))
.map(Constant::from)
.map(|(name, _, docs, stub)| Constant {
name: name.into(),
value: Option::Some(stub.into()),
docs: docs.into(),
})
.collect::<StdVec<_>>()
.into(),
flags,
Expand Down Expand Up @@ -385,9 +388,9 @@ impl<D> From<(String, PropertyFlags, D, DocComments)> for Property {
let static_ = flags.contains(PropertyFlags::Static);
let vis = Visibility::from(flags);
// TODO: Implement ty #376
let ty = abi::Option::None;
let ty = Option::None;
// TODO: Implement default #376
let default = abi::Option::<abi::RString>::None;
let default = Option::<RString>::None;
// TODO: Implement nullable #376
let nullable = false;
let docs = docs.into();
Expand Down Expand Up @@ -552,18 +555,18 @@ impl From<(String, DocComments)> for Constant {
let (name, docs) = val;
Constant {
name: name.into(),
value: abi::Option::None,
value: Option::None,
docs: docs.into(),
}
}
}

impl From<(String, Box<dyn IntoConst + Send>, DocComments)> for Constant {
fn from(val: (String, Box<dyn IntoConst + Send + 'static>, DocComments)) -> Self {
let (name, _, docs) = val;
let (name, value, docs) = val;
Constant {
name: name.into(),
value: abi::Option::None,
value: Option::Some(value.stub_value().into()),
docs: docs.into(),
}
}
Expand Down