    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62

    use quote::ToTokens;
    use std::cell::RefCell;
    use std::fmt::Display;
    use std::thread;
    use syn;

    /// A type to collect errors together and format them.
    ///
    /// Dropping this object will cause a panic. It must be consumed using `check`.
    ///
    /// References can be shared since this type uses run-time exclusive mut checking.
    #[derive(Default)]
    pub struct Ctxt {
        // The contents will be set to `None` during checking. This is so that checking can be
        // enforced.
        errors: RefCell<Option<Vec<syn::Error>>>,
    }

    impl Ctxt {
        /// Create a new context object.
        ///
        /// This object contains no errors, but will still trigger a panic if it is not `check`ed.
        pub fn new() -> Self {
            Ctxt {
                errors: RefCell::new(Some(Vec::new())),
            }
        }

        /// Add an error to the context object with a tokenenizable object.
        ///
        /// The object is used for spanning in error messages.
        pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
            self.errors
                .borrow_mut()
                .as_mut()
                .unwrap()
                // Curb monomorphization from generating too many identical methods.
                .push(syn::Error::new_spanned(obj.into_token_stream(), msg));
        }

        /// Add one of Syn's parse errors.
        pub fn syn_error(&self, err: syn::Error) {
            self.errors.borrow_mut().as_mut().unwrap().push(err);
        }

        /// Consume this object, producing a formatted error string if there are errors.
        pub fn check(self) -> Result<(), Vec<syn::Error>> {
            let errors = self.errors.borrow_mut().take().unwrap();
            match errors.len() {
                0 => Ok(()),
                _ => Err(errors),
            }
        }
    }

    impl Drop for Ctxt {
        fn drop(&mut self) {
            if !thread::panicking() && self.errors.borrow().is_some() {
                panic!("forgot to check for errors");
            }
        }
    }