Reference page
Proc-Macro and Lint Error Guide
This guide covers common error messages from #[miniextendr] proc macros and the miniextendr-lint static analysis tool.
This guide covers common error messages from #[miniextendr] proc macros and the miniextendr-lint static analysis tool.
🔗Running the Lint
just lint # Run lint on rpkg
The lint also runs automatically during cargo build/cargo check via build.rs. To disable temporarily:
MINIEXTENDR_LINT=0 cargo check --manifest-path=rpkg/src/rust/Cargo.toml🔗Lint Codes Reference
🔗Errors (CI-blocking)
| Code | Description | Fix |
|---|---|---|
| MXL001 | Reserved | (Legacy lint, no longer applicable) |
| MXL002 | Reserved | (Legacy lint, no longer applicable) |
| MXL003 | Reserved | (Legacy lint, no longer applicable) |
| MXL004 | Reserved | (Legacy lint, no longer applicable) |
| MXL005 | Reserved | (Legacy lint, no longer applicable) |
| MXL006 | Reserved | (Legacy lint, no longer applicable) |
| MXL007 | impl Type; requires ExternalPtr derive | Add #[derive(ExternalPtr)] or implement TypedExternal |
| MXL008 | Trait impl class system incompatible | Ensure trait impl uses same class system as inherent impl |
| MXL009 | Multiple impl blocks without labels | Add #[miniextendr(label = "unique")] to each impl block |
| MXL010 | Duplicate labels on impl blocks | Use unique labels for each impl block |
🔗Warnings (P0 — high impact)
| Code | Description | Fix |
|---|---|---|
| MXL100 | Duplicate entrypoint symbol | Rename one of the conflicting entries |
| MXL101 | Duplicate registration entries | Remove the duplicate entry |
| MXL102 | Trait impl missing TypedExternal | Implement TypedExternal for the type |
| MXL103 | Generic concrete type in trait-ABI | Use concrete (non-generic) types for trait ABI |
| MXL104 | #[cfg] mismatch between item and registration | Ensure #[cfg] attributes match on both |
| MXL105 | Unreachable module file | Check file paths and module hierarchy |
| MXL106 | Registered function is not pub | Add pub to the function definition |
| MXL107 | Missing #[miniextendr] impl Trait for Type | Add the attribute to the trait impl |
| MXL108 | Missing registration for trait impl | Add #[miniextendr] to the trait impl |
| MXL109 | #[cfg] mismatch between mod declarations | Ensure #[cfg] attributes are consistent |
🔗Warnings (P1 — important)
| Code | Description | Fix |
|---|---|---|
| MXL200 | Trait tag collision preflight | Use unique trait names or labels |
| MXL201 | Impl label mismatch | Check label spelling matches across impls |
| MXL202 | Orphan child module reference | Remove the reference to non-existent child module |
| MXL203 | internal + noexport redundancy | Use just #[miniextendr(internal)] (implies noexport) |
| MXL204 | Multiple root-level registrations | Only one root registration per crate |
🔗Warnings (P2 — safety)
| Code | Description | Fix |
|---|---|---|
| MXL300 | Direct Rf_error/Rf_errorcall call | Use panic!() or return Err(...) instead |
| MXL301 | _unchecked FFI call outside guard context | Use the checked wrapper, or ensure you’re inside with_r_unwind_protect |
🔗MXL300: Direct Rf_error calls
Rf_error() and Rf_errorcall() perform a C longjmp that skips Rust destructors. This leaks memory and can corrupt state. The lint detects these calls via text scanning.
Preferred alternatives:
panic!("message")— caught by miniextendr’s unwind protection, produces a structured R conditionreturn Err(...)— forResult<T, E>return types, produces a clean R error
When Rf_error is intentional (e.g., inside with_r_unwind_protect closures or test fixtures), suppress with // mxl::allow(MXL300) — see Inline Suppression below.
🔗MXL301: Unchecked FFI calls
Functions like Rf_ScalarInteger_unchecked() bypass miniextendr’s main-thread routing check. They are only safe when you are certain you’re on R’s main thread (inside ALTREP callbacks, with_r_unwind_protect closures, extern "C-unwind" functions called by R, etc.).
Preferred: Use the checked wrapper (without _unchecked suffix). It adds a debug-mode thread assertion.
When unchecked is intentional, suppress with // mxl::allow(MXL301).
🔗Inline Suppression
Both MXL300 and MXL301 support inline suppression via // mxl::allow(...) comments. The suppression comment can appear:
- On the same line (trailing comment):
Rf_error(c"%s".as_ptr(), c"intentional".as_ptr()) // mxl::allow(MXL300)
- On the immediately preceding line (standalone comment):
// mxl::allow(MXL300)
Rf_error(c"%s".as_ptr(), c"intentional".as_ptr())
Multiple codes can be suppressed in one comment:
// mxl::allow(MXL300, MXL301)
Rf_error_unchecked(c"test".as_ptr())
Important: The comment must be on the exact same line or the line directly above. A comment two lines above will not suppress the diagnostic.
// mxl::allow(MXL300) <-- too far away (2 lines above)
unsafe {
Rf_error(...) <-- NOT suppressed
}
Move the comment inside the block:
unsafe {
// mxl::allow(MXL300)
Rf_error(...) <-- suppressed
}🔗Suppression syntax
// mxl::allow(CODE)
// mxl::allow(CODE1, CODE2)
// mxl::allow(CODE1, CODE2, CODE3)
- Prefix:
// mxl::allow( - Codes: comma-separated, whitespace around commas is ignored
- Only
MXL300andMXL301are currently suppressible
🔗Common Proc-Macro Errors
🔗“dots must be the last parameter”
The ... (dots) argument must appear last in the function signature:
// Wrong
#[miniextendr]
fn bad(dots: &Dots, x: i32) -> i32 { x }
// Correct
#[miniextendr]
fn good(x: i32, dots: &Dots) -> i32 { x }🔗“expected pub function”
Only pub functions get @export in R wrappers. If the function is intentionally private, use #[miniextendr(noexport)] or #[miniextendr(internal)].
🔗“multiple impl blocks for Type need labels”
When a type has more than one #[miniextendr] impl block, each needs a unique label:
#[miniextendr(label = "core")]
impl MyType {
fn method_a(&self) -> i32 { 0 }
}
#[miniextendr(label = "extra")]
impl MyType {
fn method_b(&self) -> String { String::new() }
}🔗“trait impl class system incompatible”
A trait impl’s class system must match the inherent impl’s class system:
#[miniextendr(s3)]
impl MyType { /* ... */ }
// This will error if the trait impl uses a different class system
#[miniextendr] // Must inherit s3 from the inherent impl
impl Display for MyType { /* ... */ }🔗“type must derive ExternalPtr or implement TypedExternal”
Types used in #[miniextendr] impl blocks need pointer identity:
#[derive(ExternalPtr)]
struct MyType { /* ... */ }
#[miniextendr]
impl MyType { /* ... */ }
// Registration is automatic via #[miniextendr].🔗Debugging Tips
- Run
just lintbefore building — it catches attribute issues earlier than compile errors - Check NAMESPACE — if a function exists in Rust but not in R, run
just devtools-document - Feature-gated modules — use
#[cfg]onmoddeclarations for conditional compilation