<div align="center">
# comperr
[](https://doc.rust-lang.org/edition-guide/rust-2024/)
[](https://crates.io/crates/comperr)
[](https://docs.rs/comperr)
[](https://codeberg.org/razkar/comperr/src/branch/main/LICENSE-MIT)
[](https://codeberg.org/razkar/comperr/src/branch/main/LICENSE-APACHE)
[](https://crates.io/crates/comperr)
[](https://deps.rs/repo/codeberg/razkar/comperr)
[](https://codeberg.org/razkar/comperr)
Span-accurate compile-time errors for proc-macro authors.
```rust
return comperr::error(span, "expected a string literal");
```
</div>
## The Problem
The obvious way to emit a `compile_error!` from a proc-macro:
```rust
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
```sh
cargo add comperr
```
MSRV: **Rust 1.85** (2024 edition). One dependency: `proc_macro2`.
## Usage
**Single error, return immediately:**
```rust
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:**
```rust
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:**
```rust
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
| `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:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.
Cheers, RazkarStudio
© 2026 RazkarStudio. All rights reserved.