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* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
785*/
786#[proc_macro_derive(
787 PostgresType,
788 attributes(
789 inoutfuncs,
790 pgvarlena_inoutfuncs,
791 bikeshed_postgres_type_manually_impl_from_into_datum,
792 requires,
793 pgrx
794 )
795)]
796pub fn postgres_type(input: TokenStream) -> TokenStream {
797 let ast = parse_macro_input!(input as syn::DeriveInput);
798
799 impl_postgres_type(ast).unwrap_or_else(|e| e.into_compile_error()).into()
800}
801
802fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
803 let name = &ast.ident;
804 let generics = &ast.generics.clone();
805 let has_lifetimes = generics.lifetimes().next();
806 let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
807 let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
808 let mut args = parse_postgres_type_args(&ast.attrs);
809 let mut stream = proc_macro2::TokenStream::new();
810
811 // validate that we're only operating on a struct
812 match ast.data {
813 Data::Struct(_) => { /* this is okay */ }
814 Data::Enum(_) => {
815 // this is okay and if there's an attempt to implement PostgresEnum,
816 // it will result in compile-time error of conflicting implementation
817 // of traits (IntoDatum, inout, etc.)
818 }
819 _ => {
820 return Err(syn::Error::new(
821 ast.span(),
822 "#[derive(PostgresType)] can only be applied to structs or enums",
823 ))
824 }
825 }
826
827 if args.is_empty() {
828 // assume the user wants us to implement the InOutFuncs
829 args.insert(PostgresTypeAttribute::Default);
830 }
831
832 let lifetime = match has_lifetimes {
833 Some(lifetime) => quote! {#lifetime},
834 None => quote! {'_},
835 };
836
837 // We need another variant of the params for the ArgAbi impl
838 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
839 let mut generics_with_fcx = generics.clone();
840 // so that we can bound on Self: 'fcx
841 generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
842 syn::PredicateType {
843 lifetimes: None,
844 bounded_ty: syn::parse_quote! { Self },
845 colon_token: syn::Token),
846 bounds: syn::parse_quote! { #fcx_lt },
847 },
848 ));
849 let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
850 let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
851 impl_gens
852 .params
853 .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
854
855 // all #[derive(PostgresType)] need to implement that trait
856 // and also the FromDatum and IntoDatum
857 stream.extend(quote! {
858 impl #generics ::pgrx::datum::PostgresType for #name #generics { }
859 });
860
861 if !args.contains(&PostgresTypeAttribute::ManualFromIntoDatum) {
862 stream.extend(
863 quote! {
864 impl #generics ::pgrx::datum::IntoDatum for #name #generics {
865 fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
866 #[allow(deprecated)]
867 Some(unsafe { ::pgrx::datum::cbor_encode(&self) }.into())
868 }
869
870 fn type_oid() -> ::pgrx::pg_sys::Oid {
871 ::pgrx::wrappers::rust_regtypein::<Self>()
872 }
873 }
874
875 unsafe impl #generics ::pgrx::callconv::BoxRet for #name #generics {
876 unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
877 match ::pgrx::datum::IntoDatum::into_datum(self) {
878 None => fcinfo.return_null(),
879 Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
880 }
881 }
882 }
883
884 impl #generics ::pgrx::datum::FromDatum for #name #generics {
885 unsafe fn from_polymorphic_datum(
886 datum: ::pgrx::pg_sys::Datum,
887 is_null: bool,
888 _typoid: ::pgrx::pg_sys::Oid,
889 ) -> Option<Self> {
890 if is_null {
891 None
892 } else {
893 #[allow(deprecated)]
894 ::pgrx::datum::cbor_decode(datum.cast_mut_ptr())
895 }
896 }
897
898 unsafe fn from_datum_in_memory_context(
899 mut memory_context: ::pgrx::memcxt::PgMemoryContexts,
900 datum: ::pgrx::pg_sys::Datum,
901 is_null: bool,
902 _typoid: ::pgrx::pg_sys::Oid,
903 ) -> Option<Self> {
904 if is_null {
905 None
906 } else {
907 memory_context.switch_to(|_| {
908 // this gets the varlena Datum copied into this memory context
909 let varlena = ::pgrx::pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
910 Self::from_datum(varlena.into(), is_null)
911 })
912 }
913 }
914 }
915
916 unsafe impl #generics ::pgrx::datum::UnboxDatum for #name #generics {
917 type As<'dat> = Self where Self: 'dat;
918 unsafe fn unbox<'dat>(datum: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
919 <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(datum), false).unwrap()
920 }
921 }
922
923 unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #name #ty_gens #where_clause
924 {
925 unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
926 let index = arg.index();
927 unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
928 }
929 }
930 }
931 )
932 }
933
934 // and if we don't have custom inout/funcs, we use the JsonInOutFuncs trait
935 // which implements _in and _out #[pg_extern] functions that just return the type itself
936 if args.contains(&PostgresTypeAttribute::Default) {
937 stream.extend(quote! {
938 #[doc(hidden)]
939 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
940 pub fn #funcname_in #generics(input: Option<&#lifetime ::core::ffi::CStr>) -> Option<#name #generics> {
941 use ::pgrx::inoutfuncs::json_from_slice;
942 input.map(|cstr| json_from_slice(cstr.to_bytes()).ok()).flatten()
943 }
944
945 #[doc(hidden)]
946 #[::pgrx::pgrx_macros::pg_extern (immutable,parallel_safe)]
947 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
948 use ::pgrx::inoutfuncs::json_to_vec;
949 let mut bytes = json_to_vec(&input).unwrap();
950 bytes.push(0); // terminate
951 ::pgrx::ffi::CString::from_vec_with_nul(bytes).unwrap()
952 }
953 });
954 } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
955 // otherwise if it's InOutFuncs our _in/_out functions use an owned type instance
956 stream.extend(quote! {
957 #[doc(hidden)]
958 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
959 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<#name #generics> {
960 input.map_or_else(|| {
961 for m in <#name as ::pgrx::inoutfuncs::InOutFuncs>::NULL_ERROR_MESSAGE {
962 ::pgrx::pg_sys::error!("{m}");
963 }
964 None
965 }, |i| Some(<#name as ::pgrx::inoutfuncs::InOutFuncs>::input(i)))
966 }
967
968 #[doc(hidden)]
969 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
970 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
971 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
972 ::pgrx::inoutfuncs::InOutFuncs::output(&input, &mut buffer);
973 // SAFETY: We just constructed this StringInfo ourselves
974 unsafe { buffer.leak_cstr().to_owned() }
975 }
976 });
977 } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
978 // otherwise if it's PgVarlenaInOutFuncs our _in/_out functions use a PgVarlena
979 stream.extend(quote! {
980 #[doc(hidden)]
981 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
982 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<::pgrx::datum::PgVarlena<#name #generics>> {
983 input.map_or_else(|| {
984 for m in <#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::NULL_ERROR_MESSAGE {
985 ::pgrx::pg_sys::error!("{m}");
986 }
987 None
988 }, |i| Some(<#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::input(i)))
989 }
990
991 #[doc(hidden)]
992 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
993 pub fn #funcname_out #generics(input: ::pgrx::datum::PgVarlena<#name #generics>) -> ::pgrx::ffi::CString {
994 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
995 ::pgrx::inoutfuncs::PgVarlenaInOutFuncs::output(&*input, &mut buffer);
996 // SAFETY: We just constructed this StringInfo ourselves
997 unsafe { buffer.leak_cstr().to_owned() }
998 }
999 });
1000 }
1001
1002 let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(ast)?;
1003 sql_graph_entity_item.to_tokens(&mut stream);
1004
1005 Ok(stream)
1006}
1007
1008/// Derives the `GucEnum` trait, so that normal Rust enums can be used as a GUC.
1009#[proc_macro_derive(PostgresGucEnum, attributes(hidden))]
1010pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
1011 let ast = parse_macro_input!(input as syn::DeriveInput);
1012
1013 impl_guc_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1014}
1015
1016fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1017 let mut stream = proc_macro2::TokenStream::new();
1018
1019 // validate that we're only operating on an enum
1020 let enum_data = match ast.data {
1021 Data::Enum(e) => e,
1022 _ => {
1023 return Err(syn::Error::new(
1024 ast.span(),
1025 "#[derive(PostgresGucEnum)] can only be applied to enums",
1026 ))
1027 }
1028 };
1029 let enum_name = ast.ident;
1030 let enum_len = enum_data.variants.len();
1031
1032 let mut from_match_arms = proc_macro2::TokenStream::new();
1033 for (idx, e) in enum_data.variants.iter().enumerate() {
1034 let label = &e.ident;
1035 let idx = idx as i32;
1036 from_match_arms.extend(quote! { #idx => #enum_name::#label, })
1037 }
1038 from_match_arms.extend(quote! { _ => panic!("Unrecognized ordinal ")});
1039
1040 let mut ordinal_match_arms = proc_macro2::TokenStream::new();
1041 for (idx, e) in enum_data.variants.iter().enumerate() {
1042 let label = &e.ident;
1043 let idx = idx as i32;
1044 ordinal_match_arms.extend(quote! { #enum_name::#label => #idx, });
1045 }
1046
1047 let mut build_array_body = proc_macro2::TokenStream::new();
1048 for (idx, e) in enum_data.variants.iter().enumerate() {
1049 let label = e.ident.to_string();
1050 let mut hidden = false;
1051
1052 for att in e.attrs.iter() {
1053 let att = quote! {#att}.to_string();
1054 if att == "# [hidden]" {
1055 hidden = true;
1056 break;
1057 }
1058 }
1059
1060 build_array_body.extend(quote! {
1061 ::pgrx::pgbox::PgBox::<_, ::pgrx::pgbox::AllocatedByPostgres>::with(&mut slice[#idx], |v| {
1062 v.name = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.pstrdup(#label);
1063 v.val = #idx as i32;
1064 v.hidden = #hidden;
1065 });
1066 });
1067 }
1068
1069 stream.extend(quote! {
1070 impl ::pgrx::guc::GucEnum<#enum_name> for #enum_name {
1071 fn from_ordinal(ordinal: i32) -> #enum_name {
1072 match ordinal {
1073 #from_match_arms
1074 }
1075 }
1076
1077 fn to_ordinal(&self) -> i32 {
1078 match *self {
1079 #ordinal_match_arms
1080 }
1081 }
1082
1083 unsafe fn config_matrix(&self) -> *const ::pgrx::pg_sys::config_enum_entry {
1084 let slice = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.palloc0_slice::<::pgrx::pg_sys::config_enum_entry>(#enum_len + 1usize);
1085
1086 #build_array_body
1087
1088 slice.as_ptr()
1089 }
1090 }
1091 });
1092
1093 Ok(stream)
1094}
1095
1096#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
1097enum PostgresTypeAttribute {
1098 InOutFuncs,
1099 PgVarlenaInOutFuncs,
1100 Default,
1101 ManualFromIntoDatum,
1102}
1103
1104fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
1105 let mut categorized_attributes = HashSet::new();
1106
1107 for a in attributes {
1108 let path = &a.path();
1109 let path = quote! {#path}.to_string();
1110 match path.as_str() {
1111 "inoutfuncs" => {
1112 categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
1113 }
1114 "pgvarlena_inoutfuncs" => {
1115 categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
1116 }
1117 "bikeshed_postgres_type_manually_impl_from_into_datum" => {
1118 categorized_attributes.insert(PostgresTypeAttribute::ManualFromIntoDatum);
1119 }
1120 _ => {
1121 // we can just ignore attributes we don't understand
1122 }
1123 };
1124 }
1125
1126 categorized_attributes
1127}
1128
1129/**
1130Generate necessary code using the type in operators like `==` and `!=`.
1131
1132```rust,ignore
1133# use pgrx_pg_sys as pg_sys;
1134use pgrx::*;
1135use serde::{Deserialize, Serialize};
1136#[derive(Debug, Serialize, Deserialize, PostgresEnum, PartialEq, Eq, PostgresEq)]
1137enum DogNames {
1138 Nami,
1139 Brandy,
1140}
1141```
1142Optionally accepts the following attributes:
1143
1144* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1145
1146# No bounds?
1147Unlike some derives, this does not implement a "real" Rust trait, thus
1148PostgresEq cannot be used in trait bounds, nor can it be manually implemented.
1149*/
1150#[proc_macro_derive(PostgresEq, attributes(pgrx))]
1151pub fn derive_postgres_eq(input: TokenStream) -> TokenStream {
1152 let ast = parse_macro_input!(input as syn::DeriveInput);
1153 deriving_postgres_eq(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1154}
1155
1156/**
1157Generate necessary code using the type in operators like `>`, `<`, `<=`, and `>=`.
1158
1159```rust,ignore
1160# use pgrx_pg_sys as pg_sys;
1161use pgrx::*;
1162use serde::{Deserialize, Serialize};
1163#[derive(
1164 Debug, Serialize, Deserialize, PartialEq, Eq,
1165 PartialOrd, Ord, PostgresEnum, PostgresOrd
1166)]
1167enum DogNames {
1168 Nami,
1169 Brandy,
1170}
1171```
1172Optionally accepts the following attributes:
1173
1174* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1175
1176# No bounds?
1177Unlike some derives, this does not implement a "real" Rust trait, thus
1178PostgresOrd cannot be used in trait bounds, nor can it be manually implemented.
1179*/
1180#[proc_macro_derive(PostgresOrd, attributes(pgrx))]
1181pub fn derive_postgres_ord(input: TokenStream) -> TokenStream {
1182 let ast = parse_macro_input!(input as syn::DeriveInput);
1183 deriving_postgres_ord(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1184}
1185
1186/**
1187Generate necessary code for stable hashing the type so it can be used with `USING hash` indexes.
1188
1189```rust,ignore
1190# use pgrx_pg_sys as pg_sys;
1191use pgrx::*;
1192use serde::{Deserialize, Serialize};
1193#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PostgresEnum, PostgresHash)]
1194enum DogNames {
1195 Nami,
1196 Brandy,
1197}
1198```
1199Optionally accepts the following attributes:
1200
1201* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1202
1203# No bounds?
1204Unlike some derives, this does not implement a "real" Rust trait, thus
1205PostgresHash cannot be used in trait bounds, nor can it be manually implemented.
1206*/
1207#[proc_macro_derive(PostgresHash, attributes(pgrx))]
1208pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
1209 let ast = parse_macro_input!(input as syn::DeriveInput);
1210 deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1211}
1212
1213/**
1214Declare a `pgrx::Aggregate` implementation on a type as able to used by Postgres as an aggregate.
1215
1216Functions inside the `impl` may use the [`#[pgrx]`](macro@pgrx) attribute.
1217*/
1218#[proc_macro_attribute]
1219pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1220 // We don't care about `_attr` as we can find it in the `ItemMod`.
1221 fn wrapped(item_impl: ItemImpl) -> Result<TokenStream, syn::Error> {
1222 let sql_graph_entity_item = PgAggregate::new(item_impl)?;
1223
1224 Ok(sql_graph_entity_item.to_token_stream().into())
1225 }
1226
1227 let parsed_base = parse_macro_input!(item as syn::ItemImpl);
1228 wrapped(parsed_base).unwrap_or_else(|e| e.into_compile_error().into())
1229}
1230
1231/**
1232A helper attribute for various contexts.
1233
1234## Usage with [`#[pg_aggregate]`](macro@pg_aggregate).
1235
1236It can be decorated on functions inside a [`#[pg_aggregate]`](macro@pg_aggregate) implementation.
1237In this position, it takes the same args as [`#[pg_extern]`](macro@pg_extern), and those args have the same effect.
1238
1239## Usage for configuring SQL generation
1240
1241This attribute can be used to control the behavior of the SQL generator on a decorated item,
1242e.g. `#[pgrx(sql = false)]`
1243
1244Currently `sql` can be provided one of the following:
1245
1246* Disable SQL generation with `#[pgrx(sql = false)]`
1247* Call custom SQL generator function with `#[pgrx(sql = path::to_function)]`
1248* Render a specific fragment of SQL with a string `#[pgrx(sql = "CREATE FUNCTION ...")]`
1249
1250*/
1251#[proc_macro_attribute]
1252pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1253 item
1254}
1255
1256/**
1257Create a [PostgreSQL trigger function](https://www.postgresql.org/docs/current/plpgsql-trigger.html)
1258
1259Review the `pgrx::trigger_support::PgTrigger` documentation for use.
1260
1261 */
1262#[proc_macro_attribute]
1263pub fn pg_trigger(attrs: TokenStream, input: TokenStream) -> TokenStream {
1264 fn wrapped(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
1265 use pgrx_sql_entity_graph::{PgTrigger, PgTriggerAttribute};
1266 use syn::parse::Parser;
1267 use syn::punctuated::Punctuated;
1268 use syn::Token;
1269
1270 let attributes =
1271 Punctuated::<PgTriggerAttribute, Token![,]>::parse_terminated.parse(attrs)?;
1272 let item_fn: syn::ItemFn = syn::parse(input)?;
1273 let trigger_item = PgTrigger::new(item_fn, attributes)?;
1274 let trigger_tokens = trigger_item.to_token_stream();
1275
1276 Ok(trigger_tokens.into())
1277 }
1278
1279 wrapped(attrs, input).unwrap_or_else(|e| e.into_compile_error().into())
1280}