pgrx_macros/lib.rs
1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10extern crate proc_macro;
11
12use proc_macro::TokenStream;
13use std::collections::HashSet;
14
15use proc_macro2::Ident;
16use quote::{format_ident, quote, ToTokens};
17use syn::spanned::Spanned;
18use syn::{parse_macro_input, Attribute, Data, DeriveInput, Item, ItemImpl};
19
20use operators::{deriving_postgres_eq, deriving_postgres_hash, deriving_postgres_ord};
21use pgrx_sql_entity_graph as sql_gen;
22use sql_gen::{
23 parse_extern_attributes, CodeEnrichment, ExtensionSql, ExtensionSqlFile, ExternArgs,
24 PgAggregate, PgCast, PgExtern, PostgresEnum, Schema,
25};
26
27mod operators;
28mod rewriter;
29
30/// Declare a function as `#[pg_guard]` to indicate that it is called from a Postgres `extern "C"`
31/// function so that Rust `panic!()`s (and Postgres `elog(ERROR)`s) will be properly handled by `pgrx`
32#[proc_macro_attribute]
33pub fn pg_guard(_attr: TokenStream, item: TokenStream) -> TokenStream {
34 // get a usable token stream
35 let ast = parse_macro_input!(item as syn::Item);
36
37 let res = match ast {
38 // this is for processing the members of extern "C" { } blocks
39 // functions inside the block get wrapped as public, top-level unsafe functions that are not "extern"
40 Item::ForeignMod(block) => Ok(rewriter::extern_block(block)),
41
42 // process top-level functions
43 Item::Fn(func) => rewriter::item_fn_without_rewrite(func),
44 unknown => Err(syn::Error::new(
45 unknown.span(),
46 "#[pg_guard] can only be applied to extern \"C\" blocks and top-level functions",
47 )),
48 };
49 res.unwrap_or_else(|e| e.into_compile_error()).into()
50}
51
52/// `#[pg_test]` functions are test functions (akin to `#[test]`), but they run in-process inside
53/// Postgres during `cargo pgrx test`.
54///
55/// This can be combined with test attributes like [`#[should_panic(expected = "..")]`][expected].
56///
57/// [expected]: https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute
58#[proc_macro_attribute]
59pub fn pg_test(attr: TokenStream, item: TokenStream) -> TokenStream {
60 let mut stream = proc_macro2::TokenStream::new();
61 let args = parse_extern_attributes(proc_macro2::TokenStream::from(attr.clone()));
62
63 let mut expected_error = None;
64 args.into_iter().for_each(|v| {
65 if let ExternArgs::ShouldPanic(message) = v {
66 expected_error = Some(message)
67 }
68 });
69
70 let ast = parse_macro_input!(item as syn::Item);
71
72 match ast {
73 Item::Fn(mut func) => {
74 // Here we need to break out attributes into test and non-test attributes,
75 // so the generated #[test] attributes are in the appropriate place.
76 let mut test_attributes = Vec::new();
77 let mut non_test_attributes = Vec::new();
78
79 for attribute in func.attrs.iter() {
80 if let Some(ident) = attribute.path().get_ident() {
81 let ident_str = ident.to_string();
82
83 if ident_str == "ignore" || ident_str == "should_panic" {
84 test_attributes.push(attribute.clone());
85 } else {
86 non_test_attributes.push(attribute.clone());
87 }
88 } else {
89 non_test_attributes.push(attribute.clone());
90 }
91 }
92
93 func.attrs = non_test_attributes;
94
95 stream.extend(proc_macro2::TokenStream::from(pg_extern(
96 attr,
97 Item::Fn(func.clone()).to_token_stream().into(),
98 )));
99
100 let expected_error = match expected_error {
101 Some(msg) => quote! {Some(#msg)},
102 None => quote! {None},
103 };
104
105 let sql_funcname = func.sig.ident.to_string();
106 let test_func_name = format_ident!("pg_{}", func.sig.ident);
107
108 let attributes = func.attrs;
109 let mut att_stream = proc_macro2::TokenStream::new();
110
111 for a in attributes.iter() {
112 let as_str = a.to_token_stream().to_string();
113 att_stream.extend(quote! {
114 options.push(#as_str);
115 });
116 }
117
118 stream.extend(quote! {
119 #[test]
120 #(#test_attributes)*
121 fn #test_func_name() {
122 let mut options = Vec::new();
123 #att_stream
124
125 crate::pg_test::setup(options);
126 let res = pgrx_tests::run_test(#sql_funcname, #expected_error, crate::pg_test::postgresql_conf_options());
127 match res {
128 Ok(()) => (),
129 Err(e) => panic!("{e:?}")
130 }
131 }
132 });
133 }
134
135 thing => {
136 return syn::Error::new(
137 thing.span(),
138 "#[pg_test] can only be applied to top-level functions",
139 )
140 .into_compile_error()
141 .into()
142 }
143 }
144
145 stream.into()
146}
147
148/// Associated macro for `#[pg_test]` to provide context back to your test framework to indicate
149/// that the test system is being initialized
150#[proc_macro_attribute]
151pub fn initialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
152 item
153}
154
155/**
156Declare a function as `#[pg_cast]` to indicate that it represents a Postgres [cast](https://www.postgresql.org/docs/current/sql-createcast.html).
157
158* `assignment`: Corresponds to [`AS ASSIGNMENT`](https://www.postgresql.org/docs/current/sql-createcast.html).
159* `implicit`: Corresponds to [`AS IMPLICIT`](https://www.postgresql.org/docs/current/sql-createcast.html).
160
161By default if no attribute is specified, the cast function can only be used in an explicit cast.
162
163Functions MUST accept and return exactly one value whose type MUST be a `pgrx` supported type. `pgrx` supports many PostgreSQL types by default.
164New types can be defined via [`macro@PostgresType`] or [`macro@PostgresEnum`].
165
166`#[pg_cast]` also supports all the attributes supported by the [`macro@pg_extern]` macro, which are
167passed down to the underlying function.
168
169Example usage:
170```rust,ignore
171use pgrx::*;
172#[pg_cast(implicit)]
173fn cast_json_to_int(input: Json) -> i32 { todo!() }
174*/
175#[proc_macro_attribute]
176pub fn pg_cast(attr: TokenStream, item: TokenStream) -> TokenStream {
177 fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
178 use syn::parse::Parser;
179 use syn::punctuated::Punctuated;
180
181 let mut cast = None;
182 let mut pg_extern_attrs = proc_macro2::TokenStream::new();
183
184 // look for the attributes `#[pg_cast]` directly understands
185 match Punctuated::<syn::Path, syn::Token![,]>::parse_terminated.parse(attr) {
186 Ok(paths) => {
187 let mut new_paths = Punctuated::<syn::Path, syn::Token![,]>::new();
188 for path in paths {
189 match (PgCast::try_from(path), &cast) {
190 (Ok(style), None) => cast = Some(style),
191 (Ok(_), Some(cast)) => {
192 panic!("The cast type has already been set to `{cast:?}`")
193 }
194
195 // ... and anything it doesn't understand is blindly passed through to the
196 // underlying `#[pg_extern]` function that gets created, which will ultimately
197 // decide what's naughty and what's nice
198 (Err(unknown), _) => {
199 new_paths.push(unknown);
200 }
201 }
202 }
203
204 pg_extern_attrs.extend(new_paths.into_token_stream());
205 }
206 Err(err) => {
207 panic!("Failed to parse attribute to pg_cast: {err}")
208 }
209 }
210
211 let pg_extern = PgExtern::new(pg_extern_attrs.into(), item.clone().into())?.0;
212 Ok(CodeEnrichment(pg_extern.as_cast(cast.unwrap_or_default())).to_token_stream().into())
213 }
214
215 wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
216}
217
218/// Declare a function as `#[pg_operator]` to indicate that it represents a Postgres operator
219/// `cargo pgrx schema` will automatically generate the underlying SQL
220#[proc_macro_attribute]
221pub fn pg_operator(attr: TokenStream, item: TokenStream) -> TokenStream {
222 pg_extern(attr, item)
223}
224
225/// Used with `#[pg_operator]`. 1 value which is the operator name itself
226#[proc_macro_attribute]
227pub fn opname(_attr: TokenStream, item: TokenStream) -> TokenStream {
228 item
229}
230
231/// Used with `#[pg_operator]`. 1 value which is the function name
232#[proc_macro_attribute]
233pub fn commutator(_attr: TokenStream, item: TokenStream) -> TokenStream {
234 item
235}
236
237/// Used with `#[pg_operator]`. 1 value which is the function name
238#[proc_macro_attribute]
239pub fn negator(_attr: TokenStream, item: TokenStream) -> TokenStream {
240 item
241}
242
243/// Used with `#[pg_operator]`. 1 value which is the function name
244#[proc_macro_attribute]
245pub fn restrict(_attr: TokenStream, item: TokenStream) -> TokenStream {
246 item
247}
248
249/// Used with `#[pg_operator]`. 1 value which is the function name
250#[proc_macro_attribute]
251pub fn join(_attr: TokenStream, item: TokenStream) -> TokenStream {
252 item
253}
254
255/// Used with `#[pg_operator]`. no values
256#[proc_macro_attribute]
257pub fn hashes(_attr: TokenStream, item: TokenStream) -> TokenStream {
258 item
259}
260
261/// Used with `#[pg_operator]`. no values
262#[proc_macro_attribute]
263pub fn merges(_attr: TokenStream, item: TokenStream) -> TokenStream {
264 item
265}
266
267/**
268Declare a Rust module and its contents to be in a schema.
269
270The schema name will always be the `mod`'s identifier. So `mod flop` will create a `flop` schema.
271
272If there is a schema inside a schema, the most specific schema is chosen.
273
274In this example, the created `example` function is in the `dsl_filters` schema.
275
276```rust,ignore
277use pgrx::*;
278
279#[pg_schema]
280mod dsl {
281 use pgrx::*;
282 #[pg_schema]
283 mod dsl_filters {
284 use pgrx::*;
285 #[pg_extern]
286 fn example() { todo!() }
287 }
288}
289```
290
291File modules (like `mod name;`) aren't able to be supported due to [`rust/#54725`](https://github.com/rust-lang/rust/issues/54725).
292
293*/
294#[proc_macro_attribute]
295pub fn pg_schema(_attr: TokenStream, input: TokenStream) -> TokenStream {
296 fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
297 let pgrx_schema: Schema = syn::parse(input)?;
298 Ok(pgrx_schema.to_token_stream().into())
299 }
300
301 wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
302}
303
304/**
305Declare SQL to be included in generated extension script.
306
307Accepts a String literal, a `name` attribute, and optionally others:
308
309* `name = "item"`: Set the unique identifier to `"item"` for use in `requires` declarations.
310* `requires = [item, item_two]`: References to other `name`s or Rust items which this SQL should be present after.
311* `creates = [ Type(submod::Cust), Enum(Pre), Function(defined)]`: Communicates that this SQL block creates certain entities.
312 Please note it **does not** create matching Rust types.
313* `bootstrap` (**Unique**): Communicates that this is SQL intended to go before all other generated SQL.
314* `finalize` (**Unique**): Communicates that this is SQL intended to go after all other generated SQL.
315
316You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:
317
318```rust,ignore
319use pgrx_macros::extension_sql;
320
321extension_sql!(
322 r#"
323 -- SQL statements
324 "#,
325 name = "demo",
326);
327```
328
329To cause the SQL to be output at the start of the generated SQL:
330
331```rust,ignore
332use pgrx_macros::extension_sql;
333
334extension_sql!(
335 r#"
336 -- SQL statements
337 "#,
338 name = "demo",
339 bootstrap,
340);
341```
342
343To cause the SQL to be output at the end of the generated SQL:
344
345```rust,ignore
346use pgrx_macros::extension_sql;
347
348extension_sql!(
349 r#"
350 -- SQL statements
351 "#,
352 name = "demo",
353 finalize,
354);
355```
356
357To declare the SQL dependent, or a dependency of, other items:
358
359```rust,ignore
360use pgrx_macros::extension_sql;
361
362struct Treat;
363
364mod dog_characteristics {
365 enum DogAlignment {
366 Good
367 }
368}
369
370extension_sql!(r#"
371 -- SQL statements
372 "#,
373 name = "named_one",
374);
375
376extension_sql!(r#"
377 -- SQL statements
378 "#,
379 name = "demo",
380 requires = [ "named_one", dog_characteristics::DogAlignment ],
381);
382```
383
384To declare the SQL defines some entity (**Caution:** This is not recommended usage):
385
386```rust,ignore
387use pgrx::stringinfo::StringInfo;
388use pgrx::*;
389use pgrx_utils::get_named_capture;
390
391#[derive(Debug)]
392#[repr(C)]
393struct Complex {
394 x: f64,
395 y: f64,
396}
397
398extension_sql!(r#"\
399 CREATE TYPE complex;\
400 "#,
401 name = "create_complex_type",
402 creates = [Type(Complex)],
403);
404
405#[pg_extern(immutable)]
406fn complex_in(input: &core::ffi::CStr) -> PgBox<Complex> {
407 todo!()
408}
409
410#[pg_extern(immutable)]
411fn complex_out(complex: PgBox<Complex>) -> &'static ::core::ffi::CStr {
412 todo!()
413}
414
415extension_sql!(r#"\
416 CREATE TYPE complex (
417 internallength = 16,
418 input = complex_in,
419 output = complex_out,
420 alignment = double
421 );\
422 "#,
423 name = "demo",
424 requires = ["create_complex_type", complex_in, complex_out],
425);
426
427```
428*/
429#[proc_macro]
430pub fn extension_sql(input: TokenStream) -> TokenStream {
431 fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
432 let ext_sql: CodeEnrichment<ExtensionSql> = syn::parse(input)?;
433 Ok(ext_sql.to_token_stream().into())
434 }
435
436 wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
437}
438
439/**
440Declare SQL (from a file) to be included in generated extension script.
441
442Accepts the same options as [`macro@extension_sql`]. `name` is automatically set to the file name (not the full path).
443
444You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:
445
446```rust,ignore
447use pgrx_macros::extension_sql_file;
448extension_sql_file!(
449 "../static/demo.sql",
450);
451```
452
453To override the default name:
454
455```rust,ignore
456use pgrx_macros::extension_sql_file;
457
458extension_sql_file!(
459 "../static/demo.sql",
460 name = "singular",
461);
462```
463
464For all other options, and examples of them, see [`macro@extension_sql`].
465*/
466#[proc_macro]
467pub fn extension_sql_file(input: TokenStream) -> TokenStream {
468 fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
469 let ext_sql: CodeEnrichment<ExtensionSqlFile> = syn::parse(input)?;
470 Ok(ext_sql.to_token_stream().into())
471 }
472
473 wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
474}
475
476/// Associated macro for `#[pg_extern]` or `#[macro@pg_operator]`. Used to set the `SEARCH_PATH` option
477/// on the `CREATE FUNCTION` statement.
478#[proc_macro_attribute]
479pub fn search_path(_attr: TokenStream, item: TokenStream) -> TokenStream {
480 item
481}
482
483/**
484Declare a function as `#[pg_extern]` to indicate that it can be used by Postgres as a UDF.
485
486Optionally accepts the following attributes:
487
488* `immutable`: Corresponds to [`IMMUTABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
489* `strict`: Corresponds to [`STRICT`](https://www.postgresql.org/docs/current/sql-createfunction.html).
490 + In most cases, `#[pg_extern]` can detect when no `Option<T>`s are used, and automatically set this.
491* `stable`: Corresponds to [`STABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
492* `volatile`: Corresponds to [`VOLATILE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
493* `raw`: Corresponds to [`RAW`](https://www.postgresql.org/docs/current/sql-createfunction.html).
494* `security_definer`: Corresponds to [`SECURITY DEFINER`](https://www.postgresql.org/docs/current/sql-createfunction.html)
495* `security_invoker`: Corresponds to [`SECURITY INVOKER`](https://www.postgresql.org/docs/current/sql-createfunction.html)
496* `parallel_safe`: Corresponds to [`PARALLEL SAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
497* `parallel_unsafe`: Corresponds to [`PARALLEL UNSAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
498* `parallel_restricted`: Corresponds to [`PARALLEL RESTRICTED`](https://www.postgresql.org/docs/current/sql-createfunction.html).
499* `no_guard`: Do not use `#[pg_guard]` with the function.
500* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
501* `name`: Specifies target function name. Defaults to Rust function name.
502
503Functions can accept and return any type which `pgrx` supports. `pgrx` supports many PostgreSQL types by default.
504New types can be defined via [`macro@PostgresType`] or [`macro@PostgresEnum`].
505
506
507Without any arguments or returns:
508```rust,ignore
509use pgrx::*;
510#[pg_extern]
511fn foo() { todo!() }
512```
513
514# Arguments
515It's possible to pass even complex arguments:
516
517```rust,ignore
518use pgrx::*;
519#[pg_extern]
520fn boop(
521 a: i32,
522 b: Option<i32>,
523 c: Vec<i32>,
524 d: Option<Vec<Option<i32>>>
525) { todo!() }
526```
527
528It's possible to set argument defaults, set by PostgreSQL when the function is invoked:
529
530```rust,ignore
531use pgrx::*;
532#[pg_extern]
533fn boop(a: default!(i32, 11111)) { todo!() }
534#[pg_extern]
535fn doop(
536 a: default!(Vec<Option<&str>>, "ARRAY[]::text[]"),
537 b: default!(String, "'note the inner quotes!'")
538) { todo!() }
539```
540
541The `default!()` macro may only be used in argument position.
542
543It accepts 2 arguments:
544
545* A type
546* A `bool`, numeric, or SQL string to represent the default. `"NULL"` is a possible value, as is `"'string'"`
547
548**If the default SQL entity created by the extension:** ensure it is added to `requires` as a dependency:
549
550```rust,ignore
551use pgrx::*;
552#[pg_extern]
553fn default_value() -> i32 { todo!() }
554
555#[pg_extern(
556 requires = [ default_value, ],
557)]
558fn do_it(
559 a: default!(i32, "default_value()"),
560) { todo!() }
561```
562
563# Returns
564
565It's possible to return even complex values, as well:
566
567```rust,ignore
568use pgrx::*;
569#[pg_extern]
570fn boop() -> i32 { todo!() }
571#[pg_extern]
572fn doop() -> Option<i32> { todo!() }
573#[pg_extern]
574fn swoop() -> Option<Vec<Option<i32>>> { todo!() }
575#[pg_extern]
576fn floop() -> (i32, i32) { todo!() }
577```
578
579Like in PostgreSQL, it's possible to return tables using iterators and the `name!()` macro:
580
581```rust,ignore
582use pgrx::*;
583#[pg_extern]
584fn floop<'a>() -> TableIterator<'a, (name!(a, i32), name!(b, i32))> {
585 TableIterator::new(None.into_iter())
586}
587
588#[pg_extern]
589fn singular_floop() -> (name!(a, i32), name!(b, i32)) {
590 todo!()
591}
592```
593
594The `name!()` macro may only be used in return position inside the `T` of a `TableIterator<'a, T>`.
595
596It accepts 2 arguments:
597
598* A name, such as `example`
599* A type
600
601# Special Cases
602
603`pg_sys::Oid` is a special cased type alias, in order to use it as an argument or return it must be
604passed with it's full module path (`pg_sys::Oid`) in order to be resolved.
605
606```rust,ignore
607use pgrx::*;
608
609#[pg_extern]
610fn example_arg(animals: pg_sys::Oid) {
611 todo!()
612}
613
614#[pg_extern]
615fn example_return() -> pg_sys::Oid {
616 todo!()
617}
618```
619
620*/
621#[proc_macro_attribute]
622#[track_caller]
623pub fn pg_extern(attr: TokenStream, item: TokenStream) -> TokenStream {
624 fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
625 let pg_extern_item = PgExtern::new(attr.into(), item.into())?;
626 Ok(pg_extern_item.to_token_stream().into())
627 }
628
629 wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
630}
631
632/**
633Generate necessary bindings for using the enum with PostgreSQL.
634
635```rust,ignore
636# use pgrx_pg_sys as pg_sys;
637use pgrx::*;
638use serde::{Deserialize, Serialize};
639#[derive(Debug, Serialize, Deserialize, PostgresEnum)]
640enum DogNames {
641 Nami,
642 Brandy,
643}
644```
645
646*/
647#[proc_macro_derive(PostgresEnum, attributes(requires, pgrx))]
648pub fn postgres_enum(input: TokenStream) -> TokenStream {
649 let ast = parse_macro_input!(input as syn::DeriveInput);
650
651 impl_postgres_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
652}
653
654fn impl_postgres_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
655 let mut stream = proc_macro2::TokenStream::new();
656 let sql_graph_entity_ast = ast.clone();
657 let generics = &ast.generics.clone();
658 let enum_ident = &ast.ident;
659 let enum_name = enum_ident.to_string();
660
661 // validate that we're only operating on an enum
662 let Data::Enum(enum_data) = ast.data else {
663 return Err(syn::Error::new(
664 ast.span(),
665 "#[derive(PostgresEnum)] can only be applied to enums",
666 ));
667 };
668
669 let mut from_datum = proc_macro2::TokenStream::new();
670 let mut into_datum = proc_macro2::TokenStream::new();
671
672 for d in enum_data.variants.clone() {
673 let label_ident = &d.ident;
674 let label_string = label_ident.to_string();
675
676 from_datum.extend(quote! { #label_string => Some(#enum_ident::#label_ident), });
677 into_datum.extend(quote! { #enum_ident::#label_ident => Some(::pgrx::enum_helper::lookup_enum_by_label(#enum_name, #label_string)), });
678 }
679
680 // We need another variant of the params for the ArgAbi impl
681 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
682 let mut generics_with_fcx = generics.clone();
683 // so that we can bound on Self: 'fcx
684 generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
685 syn::PredicateType {
686 lifetimes: None,
687 bounded_ty: syn::parse_quote! { Self },
688 colon_token: syn::Token),
689 bounds: syn::parse_quote! { #fcx_lt },
690 },
691 ));
692 let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
693 let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
694 impl_gens
695 .params
696 .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
697
698 stream.extend(quote! {
699 impl ::pgrx::datum::FromDatum for #enum_ident {
700 #[inline]
701 unsafe fn from_polymorphic_datum(datum: ::pgrx::pg_sys::Datum, is_null: bool, typeoid: ::pgrx::pg_sys::Oid) -> Option<#enum_ident> {
702 if is_null {
703 None
704 } else {
705 // GREPME: non-primitive cast u64 as Oid
706 let (name, _, _) = ::pgrx::enum_helper::lookup_enum_by_oid(unsafe { ::pgrx::pg_sys::Oid::from_datum(datum, is_null)? } );
707 match name.as_str() {
708 #from_datum
709 _ => panic!("invalid enum value: {name}")
710 }
711 }
712 }
713 }
714
715 unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #enum_ident #ty_gens #where_clause {
716 unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
717 let index = arg.index();
718 unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
719 }
720
721 }
722
723 unsafe impl #generics ::pgrx::datum::UnboxDatum for #enum_ident #generics {
724 type As<'dat> = #enum_ident #generics where Self: 'dat;
725 #[inline]
726 unsafe fn unbox<'dat>(d: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
727 Self::from_datum(::core::mem::transmute(d), false).unwrap()
728 }
729 }
730
731 impl ::pgrx::datum::IntoDatum for #enum_ident {
732 #[inline]
733 fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
734 match self {
735 #into_datum
736 }
737 }
738
739 fn type_oid() -> ::pgrx::pg_sys::Oid {
740 ::pgrx::wrappers::regtypein(#enum_name)
741 }
742
743 }
744
745 unsafe impl ::pgrx::callconv::BoxRet for #enum_ident {
746 unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
747 match ::pgrx::datum::IntoDatum::into_datum(self) {
748 None => fcinfo.return_null(),
749 Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
750 }
751 }
752 }
753 });
754
755 let sql_graph_entity_item = PostgresEnum::from_derive_input(sql_graph_entity_ast)?;
756 sql_graph_entity_item.to_tokens(&mut stream);
757
758 Ok(stream)
759}
760
761/**
762Generate necessary bindings for using the type with PostgreSQL.
763
764```rust,ignore
765# use pgrx_pg_sys as pg_sys;
766use pgrx::*;
767use serde::{Deserialize, Serialize};
768#[derive(Debug, Serialize, Deserialize, PostgresType)]
769struct Dog {
770 treats_received: i64,
771 pets_gotten: i64,
772}
773
774#[derive(Debug, Serialize, Deserialize, PostgresType)]
775enum Animal {
776 Dog(Dog),
777}
778```
779
780Optionally accepts the following attributes:
781
782* `inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the type.
783* `pgvarlena_inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the `PgVarlena` of this type.
784* `pgrx(alignment = "<align>")`: Derive Postgres alignment from Rust type. One of `"on"`, or `"off"`.
785* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
786*/
787#[proc_macro_derive(
788 PostgresType,
789 attributes(
790 inoutfuncs,
791 pgvarlena_inoutfuncs,
792 bikeshed_postgres_type_manually_impl_from_into_datum,
793 requires,
794 pgrx
795 )
796)]
797pub fn postgres_type(input: TokenStream) -> TokenStream {
798 let ast = parse_macro_input!(input as syn::DeriveInput);
799
800 impl_postgres_type(ast).unwrap_or_else(|e| e.into_compile_error()).into()
801}
802
803fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
804 let name = &ast.ident;
805 let generics = &ast.generics.clone();
806 let has_lifetimes = generics.lifetimes().next();
807 let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
808 let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
809 let mut args = parse_postgres_type_args(&ast.attrs);
810 let mut stream = proc_macro2::TokenStream::new();
811
812 // validate that we're only operating on a struct
813 match ast.data {
814 Data::Struct(_) => { /* this is okay */ }
815 Data::Enum(_) => {
816 // this is okay and if there's an attempt to implement PostgresEnum,
817 // it will result in compile-time error of conflicting implementation
818 // of traits (IntoDatum, inout, etc.)
819 }
820 _ => {
821 return Err(syn::Error::new(
822 ast.span(),
823 "#[derive(PostgresType)] can only be applied to structs or enums",
824 ))
825 }
826 }
827
828 if args.is_empty() {
829 // assume the user wants us to implement the InOutFuncs
830 args.insert(PostgresTypeAttribute::Default);
831 }
832
833 let lifetime = match has_lifetimes {
834 Some(lifetime) => quote! {#lifetime},
835 None => quote! {'_},
836 };
837
838 // We need another variant of the params for the ArgAbi impl
839 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
840 let mut generics_with_fcx = generics.clone();
841 // so that we can bound on Self: 'fcx
842 generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
843 syn::PredicateType {
844 lifetimes: None,
845 bounded_ty: syn::parse_quote! { Self },
846 colon_token: syn::Token),
847 bounds: syn::parse_quote! { #fcx_lt },
848 },
849 ));
850 let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
851 let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
852 impl_gens
853 .params
854 .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
855
856 // all #[derive(PostgresType)] need to implement that trait
857 // and also the FromDatum and IntoDatum
858 stream.extend(quote! {
859 impl #generics ::pgrx::datum::PostgresType for #name #generics { }
860 });
861
862 if !args.contains(&PostgresTypeAttribute::ManualFromIntoDatum) {
863 stream.extend(
864 quote! {
865 impl #generics ::pgrx::datum::IntoDatum for #name #generics {
866 fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
867 #[allow(deprecated)]
868 Some(unsafe { ::pgrx::datum::cbor_encode(&self) }.into())
869 }
870
871 fn type_oid() -> ::pgrx::pg_sys::Oid {
872 ::pgrx::wrappers::rust_regtypein::<Self>()
873 }
874 }
875
876 unsafe impl #generics ::pgrx::callconv::BoxRet for #name #generics {
877 unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
878 match ::pgrx::datum::IntoDatum::into_datum(self) {
879 None => fcinfo.return_null(),
880 Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
881 }
882 }
883 }
884
885 impl #generics ::pgrx::datum::FromDatum for #name #generics {
886 unsafe fn from_polymorphic_datum(
887 datum: ::pgrx::pg_sys::Datum,
888 is_null: bool,
889 _typoid: ::pgrx::pg_sys::Oid,
890 ) -> Option<Self> {
891 if is_null {
892 None
893 } else {
894 #[allow(deprecated)]
895 ::pgrx::datum::cbor_decode(datum.cast_mut_ptr())
896 }
897 }
898
899 unsafe fn from_datum_in_memory_context(
900 mut memory_context: ::pgrx::memcxt::PgMemoryContexts,
901 datum: ::pgrx::pg_sys::Datum,
902 is_null: bool,
903 _typoid: ::pgrx::pg_sys::Oid,
904 ) -> Option<Self> {
905 if is_null {
906 None
907 } else {
908 memory_context.switch_to(|_| {
909 // this gets the varlena Datum copied into this memory context
910 let varlena = ::pgrx::pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
911 Self::from_datum(varlena.into(), is_null)
912 })
913 }
914 }
915 }
916
917 unsafe impl #generics ::pgrx::datum::UnboxDatum for #name #generics {
918 type As<'dat> = Self where Self: 'dat;
919 unsafe fn unbox<'dat>(datum: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
920 <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(datum), false).unwrap()
921 }
922 }
923
924 unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #name #ty_gens #where_clause
925 {
926 unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
927 let index = arg.index();
928 unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
929 }
930 }
931 }
932 )
933 }
934
935 // and if we don't have custom inout/funcs, we use the JsonInOutFuncs trait
936 // which implements _in and _out #[pg_extern] functions that just return the type itself
937 if args.contains(&PostgresTypeAttribute::Default) {
938 stream.extend(quote! {
939 #[doc(hidden)]
940 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
941 pub fn #funcname_in #generics(input: Option<&#lifetime ::core::ffi::CStr>) -> Option<#name #generics> {
942 use ::pgrx::inoutfuncs::json_from_slice;
943 input.map(|cstr| json_from_slice(cstr.to_bytes()).ok()).flatten()
944 }
945
946 #[doc(hidden)]
947 #[::pgrx::pgrx_macros::pg_extern (immutable,parallel_safe)]
948 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
949 use ::pgrx::inoutfuncs::json_to_vec;
950 let mut bytes = json_to_vec(&input).unwrap();
951 bytes.push(0); // terminate
952 ::pgrx::ffi::CString::from_vec_with_nul(bytes).unwrap()
953 }
954 });
955 } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
956 // otherwise if it's InOutFuncs our _in/_out functions use an owned type instance
957 stream.extend(quote! {
958 #[doc(hidden)]
959 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
960 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<#name #generics> {
961 input.map_or_else(|| {
962 for m in <#name as ::pgrx::inoutfuncs::InOutFuncs>::NULL_ERROR_MESSAGE {
963 ::pgrx::pg_sys::error!("{m}");
964 }
965 None
966 }, |i| Some(<#name as ::pgrx::inoutfuncs::InOutFuncs>::input(i)))
967 }
968
969 #[doc(hidden)]
970 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
971 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
972 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
973 ::pgrx::inoutfuncs::InOutFuncs::output(&input, &mut buffer);
974 // SAFETY: We just constructed this StringInfo ourselves
975 unsafe { buffer.leak_cstr().to_owned() }
976 }
977 });
978 } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
979 // otherwise if it's PgVarlenaInOutFuncs our _in/_out functions use a PgVarlena
980 stream.extend(quote! {
981 #[doc(hidden)]
982 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
983 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<::pgrx::datum::PgVarlena<#name #generics>> {
984 input.map_or_else(|| {
985 for m in <#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::NULL_ERROR_MESSAGE {
986 ::pgrx::pg_sys::error!("{m}");
987 }
988 None
989 }, |i| Some(<#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::input(i)))
990 }
991
992 #[doc(hidden)]
993 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
994 pub fn #funcname_out #generics(input: ::pgrx::datum::PgVarlena<#name #generics>) -> ::pgrx::ffi::CString {
995 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
996 ::pgrx::inoutfuncs::PgVarlenaInOutFuncs::output(&*input, &mut buffer);
997 // SAFETY: We just constructed this StringInfo ourselves
998 unsafe { buffer.leak_cstr().to_owned() }
999 }
1000 });
1001 }
1002
1003 let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(ast)?;
1004 sql_graph_entity_item.to_tokens(&mut stream);
1005
1006 Ok(stream)
1007}
1008
1009/// Derives the `GucEnum` trait, so that normal Rust enums can be used as a GUC.
1010#[proc_macro_derive(PostgresGucEnum, attributes(hidden))]
1011pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
1012 let ast = parse_macro_input!(input as syn::DeriveInput);
1013
1014 impl_guc_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1015}
1016
1017fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1018 let mut stream = proc_macro2::TokenStream::new();
1019
1020 // validate that we're only operating on an enum
1021 let enum_data = match ast.data {
1022 Data::Enum(e) => e,
1023 _ => {
1024 return Err(syn::Error::new(
1025 ast.span(),
1026 "#[derive(PostgresGucEnum)] can only be applied to enums",
1027 ))
1028 }
1029 };
1030 let enum_name = ast.ident;
1031 let enum_len = enum_data.variants.len();
1032
1033 let mut from_match_arms = proc_macro2::TokenStream::new();
1034 for (idx, e) in enum_data.variants.iter().enumerate() {
1035 let label = &e.ident;
1036 let idx = idx as i32;
1037 from_match_arms.extend(quote! { #idx => #enum_name::#label, })
1038 }
1039 from_match_arms.extend(quote! { _ => panic!("Unrecognized ordinal ")});
1040
1041 let mut ordinal_match_arms = proc_macro2::TokenStream::new();
1042 for (idx, e) in enum_data.variants.iter().enumerate() {
1043 let label = &e.ident;
1044 let idx = idx as i32;
1045 ordinal_match_arms.extend(quote! { #enum_name::#label => #idx, });
1046 }
1047
1048 let mut build_array_body = proc_macro2::TokenStream::new();
1049 for (idx, e) in enum_data.variants.iter().enumerate() {
1050 let label = e.ident.to_string();
1051 let mut hidden = false;
1052
1053 for att in e.attrs.iter() {
1054 let att = quote! {#att}.to_string();
1055 if att == "# [hidden]" {
1056 hidden = true;
1057 break;
1058 }
1059 }
1060
1061 build_array_body.extend(quote! {
1062 ::pgrx::pgbox::PgBox::<_, ::pgrx::pgbox::AllocatedByPostgres>::with(&mut slice[#idx], |v| {
1063 v.name = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.pstrdup(#label);
1064 v.val = #idx as i32;
1065 v.hidden = #hidden;
1066 });
1067 });
1068 }
1069
1070 stream.extend(quote! {
1071 impl ::pgrx::guc::GucEnum<#enum_name> for #enum_name {
1072 fn from_ordinal(ordinal: i32) -> #enum_name {
1073 match ordinal {
1074 #from_match_arms
1075 }
1076 }
1077
1078 fn to_ordinal(&self) -> i32 {
1079 match *self {
1080 #ordinal_match_arms
1081 }
1082 }
1083
1084 unsafe fn config_matrix(&self) -> *const ::pgrx::pg_sys::config_enum_entry {
1085 let slice = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.palloc0_slice::<::pgrx::pg_sys::config_enum_entry>(#enum_len + 1usize);
1086
1087 #build_array_body
1088
1089 slice.as_ptr()
1090 }
1091 }
1092 });
1093
1094 Ok(stream)
1095}
1096
1097#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
1098enum PostgresTypeAttribute {
1099 InOutFuncs,
1100 PgVarlenaInOutFuncs,
1101 Default,
1102 ManualFromIntoDatum,
1103}
1104
1105fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
1106 let mut categorized_attributes = HashSet::new();
1107
1108 for a in attributes {
1109 let path = &a.path();
1110 let path = quote! {#path}.to_string();
1111 match path.as_str() {
1112 "inoutfuncs" => {
1113 categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
1114 }
1115 "pgvarlena_inoutfuncs" => {
1116 categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
1117 }
1118 "bikeshed_postgres_type_manually_impl_from_into_datum" => {
1119 categorized_attributes.insert(PostgresTypeAttribute::ManualFromIntoDatum);
1120 }
1121 _ => {
1122 // we can just ignore attributes we don't understand
1123 }
1124 };
1125 }
1126
1127 categorized_attributes
1128}
1129
1130/**
1131Generate necessary code using the type in operators like `==` and `!=`.
1132
1133```rust,ignore
1134# use pgrx_pg_sys as pg_sys;
1135use pgrx::*;
1136use serde::{Deserialize, Serialize};
1137#[derive(Debug, Serialize, Deserialize, PostgresEnum, PartialEq, Eq, PostgresEq)]
1138enum DogNames {
1139 Nami,
1140 Brandy,
1141}
1142```
1143Optionally accepts the following attributes:
1144
1145* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1146
1147# No bounds?
1148Unlike some derives, this does not implement a "real" Rust trait, thus
1149PostgresEq cannot be used in trait bounds, nor can it be manually implemented.
1150*/
1151#[proc_macro_derive(PostgresEq, attributes(pgrx))]
1152pub fn derive_postgres_eq(input: TokenStream) -> TokenStream {
1153 let ast = parse_macro_input!(input as syn::DeriveInput);
1154 deriving_postgres_eq(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1155}
1156
1157/**
1158Generate necessary code using the type in operators like `>`, `<`, `<=`, and `>=`.
1159
1160```rust,ignore
1161# use pgrx_pg_sys as pg_sys;
1162use pgrx::*;
1163use serde::{Deserialize, Serialize};
1164#[derive(
1165 Debug, Serialize, Deserialize, PartialEq, Eq,
1166 PartialOrd, Ord, PostgresEnum, PostgresOrd
1167)]
1168enum DogNames {
1169 Nami,
1170 Brandy,
1171}
1172```
1173Optionally accepts the following attributes:
1174
1175* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1176
1177# No bounds?
1178Unlike some derives, this does not implement a "real" Rust trait, thus
1179PostgresOrd cannot be used in trait bounds, nor can it be manually implemented.
1180*/
1181#[proc_macro_derive(PostgresOrd, attributes(pgrx))]
1182pub fn derive_postgres_ord(input: TokenStream) -> TokenStream {
1183 let ast = parse_macro_input!(input as syn::DeriveInput);
1184 deriving_postgres_ord(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1185}
1186
1187/**
1188Generate necessary code for stable hashing the type so it can be used with `USING hash` indexes.
1189
1190```rust,ignore
1191# use pgrx_pg_sys as pg_sys;
1192use pgrx::*;
1193use serde::{Deserialize, Serialize};
1194#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PostgresEnum, PostgresHash)]
1195enum DogNames {
1196 Nami,
1197 Brandy,
1198}
1199```
1200Optionally accepts the following attributes:
1201
1202* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1203
1204# No bounds?
1205Unlike some derives, this does not implement a "real" Rust trait, thus
1206PostgresHash cannot be used in trait bounds, nor can it be manually implemented.
1207*/
1208#[proc_macro_derive(PostgresHash, attributes(pgrx))]
1209pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
1210 let ast = parse_macro_input!(input as syn::DeriveInput);
1211 deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1212}
1213
1214/**
1215Declare a `pgrx::Aggregate` implementation on a type as able to used by Postgres as an aggregate.
1216
1217Functions inside the `impl` may use the [`#[pgrx]`](macro@pgrx) attribute.
1218*/
1219#[proc_macro_attribute]
1220pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1221 // We don't care about `_attr` as we can find it in the `ItemMod`.
1222 fn wrapped(item_impl: ItemImpl) -> Result<TokenStream, syn::Error> {
1223 let sql_graph_entity_item = PgAggregate::new(item_impl)?;
1224
1225 Ok(sql_graph_entity_item.to_token_stream().into())
1226 }
1227
1228 let parsed_base = parse_macro_input!(item as syn::ItemImpl);
1229 wrapped(parsed_base).unwrap_or_else(|e| e.into_compile_error().into())
1230}
1231
1232/**
1233A helper attribute for various contexts.
1234
1235## Usage with [`#[pg_aggregate]`](macro@pg_aggregate).
1236
1237It can be decorated on functions inside a [`#[pg_aggregate]`](macro@pg_aggregate) implementation.
1238In this position, it takes the same args as [`#[pg_extern]`](macro@pg_extern), and those args have the same effect.
1239
1240## Usage for configuring SQL generation
1241
1242This attribute can be used to control the behavior of the SQL generator on a decorated item,
1243e.g. `#[pgrx(sql = false)]`
1244
1245Currently `sql` can be provided one of the following:
1246
1247* Disable SQL generation with `#[pgrx(sql = false)]`
1248* Call custom SQL generator function with `#[pgrx(sql = path::to_function)]`
1249* Render a specific fragment of SQL with a string `#[pgrx(sql = "CREATE FUNCTION ...")]`
1250
1251*/
1252#[proc_macro_attribute]
1253pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1254 item
1255}
1256
1257/**
1258Create a [PostgreSQL trigger function](https://www.postgresql.org/docs/current/plpgsql-trigger.html)
1259
1260Review the `pgrx::trigger_support::PgTrigger` documentation for use.
1261
1262 */
1263#[proc_macro_attribute]
1264pub fn pg_trigger(attrs: TokenStream, input: TokenStream) -> TokenStream {
1265 fn wrapped(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
1266 use pgrx_sql_entity_graph::{PgTrigger, PgTriggerAttribute};
1267 use syn::parse::Parser;
1268 use syn::punctuated::Punctuated;
1269 use syn::Token;
1270
1271 let attributes =
1272 Punctuated::<PgTriggerAttribute, Token![,]>::parse_terminated.parse(attrs)?;
1273 let item_fn: syn::ItemFn = syn::parse(input)?;
1274 let trigger_item = PgTrigger::new(item_fn, attributes)?;
1275 let trigger_tokens = trigger_item.to_token_stream();
1276
1277 Ok(trigger_tokens.into())
1278 }
1279
1280 wrapped(attrs, input).unwrap_or_else(|e| e.into_compile_error().into())
1281}