Skip to content

Commit ca8f8a2

Browse files
committed
test(macro): add test infrastructure for macro crate
Fixes: #530
1 parent 9c1285b commit ca8f8a2

File tree

6 files changed

+180
-46
lines changed

6 files changed

+180
-46
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ jobs:
118118
toolchain: ${{ matrix.rust }}
119119
components: rustfmt, clippy
120120
- run: rustup show
121+
- name: Install Cargo expand
122+
uses: dtolnay/install@cargo-expand
121123
- name: Cache cargo dependencies
122124
uses: Swatinem/rust-cache@v2
123125
# Uncomment the following if statement if caching nightly deps

.github/workflows/coverage.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ jobs:
5555
echo "LIBCLANG_PATH=${{ runner.temp }}/llvm-${{ env.clang }}/lib" >> $GITHUB_ENV
5656
echo "LLVM_VERSION=${{ steps.clang.outputs.version }}" >> $GITHUB_ENV
5757
echo "LLVM_CONFIG_PATH=${{ runner.temp }}/llvm-${{ env.clang }}/bin/llvm-config" >> $GITHUB_ENV
58+
- name: Install Cargo expand
59+
uses: dtolnay/install@cargo-expand
5860
- name: Install tarpaulin
59-
run: |
60-
cargo install cargo-tarpaulin --locked
61-
cargo tarpaulin --version
61+
uses: dtolnay/install@cargo-tarpaulin
6262
- name: Run tests
6363
run: |
6464
cargo tarpaulin --engine llvm --workspace --all-features --tests --exclude tests --exclude-files docsrs_bindings.rs --timeout 120 --out Xml

crates/macros/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,8 @@ convert_case = "0.8.0"
2222

2323
[lints.rust]
2424
missing_docs = "warn"
25+
26+
[dev-dependencies]
27+
glob = "0.3.2"
28+
macrotest = "1.1.0"
29+
runtime-macros = "1.1.1"

crates/macros/src/lib.rs

Lines changed: 147 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ mod syn_ext;
1212
mod zval;
1313

1414
use proc_macro::TokenStream;
15-
use syn::{
16-
parse_macro_input, DeriveInput, ItemConst, ItemFn, ItemForeignMod, ItemImpl, ItemStruct,
17-
};
15+
use proc_macro2::TokenStream as TokenStream2;
16+
use syn::{DeriveInput, ItemConst, ItemFn, ItemForeignMod, ItemImpl, ItemStruct};
1817

1918
extern crate proc_macro;
2019

@@ -203,14 +202,17 @@ extern crate proc_macro;
203202
// END DOCS FROM classes.md
204203
#[proc_macro_attribute]
205204
pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
206-
let input = parse_macro_input!(input as ItemStruct);
205+
php_class_internal(args.into(), input.into()).into()
206+
}
207+
208+
#[allow(clippy::needless_pass_by_value)]
209+
fn php_class_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
210+
let input = parse_macro_input2!(input as ItemStruct);
207211
if !args.is_empty() {
208-
return err!(input => "`#[php_class(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error().into();
212+
return err!(input => "`#[php_class(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
209213
}
210214

211-
class::parser(input)
212-
.unwrap_or_else(|e| e.to_compile_error())
213-
.into()
215+
class::parser(input).unwrap_or_else(|e| e.to_compile_error())
214216
}
215217

216218
// BEGIN DOCS FROM function.md
@@ -371,14 +373,17 @@ pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
371373
// END DOCS FROM function.md
372374
#[proc_macro_attribute]
373375
pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
374-
let input = parse_macro_input!(input as ItemFn);
376+
php_function_internal(args.into(), input.into()).into()
377+
}
378+
379+
#[allow(clippy::needless_pass_by_value)]
380+
fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
381+
let input = parse_macro_input2!(input as ItemFn);
375382
if !args.is_empty() {
376-
return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error().into();
383+
return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
377384
}
378385

379-
function::parser(input)
380-
.unwrap_or_else(|e| e.to_compile_error())
381-
.into()
386+
function::parser(input).unwrap_or_else(|e| e.to_compile_error())
382387
}
383388

384389
// BEGIN DOCS FROM constant.md
@@ -436,14 +441,17 @@ pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
436441
// END DOCS FROM constant.md
437442
#[proc_macro_attribute]
438443
pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
439-
let input = parse_macro_input!(input as ItemConst);
444+
php_const_internal(args.into(), input.into()).into()
445+
}
446+
447+
#[allow(clippy::needless_pass_by_value)]
448+
fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
449+
let input = parse_macro_input2!(input as ItemConst);
440450
if !args.is_empty() {
441-
return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error().into();
451+
return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
442452
}
443453

444-
constant::parser(input)
445-
.unwrap_or_else(|e| e.to_compile_error())
446-
.into()
454+
constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
447455
}
448456

449457
// BEGIN DOCS FROM module.md
@@ -516,14 +524,17 @@ pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
516524
// END DOCS FROM module.md
517525
#[proc_macro_attribute]
518526
pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
519-
let input = parse_macro_input!(input as ItemFn);
527+
php_module_internal(args.into(), input.into()).into()
528+
}
529+
530+
#[allow(clippy::needless_pass_by_value)]
531+
fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
532+
let input = parse_macro_input2!(input as ItemFn);
520533
if !args.is_empty() {
521-
return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error().into();
534+
return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
522535
}
523536

524-
module::parser(input)
525-
.unwrap_or_else(|e| e.to_compile_error())
526-
.into()
537+
module::parser(input).unwrap_or_else(|e| e.to_compile_error())
527538
}
528539

529540
// BEGIN DOCS FROM impl.md
@@ -713,14 +724,17 @@ pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
713724
// END DOCS FROM impl.md
714725
#[proc_macro_attribute]
715726
pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
716-
let input = parse_macro_input!(input as ItemImpl);
727+
php_impl_internal(args.into(), input.into()).into()
728+
}
729+
730+
#[allow(clippy::needless_pass_by_value)]
731+
fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
732+
let input = parse_macro_input2!(input as ItemImpl);
717733
if !args.is_empty() {
718-
return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error().into();
734+
return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
719735
}
720736

721-
impl_::parser(input)
722-
.unwrap_or_else(|e| e.to_compile_error())
723-
.into()
737+
impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
724738
}
725739

726740
// BEGIN DOCS FROM extern.md
@@ -789,12 +803,15 @@ pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
789803
/// [`Zval`]: crate::types::Zval
790804
// END DOCS FROM extern.md
791805
#[proc_macro_attribute]
792-
pub fn php_extern(_: TokenStream, input: TokenStream) -> TokenStream {
793-
let input = parse_macro_input!(input as ItemForeignMod);
806+
pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
807+
php_extern_internal(args.into(), input.into()).into()
808+
}
809+
810+
#[allow(clippy::needless_pass_by_value)]
811+
fn php_extern_internal(_: TokenStream2, input: TokenStream2) -> TokenStream2 {
812+
let input = parse_macro_input2!(input as ItemForeignMod);
794813

795-
extern_::parser(input)
796-
.unwrap_or_else(|e| e.to_compile_error())
797-
.into()
814+
extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
798815
}
799816

800817
// BEGIN DOCS FROM zval_convert.md
@@ -953,11 +970,13 @@ pub fn php_extern(_: TokenStream, input: TokenStream) -> TokenStream {
953970
// END DOCS FROM zval_convert.md
954971
#[proc_macro_derive(ZvalConvert)]
955972
pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
956-
let input = parse_macro_input!(input as DeriveInput);
973+
zval_convert_derive_internal(input.into()).into()
974+
}
957975

958-
zval::parser(input)
959-
.unwrap_or_else(|e| e.to_compile_error())
960-
.into()
976+
fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
977+
let input = parse_macro_input2!(input as DeriveInput);
978+
979+
zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
961980
}
962981

963982
/// Defines an `extern` function with the Zend fastcall convention based on
@@ -992,35 +1011,61 @@ pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
9921011
/// Rust and the `abi_vectorcall` feature enabled.
9931012
#[proc_macro]
9941013
pub fn zend_fastcall(input: TokenStream) -> TokenStream {
995-
let input = parse_macro_input!(input as ItemFn);
1014+
zend_fastcall_internal(input.into()).into()
1015+
}
9961016

997-
fastcall::parser(input).into()
1017+
fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
1018+
let input = parse_macro_input2!(input as ItemFn);
1019+
1020+
fastcall::parser(input)
9981021
}
9991022

10001023
/// Wraps a function to be used in the [`Module::function`] method.
10011024
#[proc_macro]
10021025
pub fn wrap_function(input: TokenStream) -> TokenStream {
1003-
let input = parse_macro_input!(input as syn::Path);
1026+
wrap_function_internal(input.into()).into()
1027+
}
1028+
1029+
fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
1030+
let input = parse_macro_input2!(input as syn::Path);
10041031

10051032
match function::wrap(&input) {
10061033
Ok(parsed) => parsed,
10071034
Err(e) => e.to_compile_error(),
10081035
}
1009-
.into()
10101036
}
10111037

10121038
/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
10131039
#[proc_macro]
10141040
pub fn wrap_constant(input: TokenStream) -> TokenStream {
1015-
let input = parse_macro_input!(input as syn::Path);
1041+
wrap_constant_internal(input.into()).into()
1042+
}
1043+
1044+
fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
1045+
let input = parse_macro_input2!(input as syn::Path);
10161046

10171047
match constant::wrap(&input) {
10181048
Ok(parsed) => parsed,
10191049
Err(e) => e.to_compile_error(),
10201050
}
1021-
.into()
10221051
}
10231052

1053+
macro_rules! parse_macro_input2 {
1054+
($tokenstream:ident as $ty:ty) => {
1055+
match syn::parse2::<$ty>($tokenstream) {
1056+
Ok(data) => data,
1057+
Err(err) => {
1058+
return proc_macro2::TokenStream::from(err.to_compile_error());
1059+
}
1060+
}
1061+
};
1062+
($tokenstream:ident) => {
1063+
$crate::parse_macro_input!($tokenstream as _)
1064+
};
1065+
}
1066+
1067+
pub(crate) use parse_macro_input2;
1068+
10241069
macro_rules! err {
10251070
($span:expr => $($msg:tt)*) => {
10261071
::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
@@ -1061,3 +1106,62 @@ pub(crate) mod prelude {
10611106
pub(crate) use crate::{bail, err};
10621107
pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
10631108
}
1109+
1110+
#[cfg(test)]
1111+
mod tests {
1112+
use super::*;
1113+
use std::path::PathBuf;
1114+
1115+
type AttributeFn =
1116+
fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1117+
type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1118+
1119+
#[test]
1120+
pub fn test_expand() {
1121+
macrotest::expand("tests/expand/*.rs");
1122+
for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
1123+
let entry = entry.expect("Failed to read expand test file");
1124+
runtime_expand_attr(&entry);
1125+
runtime_expand_func(&entry);
1126+
runtime_expand_derive(&entry);
1127+
}
1128+
}
1129+
1130+
fn runtime_expand_attr(path: &PathBuf) {
1131+
let file = std::fs::File::open(path).expect("Failed to open expand test file");
1132+
runtime_macros::emulate_attributelike_macro_expansion(
1133+
file,
1134+
&[
1135+
("php_class", php_class_internal as AttributeFn),
1136+
("php_const", php_const_internal as AttributeFn),
1137+
("php_extern", php_extern_internal as AttributeFn),
1138+
("php_function", php_function_internal as AttributeFn),
1139+
("php_impl", php_impl_internal as AttributeFn),
1140+
("php_module", php_module_internal as AttributeFn),
1141+
],
1142+
)
1143+
.expect("Failed to expand attribute macros in test file");
1144+
}
1145+
1146+
fn runtime_expand_func(path: &PathBuf) {
1147+
let file = std::fs::File::open(path).expect("Failed to open expand test file");
1148+
runtime_macros::emulate_functionlike_macro_expansion(
1149+
file,
1150+
&[
1151+
("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
1152+
("wrap_function", wrap_function_internal as FunctionLikeFn),
1153+
("wrap_constant", wrap_constant_internal as FunctionLikeFn),
1154+
],
1155+
)
1156+
.expect("Failed to expand function-like macros in test file");
1157+
}
1158+
1159+
fn runtime_expand_derive(path: &PathBuf) {
1160+
let file = std::fs::File::open(path).expect("Failed to open expand test file");
1161+
runtime_macros::emulate_derive_macro_expansion(
1162+
file,
1163+
&[("ZvalConvert", zval_convert_derive_internal)],
1164+
)
1165+
.expect("Failed to expand derive macros in test file");
1166+
}
1167+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#[macro_use]
2+
extern crate ext_php_rs_derive;
3+
const MY_CONST: &str = "Hello, world!";
4+
#[allow(non_upper_case_globals)]
5+
const _internal_const_docs_MY_CONST: &[&str] = &[];
6+
#[allow(non_upper_case_globals)]
7+
const _internal_const_name_MY_CONST: &str = "MY_CONST";
8+
fn main() {
9+
(
10+
_internal_const_name_MY_CONST,
11+
MY_CONST,
12+
_internal_const_docs_MY_CONST,
13+
);
14+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#[macro_use]
2+
extern crate ext_php_rs_derive;
3+
4+
#[php_const]
5+
const MY_CONST: &str = "Hello, world!";
6+
7+
fn main() {
8+
wrap_constant!(MY_CONST);
9+
}

0 commit comments

Comments
 (0)