Gnome-class Internals

As with any procedural macro, gnome-class goes to great lengths to enusre that
errors are handled properly all throughout the macro. The errors that we're
interested in fall into a few categories:

Parse errors. For example the tokens inside of a gobject_gen! { ... }
invocation are invalid or malformed.

AST errors. While it's syntactically valid to define the same function twice
it's semantically invalid to do so. This class of errors is any error which
originates in the the procedural macro while it's generating the final set of
tokens.

The first step of the gobject_gen! macro is to parse the input into an
internal AST representation. This parsing operation is fallible, and errors can
happen at any time!

All parsing happens in src/parser/* and it generates an ast::Program. The
trick for handling errors here is all related to syn, the parsing library that
we're using. The syn crate provides a Parse trait which is used to define
custom parsers. Each custom parser can be defined in terms of other parsers as
well. The implementation of Parse for ast::Program transitively uses many
implementations of Parse for items already in syn.

The parsers themselves defined in this macro are each responsible for error
handling. Errors can be generated when a sub-parser fails or explicitly
generated via syn methods. The syn crate provides many useful opportunities
to produce good error messages during parsing, for example pointing directly at
an erroneous token and indicating what expected tokens were there.

The tl;dr; of handling parse errors is "we use syn and it just works".

Things get a little more interesting with AST errors or other semantic errors
that are detected after parsing is completed. Outside of parsing we're not using
syn's framework of error handling, but we still use syn::parse::Error for
our fundamental error type!

The first thing you'll notice is that almost all functions in the procedural
macro are fallible, returning a Result<T>. This is a typedef for Result<T, Errors> where the Errors type is defined in the src/errors.rs module. An
instance of Errors represents a list of errors to present to the user. A
list is used here so as many errors about the AST can be collected and presented
to the user, rather than forcing them to go through errors one at a time.

The Errors type is a list of syn::parse::Error errors, and implements From
from the syn error as well. Typically the Errors type is only constructed in
loops where each iteration is fallible (and errors are collected across
iterators). Otherwise it's vastly more common to only create one error and
return it via the From impl.

The some_item argument must implement the ToTokens trait and the error
returned will point to the spans of some_item. This is a convenient and
lightweight way of creating a custom error message on a specified set of spanned
tokens. Internally this uses syn::parse::Error::new_spanned to create an error
which actually spans the tokens represented by some_item.

With this idiom you'll find bail! used liberally throughout the library.
Almost all semantic errors are created and returned through this macro (which is
similar to the failure crate's own version of bail!). The first
argument is typically whatever token is being examined or construct that's
relevant, and is used to provide context for the error to ensure the users sees
not only the error message but where in the code it's actually pointing to.

Note that there is also a format_err! macro to create an instance of
syn::parse::Error if necessary.

The final class of errors has to do with errors in user-written code, such as
type errors or borrow-check errors. These errors do not come from gobject_gen!
or the macro here, but rather from the compiler. If this happens, though, we
want to make sure that the compiler errors are presented in a meaningful
fashion.

This class of errors is largely transparently handled by simply using syn. The
syn crate preserves all Span information of all tokens which means that all
errors messages will be appropriately positioned by rustc. The crucial aspect of
this error handling is ensuring that the Span information is not erased or
forgotten from the input tokens, as the Span on each token is used to generate
compiler diagnostics.

All of this is fundamentally built on the concept of Span and the ability for
a macro to expand to arbitrary tokens, including other macro invocations. A
Span represents a pointer to a part of the code, and of the tokens in the
original TokenStream are annotated with a span of where they came from. These
Span objects are then used to set spans on the returned TokenStream or
otherwise tokens may be preserved as-is in the output. By ensuring as many
tokens as possible have correct Span information we can have the highest
quality diagnostics from the compiler.

If an error actually happens then we'll bubble out an Err(error_list) all the
way to the entry point of the macro. We still have to return a TokenStream
though! To do this we convert the error_list to a TokenStream by iterating
over each error and converting it to a TokenStream. The way
syn::error::Error is converted to tokens looks like:

It generates an invocation of the compile_error! macro which is a way to
produce custom error messages when compiling Rust code. This macro is defined by
the Rust compiler.

By controlling the span information on each of these tokens (the compile_error
identifier, the ! punctuation, and the ( ... ) group) we can control where
the error message is pointed to. The implementation will adjust the span of each
of these tokens generated to the tokens relevant to the error messages, causing
rustc to produce a directed aerror message at the tokens we want.

The "default span" is created with Span::call_site() which represents the
call-site of the macro, or the macro invocation. This Span is used by default
for all tokens generated by quote! (liberally used to create TokenStream).
If an error happens on tokens that point to Span::call_site() then the error
will look like it comes from the macro invocation.

This typically happens when the macro itself generates invalid code. For example
if you were to return quote! { let x: u32 = "foo"; } then that's a type error
but the error message will point to the entire macro invocation (of
gobject_gen!, not quote!) due to the usage of Span::call_site() on each of
these tokens.

One helpful way to investigate these errors it to use the cargo expand
subcommand. That subcommand will print out the output of the macro, allowing you
to manually inspect the output or otherwise run it through rustc to figure out
where the error is happening.