comperr 1.0.0

A minimal, lightweight crate for emitting span-accurate compile-time errors from procedural macros.
Documentation
  • Coverage
  • 100%
    9 out of 9 items documented0 out of 8 items with examples
  • Size
  • Source code size: 47.91 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 1.82 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 1m 1s Average build duration of successful builds.
  • all releases: 51s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • Repository
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • razkar-studio

comperr

Rust Version Crates.io Version docs.rs License MIT License Apache-2.0 Crates.io Downloads Deps.rs Maintenance

Span-accurate compile-time errors for proc-macro authors.

return comperr::error(span, "expected a string literal");

The Problem

The obvious way to emit a compile_error! from a proc-macro:

format!("compile_error!(\"{}\");", msg)
    .parse::<TokenStream>()
    .unwrap()

produces tokens with no source location. The compiler points at the macro call site, not the token that caused the problem. Your users see the wrong line.

comperr builds the compile_error!(...) invocation token by token, calling .set_span() on each one. The diagnostic lands where you intended.

Installation

cargo add comperr

MSRV: Rust 1.85 (2024 edition). One dependency: proc_macro2.

Usage

Single error, return immediately:

use comperr::error;
use proc_macro2::{Span, TokenStream};

pub fn my_macro(input: TokenStream) -> TokenStream {
    return error(span, "expected a string literal");
}

Multiple errors, emit them all at once:

use comperr::Error;
use proc_macro2::TokenStream;

pub fn my_macro(input: TokenStream) -> TokenStream {
    let mut errors = Error::empty();

    for field in &fields {
        if !is_valid(field) {
            errors.combine(Error::new(field.span(), "unsupported field type"));
        }
    }

    if !errors.is_empty() {
        return errors.to_compile_error();
    }

    // emit normal output
    TokenStream::new()
}

Collect errors from an iterator:

use comperr::Error;

let errors: Error = fields
    .iter()
    .filter_map(|f| validate(f).err())
    .collect();

if !errors.is_empty() {
    return errors.to_compile_error();
}

API

Item Purpose
error(span, msg) One-shot: build and return a single-error TokenStream.
Error::new(span, msg) Create a single Error.
Error::empty() Create an empty accumulator, safe to combine into.
Error::is_empty() Check whether any messages have been added.
Error::combine(other) Merge another Error in. All messages emit together.
Error::to_compile_error() Produce the TokenStream to return from your macro.
Error::from_token_stream(ts) Reconstruct an Error from a compile_error! token stream.
impl Extend<Error> Push errors from an iterator into an existing Error.
impl FromIterator<Error> Collect an iterator of errors into a single Error.

Error also implements Display, Debug, Clone, and std::error::Error.

How It Works

Every token in a TokenStream carries a Span recording its source location. When the compiler processes a compile_error! invocation, it reads the span off the argument token to decide where to point the diagnostic. comperr calls .set_span() on every token it constructs, so the span you pass in is what the compiler sees, not some anonymous internal position.

License

Licensed under either of:

at your option.

Cheers, RazkarStudio

© 2026 RazkarStudio. All rights reserved.