rust-lang/rust · error · syn::Error

attributes must be outer attributes (`///`), not inner attri

Error message

attributes must be outer attributes (`///`), not inner attributes

What it means

Also raised by check_attributes in rustc_macros::query, in the else-branch after confirming the attribute path is doc. The doc attribute must use outer style (///) rather than inner style (//!). On query/non_query declarations only a leading doc comment makes sense; an inner doc comment is a structural mistake because there is no item body for it to document.

Source

Thrown at compiler/rustc_macros/src/query.rs:23

use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
    AttrStyle, Attribute, Error, Expr, Ident, Pat, ReturnType, Token, Type, braced, parenthesized,
    parse_macro_input, token,
};

mod kw {
    syn::custom_keyword!(non_query);
    syn::custom_keyword!(query);
}

/// Ensures only doc comment attributes are used
fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> {
    let inner = |attr: Attribute| {
        if !attr.path().is_ident("doc") {
            Err(Error::new(attr.span(), "attributes not supported on queries"))
        } else if attr.style != AttrStyle::Outer {
            Err(Error::new(
                attr.span(),
                "attributes must be outer attributes (`///`), not inner attributes",
            ))
        } else {
            Ok(attr)
        }
    };
    attrs.into_iter().map(inner).collect()
}

/// Declaration of a compiler query.
///
/// ```ignore (illustrative)
/// /// Doc comment for `my_query`.
/// //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^              doc_comments
/// query my_query(key: DefId) -> Value { anon }
/// //    ^^^^^^^^                               name
/// //             ^^^                           key_pat

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Replace //! with /// above the query/non_query declaration
  2. Ensure each query has its own /// doc comment rather than relying on a //! block

Example fix

// before
//! Documents my_query.
query my_query(key: DefId) -> Value { eval_always }
// after
/// Documents my_query.
query my_query(key: DefId) -> Value { eval_always }
Defensive patterns

Strategy: validation

Validate before calling

// 'attributes must be outer attributes (`///`), not inner attributes'
// — fired by the queries macro when you write #![...] (inner) instead of
// /// (outer) on a query. Validate source before expansion.
fn reject_inner_attrs_in_queries(src: &str) -> Result<(), String> {
    for (i, line) in src.lines().enumerate() {
        let t = line.trim_start();
        if t.starts_with("#![") || t.starts_with("#![ ") {
            return Err(format!(
                "line {}: inner attribute `#![...]` is not allowed here; use outer `///`",
                i + 1
            ));
        }
    }
    Ok(())
}

Type guard

enum AttrStyle { Outer, Inner, NotAttr }

fn classify_first_attr(line: &str) -> AttrStyle {
    let t = line.trim_start();
    if t.starts_with("///") || t.starts_with("//!") && t.trim_end_matches('/').trim().is_empty() == false {
        // outer doc comment OR (handled below) inner doc comment
    }
    let t = line.trim_start();
    if t.starts_with("///") || t.starts_with("#[") { AttrStyle::Outer }
    else if t.starts_with("#![") || t.starts_with("//!") { AttrStyle::Inner }
    else { AttrStyle::NotAttr }
}

fn is_outer_or_none(line: &str) -> bool {
    !matches!(classify_first_attr(line), AttrStyle::Inner)
}

Try / catch

// Compile-time error from the queries macro; prevent via pre-commit lint:
#[test]
fn queries_use_only_outer_attrs() {
    let src = include_str!("../src/compiler/rustc_query_impl.rs");
    for (i, line) in src.lines().enumerate() {
        let t = line.trim_start();
        if t.starts_with("#![") || t.starts_with("//!") {
            panic!("line {}: inner attribute/comment not supported on queries; use `///`", i + 1);
        }
    }
}

Prevention

When it happens

Trigger: An attribute with path doc and style != AttrStyle::Outer is parsed above a query or non_query declaration. In practice this is a //! comment placed where a /// comment is expected, because Attribute::parse_outer still picks up some inner-style tokens in certain macro contexts, or the user wrote //! above the declaration.

Common situations: Editing rustc query definition files and using //! (module doc) style instead of /// (item doc) above a query; converting a module-level comment to an item comment and forgetting to flip the slashes.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/0eee84e703bab76e.json. Report an issue: GitHub.