Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7cfc46b
feat(types): support primitive union type hints
ptondereau Apr 28, 2026
c4d5eff
feat(types): support nullable primitive unions
ptondereau Apr 28, 2026
91c83f5
feat(types): support union return types
ptondereau Apr 28, 2026
df29728
feat(describe)!: carry PHP union types through stub generation
ptondereau Apr 28, 2026
ef40ee1
feat(describe)!: carry PHP union types on class properties
ptondereau Apr 28, 2026
0638467
feat(types): support class union type hints
ptondereau Apr 28, 2026
52d2631
feat(describe)!: carry class unions through stub generation
ptondereau Apr 28, 2026
9621934
test(integration): cover Foo|Bar class union end-to-end
ptondereau Apr 28, 2026
a78aabb
fix(zend): emit class union as literal name on every PHP version
ptondereau Apr 28, 2026
cbbf314
feat(types): support PHP intersection type hints
ptondereau Apr 29, 2026
6e7f010
feat(describe)!: carry intersection types through stub generation
ptondereau Apr 29, 2026
c2b70c5
test(integration): cover Foo&Bar intersection end-to-end
ptondereau Apr 29, 2026
b185256
chore(flake): add php82 and php83 devshells
ptondereau Apr 29, 2026
fadad10
feat(types): support PHP DNF type hints
ptondereau Apr 29, 2026
98f40f2
feat(describe)!: carry DNF types through stub generation
ptondereau Apr 29, 2026
849d4f4
test(integration): cover (A&B)|C DNF end-to-end on PHP 8.3+
ptondereau Apr 29, 2026
f0db375
feat(types): add FromStr parser and Display for PhpType
ptondereau Apr 29, 2026
90d4e66
feat(macros): add #[php(types/returns)] proc-macro attribute
ptondereau Apr 29, 2026
f358296
feat(macros): add #[derive(PhpUnion)] for Rust enums
ptondereau Apr 29, 2026
dc4522e
feat(args)!: replace From<Arg> for _zend_expected_type with safe wrapper
ptondereau Apr 30, 2026
865a16c
chore(macros): wrap LitStr in backticks in validate_php_types_litstr doc
ptondereau Apr 30, 2026
79f045e
chore(clippy): clear pedantic warnings in macro tests and PHP fixtures
ptondereau Apr 30, 2026
0c0ac4d
feat(builders)!: register typed properties via zend_declare_typed_pro…
ptondereau May 4, 2026
648135a
test(builders): add register_property validation gate tests
ptondereau May 4, 2026
f9d73c4
feat!: extract type-string parser into ext-php-rs-types crate
ptondereau May 4, 2026
9131314
docs(examples): showcase compile-time #[php(types/returns)] in hello_…
ptondereau May 4, 2026
839627a
test(integration): cover compound returns via #[php(returns)] attribute
ptondereau May 4, 2026
c6530d3
docs(examples): showcase Rust-class type-string in hello_world
ptondereau May 4, 2026
7cdd8c6
docs(guide): document class-union refs in #[php(types/returns)]
ptondereau May 4, 2026
fc73edf
docs(macros): sync function/zval_convert lib.rs docs from guide
ptondereau May 4, 2026
437dc91
docs(guide): drop internal slice-06 reference in php_union page
ptondereau May 4, 2026
4590216
fix(types): rename lits to literals to satisfy typos linter
ptondereau May 4, 2026
eae8742
fix(docs): backtick arg_info in PHP-type override section
ptondereau May 4, 2026
db6f73f
fix(types): import DnfTerm locally in pre-82 property test
ptondereau May 4, 2026
ad2239b
chore(macros): regenerate generated artifacts
ptondereau May 5, 2026
319a2bc
fix(types): gate DNF property registration at PHP 8.3+
ptondereau May 5, 2026
f78ca9c
fix(build): introduce optional libphp linking with bail-on-missing
ptondereau May 5, 2026
5570a51
fix(tests): wrap PHP function handlers in zend_fastcall! for windows
ptondereau May 5, 2026
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
17 changes: 8 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,14 @@ jobs:
run: cargo build --release --features closure,anyhow,runtime,observer --workspace
# Test
- name: Test inline examples
# Macos fails on unstable rust. We skip the inline examples test for now.
if: "!(contains(matrix.os, 'macos') && matrix.rust == 'nightly')"
# macOS test binaries crash at load on macos-15 (chained fixups, ld-prime)
# because shivammathur/setup-php's Homebrew formulas (NTS and -debug-zts)
# do not ship libphp at <php-config --prefix>/lib, leaving PHP runtime
# data symbols unresolved. Build coverage on macOS still runs above.
# Restore this step for macOS once a libphp is available on the runner.
if: "!contains(matrix.os, 'macos')"
env:
EXT_PHP_RS_LINK_LIBPHP: "1"
run: cargo test --release --workspace --features closure,anyhow,runtime,observer --no-fail-fast
test-embed:
name: Test with embed (${{ matrix.label }})
Expand Down Expand Up @@ -303,10 +309,3 @@ jobs:
-w /workspace \
extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts[1] }} \
build --release --features closure,anyhow,runtime,observer --workspace
- name: Run tests
run: |
docker run \
-v $(pwd):/workspace \
-w /workspace \
extphprs/ext-php-rs:musl-${{ matrix.php }}-${{ matrix.phpts[1] }} \
test --workspace --release --features closure,anyhow,runtime,observer --no-fail-fast
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ smartstring = { version = "1", optional = true }
indexmap = { version = "2", optional = true }
inventory = "0.3"
ext-php-rs-derive = { version = "=0.11.12", path = "./crates/macros" }
ext-php-rs-types = { version = "=0.1.0", path = "./crates/types" }

[dev-dependencies]
skeptic = "0.13"
Expand Down Expand Up @@ -61,7 +62,11 @@ runtime = ["ext-php-rs-bindgen/runtime"]
static = ["ext-php-rs-bindgen/static"]

[workspace]
members = ["crates/macros", "crates/cli", "crates/php-build", "tests"]
members = ["crates/macros", "crates/cli", "crates/php-build", "crates/types", "tests"]

[workspace.dependencies]
proc-macro2 = "1.0.26"
quote = "1.0.9"

[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docs"]
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ For more examples read the library
- **Lightweight:** You don't have to use the built-in helper macros. It's
possible to write your own glue code around your own functions.
- **Extensible:** Implement `IntoZval` and `FromZval` for your own custom types,
allowing the type to be used as function parameters and return types.
allowing the type to be used as function parameters and return types. For
PHP type shapes that don't map to a single Rust type (primitive unions,
class unions, intersections, DNF), use `#[php(types = "...")]` on a
parameter or `#[php(returns = "...")]` on a function — see the
[function macro guide](guide/src/macros/function.md#overriding-the-registered-php-type).

## Goals

Expand Down
14 changes: 14 additions & 0 deletions allowed_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,19 @@ bind! {
_sapi_module_struct,
_zend_expected_type,
_zend_expected_type_Z_EXPECTED_ARRAY,
_zend_expected_type_Z_EXPECTED_ARRAY_OR_NULL,
_zend_expected_type_Z_EXPECTED_BOOL,
_zend_expected_type_Z_EXPECTED_BOOL_OR_NULL,
_zend_expected_type_Z_EXPECTED_DOUBLE,
_zend_expected_type_Z_EXPECTED_DOUBLE_OR_NULL,
_zend_expected_type_Z_EXPECTED_LONG,
_zend_expected_type_Z_EXPECTED_LONG_OR_NULL,
_zend_expected_type_Z_EXPECTED_OBJECT,
_zend_expected_type_Z_EXPECTED_OBJECT_OR_NULL,
_zend_expected_type_Z_EXPECTED_RESOURCE,
_zend_expected_type_Z_EXPECTED_RESOURCE_OR_NULL,
_zend_expected_type_Z_EXPECTED_STRING,
_zend_expected_type_Z_EXPECTED_STRING_OR_NULL,
_zend_new_array,
_zval_struct__bindgen_ty_1,
_zval_struct__bindgen_ty_2,
Expand Down Expand Up @@ -86,6 +93,7 @@ bind! {
zend_class_entry,
zend_declare_class_constant,
zend_declare_property,
zend_declare_typed_property,
zend_do_implement_interface,
zend_empty_array,
zend_read_property,
Expand Down Expand Up @@ -138,6 +146,7 @@ bind! {
zend_throw_exception_object,
zend_type,
zend_value,
zend_wrong_parameter_type_error,
zend_wrong_parameters_count_error,
zval,
CONST_CS,
Expand Down Expand Up @@ -259,6 +268,11 @@ bind! {
ts_rsrc_id,
_ZEND_TYPE_NAME_BIT,
_ZEND_TYPE_LITERAL_NAME_BIT,
_ZEND_TYPE_LIST_BIT,
_ZEND_TYPE_UNION_BIT,
_ZEND_TYPE_INTERSECTION_BIT,
_ZEND_TYPE_ARENA_BIT,
zend_type_list,
ZEND_INTERNAL_FUNCTION,
ZEND_USER_FUNCTION,
ZEND_EVAL_CODE,
Expand Down
47 changes: 47 additions & 0 deletions crates/cli/tests/snapshots/stubs_snapshot__hello_world_stubs.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ namespace {

const HELLO_WORLD = 100;

class OtherTestClass {
public function __construct() {}
}

class TestClass {
const NEW_CONSTANT_NAME = 5;

Expand Down Expand Up @@ -58,6 +62,30 @@ namespace {
public static function x(): int {}
}

/**
* Companion to `flexible_id` showing that the same compile-time parsing
* works for class-side type strings. The literal `\TestClass|\OtherTestClass`
* is parsed at macro-expansion time and resolves the class names against
* PHP's global namespace at extension load. Use a leading `\` for the
* fully qualified name; bare `TestClass` works too because the engine
* places `#[php_class]`-defined structs in the global namespace.
*
* @param \TestClass|\OtherTestClass $_value
* @return void
*/
function accept_class_value(\TestClass|\OtherTestClass $_value): void {}

/**
* Demonstrates compound PHP type hints. The argument accepts `int|string`
* and the return type registers as `int|string|null`. Both strings are
* parsed at macro-expansion time, so a typo such as `?Foo&Bar` would
* fail at `cargo build` rather than at extension load.
*
* @param int|string $_value
* @return int|string|null
*/
function flexible_id(int|string $_value): int|string|null {}

/**
* @param object $z
* @return int
Expand All @@ -73,4 +101,23 @@ namespace {
* @return \TestClass
*/
function new_class(): \TestClass {}

/**
* @param bool $use_float
* @return int|float
*/
function pick_number(bool $use_float): int|float {}

/**
* Demonstrates `#[php(returns = "...")]` widening the inferred return
* metadata. The Rust signature returns a concrete `TestClass`, so the
* macro would otherwise register the return type as just `\TestClass`.
* The override widens it to `\TestClass|\OtherTestClass`, which is
* useful when a function returns one specific subtype today but the
* PHP-side contract should leave room for a wider set of legal
* values. Reflection on this function reports the wider union.
*
* @return \TestClass|\OtherTestClass
*/
function produce_test_class_or_other(): \TestClass|\OtherTestClass {}
}
7 changes: 5 additions & 2 deletions crates/macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ proc-macro = true
[dependencies]
syn = { version = "2.0.100", features = ["full", "extra-traits", "printing"] }
darling = "0.23"
quote = "1.0.9"
proc-macro2 = "1.0.26"
quote = { workspace = true }
proc-macro2 = { workspace = true }
convert_case = "0.11.0"
itertools = "0.14.0"
ext-php-rs-types = { version = "=0.1.0", path = "../types", features = [
"proc-macro",
] }

[lints.rust]
missing_docs = "warn"
Expand Down
3 changes: 2 additions & 1 deletion crates/macros/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ fn generate_registered_class_impl(
fields.iter().partition(|prop| !prop.is_static());

// Generate instance property descriptors with getter/setter fn pointers.
// Each field property gets a pair of static functions and a PropertyDescriptor entry.
// Each field property gets a pair of static functions and a PropertyDescriptor
// entry.
let field_prop_count = instance_props.len();
let field_prop_data: Vec<(TokenStream, TokenStream)> = instance_props
.iter()
Expand Down
Loading
Loading