Skip to main content

annotate_build/
config.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::io;
4use std::rc::Rc;
5
6use crate::visitor;
7
8#[derive(Debug, PartialEq, serde::Deserialize)]
9struct ConfigSpec {
10    schema_version: u32,
11    #[serde(default)]
12    functions: Vec<FunctionSpec>,
13    #[serde(default)]
14    modules: Vec<ModuleSpec>,
15}
16
17#[derive(Debug, PartialEq, serde::Deserialize)]
18struct FunctionSpec {
19    pragma: String,
20}
21
22#[derive(Debug, PartialEq, serde::Deserialize)]
23struct ModuleSpec {
24    pragma: String,
25    #[serde(default)]
26    derives: Vec<ModuleDeriveSpec>,
27}
28
29#[derive(Debug, PartialEq, serde::Deserialize)]
30pub(crate) struct ModuleDeriveSpec {
31    pub(crate) name: Option<String>,
32    #[serde(default)]
33    pub(crate) modules: Vec<ModuleDeriveSpec>,
34    #[serde(default)]
35    pub(crate) functions: Vec<String>,
36}
37
38impl TryFrom<&str> for ConfigSpec {
39    type Error = serde_json::Error;
40
41    fn try_from(spec: &str) -> Result<Self, Self::Error> {
42        let spec: Self = serde_json::from_str(spec)?;
43        if spec.schema_version != crate::SCHEMA_VERSION {
44            return Err(serde_json::Error::io(io::Error::new(
45                io::ErrorKind::InvalidData,
46                format!(
47                    "schema_version {} does not match supported version {}",
48                    spec.schema_version,
49                    crate::SCHEMA_VERSION
50                ),
51            )));
52        }
53
54        Ok(spec)
55    }
56}
57
58pub(crate) fn build_config_from_json_spec(spec: &str) -> Result<BuildConfig, serde_json::Error> {
59    let spec = ConfigSpec::try_from(spec)?;
60    Ok(BuildConfig::from_spec(&spec))
61}
62
63#[derive(Default, Debug)]
64pub struct BuildConfig {
65    pub(crate) pragmas: Vec<String>,
66    pub(crate) derives: Vec<visitor::CustomDerive>,
67    pub(crate) module_derives: HashMap<String, Vec<visitor::CustomModuleDerive>>,
68}
69
70impl BuildConfig {
71    pub(crate) fn merge(&mut self, other: Self) {
72        for pragma in other.pragmas {
73            push_unique_pragma(&mut self.pragmas, pragma.as_str());
74        }
75
76        self.derives.extend(other.derives);
77
78        for (pragma, derives) in other.module_derives {
79            self.module_derives
80                .entry(pragma)
81                .or_default()
82                .extend(derives);
83        }
84    }
85
86    /// Register an additional attribute name that should be treated like `#[pragma(...)]`.
87    pub fn pragma(&mut self, pragma: impl Into<String>) -> &mut Self {
88        self.pragmas.push(pragma.into());
89        self
90    }
91
92    /// Register a custom derive name and its mustache template expansion.
93    pub fn derive(&mut self, name: impl Into<String>, template: impl AsRef<str>) -> &mut Self {
94        self.derives
95            .push(visitor::CustomDerive::new(name, template.as_ref()));
96        self
97    }
98
99    pub fn module_derive(
100        &mut self,
101        extension: impl Into<String>,
102        configure: impl FnOnce(&mut ModuleDeriveBuilder),
103    ) -> &mut Self {
104        let extension = extension.into();
105        let existing_derives = self
106            .module_derives
107            .get(&extension)
108            .cloned()
109            .unwrap_or_default();
110
111        let mut builder = ModuleDeriveBuilder::new(existing_derives);
112        configure(&mut builder);
113
114        self.module_derives
115            .insert(extension, builder.derives.borrow().clone());
116
117        self
118    }
119
120    fn from_spec(spec: &ConfigSpec) -> Self {
121        let mut config = BuildConfig::default();
122
123        for function in &spec.functions {
124            push_unique_pragma(&mut config.pragmas, &function.pragma);
125        }
126
127        for module in &spec.modules {
128            push_unique_pragma(&mut config.pragmas, &module.pragma);
129            config.module_derives.insert(
130                module.pragma.clone(),
131                module
132                    .derives
133                    .iter()
134                    .map(custom_module_derive_from_spec)
135                    .collect(),
136            );
137        }
138
139        config
140    }
141}
142
143#[derive(Default, Clone)]
144pub struct ModuleDeriveBuilder {
145    derives: Rc<RefCell<Vec<visitor::CustomModuleDerive>>>,
146    current_derive_path: Vec<usize>,
147}
148
149impl ModuleDeriveBuilder {
150    fn new(existing_derives: Vec<visitor::CustomModuleDerive>) -> Self {
151        Self {
152            derives: Rc::new(RefCell::new(existing_derives)),
153            current_derive_path: vec![],
154        }
155    }
156
157    pub fn module(&mut self, module: impl Into<String>) -> Self {
158        let mut clone = self.clone();
159
160        if clone.current_derive_path.is_empty() {
161            let index = clone.derives.borrow().len();
162            clone
163                .derives
164                .borrow_mut()
165                .push(visitor::CustomModuleDerive::new(module.into()));
166            clone.current_derive_path.push(index);
167        } else {
168            let mut index = 0;
169            clone.with_derive_at_path(|derive| {
170                index = derive.add_module(module.into());
171            });
172            clone.current_derive_path.push(index);
173        }
174
175        clone
176    }
177
178    pub fn functions(&mut self, functions: impl IntoIterator<Item = impl Into<String>>) -> Self {
179        let clone = self.clone();
180
181        clone.with_derive_at_path(|derive| {
182            for each in functions {
183                derive.add_function(each.into());
184            }
185        });
186
187        clone
188    }
189
190    fn with_derive_at_path(&self, f: impl FnOnce(&mut visitor::CustomModuleDerive)) {
191        if self.derives.borrow().is_empty() {
192            self.derives
193                .borrow_mut()
194                .push(visitor::CustomModuleDerive::default());
195        }
196
197        let mut borrow = self.derives.borrow_mut();
198        if self.current_derive_path.is_empty() {
199            let last_derive = borrow
200                .last_mut()
201                .unwrap()
202                .derive_at_mut(&self.current_derive_path);
203
204            f(last_derive)
205        } else {
206            let index = *self.current_derive_path.first().unwrap();
207            let first_derive = borrow.get_mut(index).unwrap();
208            f(first_derive.derive_at_mut(&self.current_derive_path[1..]));
209        }
210    }
211}
212
213fn push_unique_pragma(pragmas: &mut Vec<String>, pragma: &str) {
214    if pragmas.iter().all(|existing| existing != pragma) {
215        pragmas.push(pragma.to_string());
216    }
217}
218
219fn custom_module_derive_from_spec(spec: &ModuleDeriveSpec) -> visitor::CustomModuleDerive {
220    visitor::CustomModuleDerive::from_spec(spec)
221}
222
223#[macro_export]
224macro_rules! custom {
225    ($($tokens:tt)*) => {
226        $crate::__custom_config!(@parse [] [] $($tokens)*)
227    };
228}
229
230#[doc(hidden)]
231#[macro_export]
232macro_rules! __custom_config {
233    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
234        concat!(
235            "{",
236            "\"schema_version\":",
237            $crate::__custom_schema_version!(),
238            ",",
239            "\"functions\":[",
240            $crate::__custom_join!($($functions),*),
241            "],",
242            "\"modules\":[",
243            $crate::__custom_join!($($modules),*),
244            "]",
245            "}"
246        )
247    };
248    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn pragma $pragma:literal, $($rest:tt)*) => {
249        $crate::__custom_config!(
250            @parse
251            [
252                $($functions,)*
253                concat!("{", "\"pragma\":", $crate::__custom_json_string_literal!($pragma), "}"),
254            ]
255            [ $($modules,)* ]
256            $($rest)*
257        )
258    };
259    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn pragma $pragma:literal) => {
260        $crate::__custom_config!(
261            @parse
262            [
263                $($functions,)*
264                concat!("{", "\"pragma\":", $crate::__custom_json_string_literal!($pragma), "}"),
265            ]
266            [ $($modules,)* ]
267        )
268    };
269    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod pragma $pragma:literal { $($body:tt)* }, $($rest:tt)*) => {
270        $crate::__custom_config!(
271            @parse
272            [ $($functions,)* ]
273            [
274                $($modules,)*
275                concat!(
276                    "{",
277                    "\"pragma\":",
278                    $crate::__custom_json_string_literal!($pragma),
279                    ",",
280                    "\"derives\":[",
281                    $crate::__custom_module_derives!(@parse [] $($body)*),
282                    "]",
283                    "}"
284                ),
285            ]
286            $($rest)*
287        )
288    };
289    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod pragma $pragma:literal { $($body:tt)* }) => {
290        $crate::__custom_config!(
291            @parse
292            [ $($functions,)* ]
293            [
294                $($modules,)*
295                concat!(
296                    "{",
297                    "\"pragma\":",
298                    $crate::__custom_json_string_literal!($pragma),
299                    ",",
300                    "\"derives\":[",
301                    $crate::__custom_module_derives!(@parse [] $($body)*),
302                    "]",
303                    "}"
304                ),
305            ]
306        )
307    };
308}
309
310#[doc(hidden)]
311#[macro_export]
312macro_rules! __custom_join {
313    () => {
314        ""
315    };
316    ($value:expr) => {
317        $value
318    };
319    ($first:expr, $($rest:expr),+ $(,)?) => {
320        concat!($first, ",", $crate::__custom_join!($($rest),+))
321    };
322}
323
324#[doc(hidden)]
325#[macro_export]
326macro_rules! __custom_json_string_literal {
327    ($value:literal) => {
328        concat!("\"", $value, "\"")
329    };
330}
331
332#[doc(hidden)]
333#[macro_export]
334macro_rules! __custom_schema_version {
335    () => {
336        "1"
337    };
338}
339
340#[doc(hidden)]
341#[macro_export]
342macro_rules! __custom_module_derives {
343    (@parse [ $($derives:expr,)* ]) => {
344        $crate::__custom_join!($($derives),*)
345    };
346    (@parse [ $($derives:expr,)* ] derive { mod $name:ident { $($body:tt)* } }, $($rest:tt)*) => {
347        $crate::__custom_module_derives!(
348            @parse
349            [
350                $($derives,)*
351                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
352            ]
353            $($rest)*
354        )
355    };
356    (@parse [ $($derives:expr,)* ] derive { mod $name:ident { $($body:tt)* } }) => {
357        $crate::__custom_module_derives!(
358            @parse
359            [
360                $($derives,)*
361                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
362            ]
363        )
364    };
365    (@parse [ $($derives:expr,)* ] derive { $($body:tt)* }, $($rest:tt)*) => {
366        $crate::__custom_module_derives!(
367            @parse
368            [
369                $($derives,)*
370                $crate::__custom_module_derive_spec!(@unnamed [] [] $($body)*),
371            ]
372            $($rest)*
373        )
374    };
375    (@parse [ $($derives:expr,)* ] derive { $($body:tt)* }) => {
376        $crate::__custom_module_derives!(
377            @parse
378            [
379                $($derives,)*
380                $crate::__custom_module_derive_spec!(@unnamed [] [] $($body)*),
381            ]
382        )
383    };
384    (@parse [ $($derives:expr,)* ] derive $name:literal { $($body:tt)* }, $($rest:tt)*) => {
385        $crate::__custom_module_derives!(
386            @parse
387            [
388                $($derives,)*
389                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
390            ]
391            $($rest)*
392        )
393    };
394    (@parse [ $($derives:expr,)* ] derive $name:literal { $($body:tt)* }) => {
395        $crate::__custom_module_derives!(
396            @parse
397            [
398                $($derives,)*
399                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
400            ]
401        )
402    };
403}
404
405#[doc(hidden)]
406#[macro_export]
407macro_rules! __custom_module_derive_spec {
408    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
409        concat!(
410            "{",
411            "\"functions\":[",
412            $crate::__custom_join!($($functions),*),
413            "],",
414            "\"modules\":[",
415            $crate::__custom_join!($($modules),*),
416            "]",
417            "}"
418        )
419    };
420    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
421        concat!(
422            "{",
423            "\"name\":\"",
424            stringify!($name),
425            "\",",
426            "\"functions\":[",
427            $crate::__custom_join!($($functions),*),
428            "],",
429            "\"modules\":[",
430            $crate::__custom_join!($($modules),*),
431            "]",
432            "}"
433        )
434    };
435    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
436        concat!(
437            "{",
438            "\"name\":",
439            $crate::__custom_json_string_literal!($name),
440            ",",
441            "\"functions\":[",
442            $crate::__custom_join!($($functions),*),
443            "],",
444            "\"modules\":[",
445            $crate::__custom_join!($($modules),*),
446            "]",
447            "}"
448        )
449    };
450    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident, $($rest:tt)*) => {
451        $crate::__custom_module_derive_spec!(
452            @unnamed
453            [ $($functions,)* concat!("\"", stringify!($function), "\""), ]
454            [ $($modules,)* ]
455            $($rest)*
456        )
457    };
458    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident) => {
459        $crate::__custom_module_derive_spec!(
460            @unnamed
461            [ $($functions,)* concat!("\"", stringify!($function), "\""), ]
462            [ $($modules,)* ]
463        )
464    };
465    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident, $($rest:tt)*) => {
466        $crate::__custom_module_derive_spec!(
467            @named
468            $name
469            [ $($functions,)* concat!("\"", stringify!($function), "\""), ]
470            [ $($modules,)* ]
471            $($rest)*
472        )
473    };
474    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident) => {
475        $crate::__custom_module_derive_spec!(
476            @named
477            $name
478            [ $($functions,)* concat!("\"", stringify!($function), "\""), ]
479            [ $($modules,)* ]
480        )
481    };
482    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident, $($rest:tt)*) => {
483        $crate::__custom_module_derive_spec!(
484            @named_ident
485            $name
486            [ $($functions,)* concat!("\"", stringify!($function), "\""), ]
487            [ $($modules,)* ]
488            $($rest)*
489        )
490    };
491    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident) => {
492        $crate::__custom_module_derive_spec!(
493            @named_ident
494            $name
495            [ $($functions,)* concat!("\"", stringify!($function), "\""), ]
496            [ $($modules,)* ]
497        )
498    };
499    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:literal { $($body:tt)* }, $($rest:tt)*) => {
500        $crate::__custom_module_derive_spec!(
501            @unnamed
502            [ $($functions,)* ]
503            [
504                $($modules,)*
505                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
506            ]
507            $($rest)*
508        )
509    };
510    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:literal { $($body:tt)* }) => {
511        $crate::__custom_module_derive_spec!(
512            @unnamed
513            [ $($functions,)* ]
514            [
515                $($modules,)*
516                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
517            ]
518        )
519    };
520    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:ident { $($body:tt)* }, $($rest:tt)*) => {
521        $crate::__custom_module_derive_spec!(
522            @unnamed
523            [ $($functions,)* ]
524            [
525                $($modules,)*
526                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
527            ]
528            $($rest)*
529        )
530    };
531    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:ident { $($body:tt)* }) => {
532        $crate::__custom_module_derive_spec!(
533            @unnamed
534            [ $($functions,)* ]
535            [
536                $($modules,)*
537                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
538            ]
539        )
540    };
541    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }, $($rest:tt)*) => {
542        $crate::__custom_module_derive_spec!(
543            @named
544            $name
545            [ $($functions,)* ]
546            [
547                $($modules,)*
548                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
549            ]
550            $($rest)*
551        )
552    };
553    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }) => {
554        $crate::__custom_module_derive_spec!(
555            @named
556            $name
557            [ $($functions,)* ]
558            [
559                $($modules,)*
560                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
561            ]
562        )
563    };
564    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }, $($rest:tt)*) => {
565        $crate::__custom_module_derive_spec!(
566            @named_ident
567            $name
568            [ $($functions,)* ]
569            [
570                $($modules,)*
571                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
572            ]
573            $($rest)*
574        )
575    };
576    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }) => {
577        $crate::__custom_module_derive_spec!(
578            @named_ident
579            $name
580            [ $($functions,)* ]
581            [
582                $($modules,)*
583                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
584            ]
585        )
586    };
587}
588
589#[cfg(test)]
590mod tests {
591    use super::{ConfigSpec, FunctionSpec, ModuleDeriveSpec, ModuleSpec};
592    use crate::BuildConfig;
593
594    fn config_spec() -> ConfigSpec {
595        ConfigSpec {
596            schema_version: crate::SCHEMA_VERSION,
597            functions: vec![FunctionSpec {
598                pragma: "command".to_string(),
599            }],
600            modules: vec![ModuleSpec {
601                pragma: "tooling".to_string(),
602                derives: vec![ModuleDeriveSpec {
603                    name: None,
604                    functions: vec!["bootstrap".to_string(), "init".to_string()],
605                    modules: vec![ModuleDeriveSpec {
606                        name: Some("nested".to_string()),
607                        functions: vec!["run".to_string()],
608                        modules: vec![],
609                    }],
610                }],
611            }],
612        }
613    }
614
615    const CONFIG_SPEC_USING_MACROS: &str = custom! {
616        fn pragma "command",
617        mod pragma "tooling" {
618            derive {
619                fn bootstrap,
620                fn init,
621                mod nested {
622                    fn run,
623                }
624            }
625        }
626    };
627
628    const PHLOW_SPEC_MACROS: &str = custom! {
629        fn pragma "view",
630        mod pragma "extensions" {
631            derive {
632                mod __utilities {
633                    fn phlow_to_string,
634                    fn phlow_type_name,
635                    fn phlow_create_view,
636                    fn phlow_defining_methods,
637                }
638            }
639        }
640    };
641
642    fn phlow_spec() -> ConfigSpec {
643        ConfigSpec {
644            schema_version: crate::SCHEMA_VERSION,
645            functions: vec![FunctionSpec {
646                pragma: "view".to_string(),
647            }],
648            modules: vec![ModuleSpec {
649                pragma: "extensions".to_string(),
650                derives: vec![ModuleDeriveSpec {
651                    name: Some("__utilities".to_string()),
652                    functions: vec![
653                        "phlow_to_string".to_string(),
654                        "phlow_type_name".to_string(),
655                        "phlow_create_view".to_string(),
656                        "phlow_defining_methods".to_string(),
657                    ],
658                    modules: vec![],
659                }],
660            }],
661        }
662    }
663
664    const PHLOW_SPEC_JSON: &str = r#"
665        {
666            "schema_version": 1,
667            "functions": [ { "pragma": "view" } ],
668            "modules": [ {
669                "pragma": "extensions",
670                "derives": [
671                    {
672                        "name": "__utilities",
673                        "functions": [
674                            "phlow_to_string",
675                            "phlow_type_name",
676                            "phlow_create_view",
677                            "phlow_defining_methods"
678                        ]
679                    }
680                ] } ]
681        }
682    "#;
683
684    #[test]
685    fn build_config_from_spec_collects_pragmas_and_module_derives() {
686        let spec = config_spec();
687        let config = BuildConfig::from_spec(&spec);
688
689        assert_eq!(config.pragmas, vec!["command", "tooling"]);
690        assert!(config.derives.is_empty());
691
692        let tooling_derives = config.module_derives.get("tooling").unwrap();
693        assert_eq!(tooling_derives.len(), 1);
694
695        let root_derive = &tooling_derives[0];
696        assert_eq!(root_derive.function_names(), ["bootstrap", "init"]);
697        assert_eq!(root_derive.modules().len(), 1);
698
699        let nested = &root_derive.modules()[0];
700        assert_eq!(nested.name(), Some("nested"));
701        assert_eq!(nested.function_names(), ["run"]);
702    }
703
704    #[test]
705    fn custom_macro_builds_the_same_config_spec() {
706        let spec = ConfigSpec::try_from(CONFIG_SPEC_USING_MACROS).unwrap();
707        assert_eq!(config_spec(), spec);
708    }
709
710    #[test]
711    fn phlow_macro_spec_matches_manual_spec() {
712        let spec = ConfigSpec::try_from(PHLOW_SPEC_MACROS).unwrap();
713        assert_eq!(phlow_spec(), spec);
714    }
715
716    #[test]
717    fn phlow_json_string_deserializes_as_manual_spec() {
718        let spec = ConfigSpec::try_from(PHLOW_SPEC_JSON).unwrap();
719        assert_eq!(phlow_spec(), spec);
720    }
721
722    #[test]
723    fn json_schema_version_must_match() {
724        let error = ConfigSpec::try_from(
725            r#"
726                {
727                    "schema_version": 999,
728                    "functions": []
729                }
730            "#,
731        )
732        .unwrap_err();
733
734        assert_eq!(
735            error.to_string(),
736            format!(
737                "schema_version 999 does not match supported version {}",
738                crate::SCHEMA_VERSION
739            )
740        );
741    }
742
743    #[test]
744    fn build_config_merge_combines_specs() {
745        const SECOND_SPEC: &str = custom! {
746            fn pragma "inspect",
747            mod pragma "extensions" {
748                derive {
749                    mod diagnostics {
750                        fn collect_warnings,
751                    }
752                }
753            }
754        };
755
756        let first_spec = config_spec();
757        let second_spec = ConfigSpec::try_from(SECOND_SPEC).unwrap();
758        let mut config = BuildConfig::from_spec(&first_spec);
759        config.merge(BuildConfig::from_spec(&second_spec));
760
761        assert_eq!(
762            config.pragmas,
763            vec!["command", "tooling", "inspect", "extensions"]
764        );
765
766        let tooling_derives = config.module_derives.get("tooling").unwrap();
767        assert_eq!(tooling_derives.len(), 1);
768
769        let extension_derives = config.module_derives.get("extensions").unwrap();
770        assert_eq!(extension_derives.len(), 1);
771        assert_eq!(extension_derives[0].name(), Some("diagnostics"));
772        assert_eq!(extension_derives[0].function_names(), ["collect_warnings"]);
773    }
774
775    #[test]
776    fn macro_schema_version_matches_public_constant() {
777        assert_eq!(
778            crate::SCHEMA_VERSION.to_string(),
779            crate::__custom_schema_version!()
780        );
781    }
782}