From: Pierre-Emmanuel Patry <[email protected]>

Builtin attribute checking should be done after macro expansion because
we want to support macros within attributes.

gcc/rust/ChangeLog:

        * Make-lang.in: Add file for builtin attribute checking.
        * hir/rust-ast-lower-base.cc 
(ASTLoweringBase::handle_doc_item_attribute):
        Remove error emission and replace it with an assert. The error message
        will now be emitted from the builtin attribute checking pass.
        * rust-session-manager.cc (Session::compile_crate): Add builtin
        attribute checking step after expansion.
        * util/rust-attributes.cc (Attributes::is_known): Move down.
        (Attributes::extract_string_literal): Likewise.
        (Attributes::valid_outer_attribute): Add a function to retrieve outer/
        inner status for a given builtin attribute value.
        (AttributeChecker::visit): Move some checking to builtin attribute
        checker.
        (identify_builtin): Rename function from here ...
        (lookup_builtin): ... to here.
        (check_doc_alias): Move to builtin attribute checker.
        (check_doc_attribute): Likewise.
        (check_deprecated_attribute): Likewise.
        (check_valid_attribute_for_item): Likewise.
        (AttributeChecker::check_inner_attribute): Likewise.
        (check_link_section_attribute): Likewise.
        (check_export_name_attribute): Likewise.
        (check_lint_attribute): Likewise.
        (check_no_mangle_function): Likewise.
        (is_proc_macro_type): Update function call name.
        * util/rust-attributes.h (identify_builtin): Update prototype.
        (lookup_builtin): Likewise.
        * checks/errors/rust-builtin-attribute-checker.cc: New file.
        * checks/errors/rust-builtin-attribute-checker.h: New file.

gcc/testsuite/ChangeLog:

        * rust/compile/issue-4226.rs: Update text to match error message from
        attribute checking pass that was not triggered before.

Signed-off-by: Pierre-Emmanuel Patry <[email protected]>
---
 gcc/rust/Make-lang.in                         |   1 +
 .../errors/rust-builtin-attribute-checker.cc  | 602 ++++++++++++++++++
 .../errors/rust-builtin-attribute-checker.h   |  58 ++
 gcc/rust/hir/rust-ast-lower-base.cc           |   8 +-
 gcc/rust/rust-session-manager.cc              |   3 +
 gcc/rust/util/rust-attributes.cc              | 571 ++---------------
 gcc/rust/util/rust-attributes.h               |   4 +-
 gcc/testsuite/rust/compile/issue-4226.rs      |   4 +-
 8 files changed, 725 insertions(+), 526 deletions(-)
 create mode 100644 gcc/rust/checks/errors/rust-builtin-attribute-checker.cc
 create mode 100644 gcc/rust/checks/errors/rust-builtin-attribute-checker.h

diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in
index 4c394d39c0b..fc11abf1f1c 100644
--- a/gcc/rust/Make-lang.in
+++ b/gcc/rust/Make-lang.in
@@ -126,6 +126,7 @@ GRS_OBJS = \
     rust/rust-hir.o \
     rust/rust-hir-map.o \
     rust/rust-attributes.o \
+    rust/rust-builtin-attribute-checker.o \
     rust/rust-keyword-values.o \
     rust/rust-abi.o \
     rust/rust-token-converter.o \
diff --git a/gcc/rust/checks/errors/rust-builtin-attribute-checker.cc 
b/gcc/rust/checks/errors/rust-builtin-attribute-checker.cc
new file mode 100644
index 00000000000..8cb251f7686
--- /dev/null
+++ b/gcc/rust/checks/errors/rust-builtin-attribute-checker.cc
@@ -0,0 +1,602 @@
+// Copyright (C) 2026 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-builtin-attribute-checker.h"
+#include "optional.h"
+#include "rust-attributes.h"
+#include "rust-attribute-values.h"
+#include "rust-session-manager.h"
+
+namespace Rust {
+namespace Analysis {
+
+using Attrs = Values::Attributes;
+
+void
+check_inner_attribute (const AST::Attribute &attribute)
+{
+  auto result_opt = lookup_builtin (attribute);
+  if (!result_opt.has_value ())
+    return;
+  auto result = result_opt.value ();
+
+  if (Attributes::valid_outer_attribute (result.name))
+    rust_error_at (attribute.get_locus (),
+                  "attribute cannot be used at crate level");
+}
+
+/**
+ * Check that the string given to #[doc(alias = ...)] or #[doc(alias(...))] is
+ * valid.
+ *
+ * This means no whitespace characters other than spaces and no quoting
+ * characters.
+ */
+static void
+check_doc_alias (const std::string &alias_input, const location_t locus)
+{
+  // FIXME: The locus here is for the whole attribute. Can we get the locus
+  // of the alias input instead?
+  for (auto c : alias_input)
+    if ((ISSPACE (c) && c != ' ') || c == '\'' || c == '\"')
+      {
+       auto to_print = std::string (1, c);
+       switch (c)
+         {
+         case '\n':
+           to_print = "\\n";
+           break;
+         case '\t':
+           to_print = "\\t";
+           break;
+         default:
+           break;
+         }
+       rust_error_at (locus,
+                      "invalid character used in %<#[doc(alias)]%> input: %qs",
+                      to_print.c_str ());
+      }
+
+  if (alias_input.empty ())
+    return;
+
+  if (alias_input.front () == ' ' || alias_input.back () == ' ')
+    rust_error_at (locus,
+                  "%<#[doc(alias)]%> input cannot start or end with a space");
+}
+
+static void
+check_doc_attribute (const AST::Attribute &attribute)
+{
+  if (!attribute.has_attr_input ())
+    {
+      rust_error_at (
+       attribute.get_locus (),
+       "valid forms for the attribute are "
+       "%<#[doc(hidden|inline|...)]%> and %<#[doc = \" string \"]%>");
+      return;
+    }
+
+  switch (attribute.get_attr_input ().get_attr_input_type ())
+    {
+    case AST::AttrInput::LITERAL:
+    case AST::AttrInput::META_ITEM:
+    case AST::AttrInput::EXPR:
+      break;
+      // FIXME: Handle them as well
+
+    case AST::AttrInput::TOKEN_TREE:
+      {
+       // FIXME: This doesn't check for #[doc(alias(...))]
+       const auto &option = static_cast<const AST::DelimTokenTree &> (
+         attribute.get_attr_input ());
+       auto *meta_item = option.parse_to_meta_item ();
+
+       for (auto &item : meta_item->get_items ())
+         {
+           if (item->is_key_value_pair ())
+             {
+               auto name_value
+                 = static_cast<AST::MetaNameValueStr *> (item.get ())
+                     ->get_name_value_pair ();
+
+               // FIXME: Check for other stuff than #[doc(alias = ...)]
+               if (name_value.first.as_string () == "alias")
+                 check_doc_alias (name_value.second, attribute.get_locus ());
+             }
+         }
+       break;
+      }
+    }
+}
+
+static void
+check_deprecated_attribute (const AST::Attribute &attribute)
+{
+  if (!attribute.has_attr_input ())
+    return;
+
+  const auto &input = attribute.get_attr_input ();
+
+  if (input.get_attr_input_type () != AST::AttrInput::META_ITEM)
+    return;
+
+  auto &meta = static_cast<const AST::AttrInputMetaItemContainer &> (input);
+
+  for (auto &current : meta.get_items ())
+    {
+      switch (current->get_kind ())
+       {
+       case AST::MetaItemInner::Kind::MetaItem:
+         {
+           auto *meta_item = static_cast<AST::MetaItem *> (current.get ());
+
+           switch (meta_item->get_item_kind ())
+             {
+             case AST::MetaItem::ItemKind::NameValueStr:
+               {
+                 auto *nv = static_cast<AST::MetaNameValueStr *> (meta_item);
+
+                 const std::string key = nv->get_name ().as_string ();
+
+                 if (key != "since" && key != "note")
+                   {
+                     rust_error_at (nv->get_locus (), "unknown meta item %qs",
+                                    key.c_str ());
+                     rust_inform (nv->get_locus (),
+                                  "expected one of %<since%>, %<note%>");
+                   }
+               }
+               break;
+
+             case AST::MetaItem::ItemKind::Path:
+               {
+                 // #[deprecated(a,a)]
+                 auto *p = static_cast<AST::MetaItemPath *> (meta_item);
+
+                 std::string ident = p->get_path ().as_string ();
+
+                 rust_error_at (p->get_locus (), "unknown meta item %qs",
+                                ident.c_str ());
+                 rust_inform (p->get_locus (),
+                              "expected one of %<since%>, %<note%>");
+               }
+               break;
+
+             case AST::MetaItem::ItemKind::Word:
+               {
+                 // #[deprecated("a")]
+                 auto *w = static_cast<AST::MetaWord *> (meta_item);
+
+                 rust_error_at (
+                   w->get_locus (),
+                   "item in %<deprecated%> must be a key/value pair");
+               }
+               break;
+
+             case AST::MetaItem::ItemKind::PathExpr:
+               {
+                 // #[deprecated(since=a)]
+                 auto *px = static_cast<AST::MetaItemPathExpr *> (meta_item);
+
+                 rust_error_at (
+                   px->get_locus (),
+                   "expected unsuffixed literal or identifier, found %qs",
+                   px->get_expr ().as_string ().c_str ());
+               }
+               break;
+
+             case AST::MetaItem::ItemKind::Seq:
+             case AST::MetaItem::ItemKind::ListPaths:
+             case AST::MetaItem::ItemKind::ListNameValueStr:
+             default:
+               gcc_unreachable ();
+               break;
+             }
+         }
+         break;
+
+       case AST::MetaItemInner::Kind::LitExpr:
+       default:
+         gcc_unreachable ();
+         break;
+       }
+    }
+}
+
+static void
+check_link_section_attribute (const AST::Attribute &attribute)
+{
+  if (!attribute.has_attr_input ())
+    {
+      rust_error_at (attribute.get_locus (),
+                    "malformed %<link_section%> attribute input");
+      rust_inform (attribute.get_locus (),
+                  "must be of the form: %<#[link_section = \"name\"]%>");
+    }
+}
+
+static void
+check_export_name_attribute (const AST::Attribute &attribute)
+{
+  if (!attribute.has_attr_input ())
+    {
+      rust_error_at (attribute.get_locus (),
+                    "malformed %<export_name%> attribute input");
+      rust_inform (attribute.get_locus (),
+                  "must be of the form: %<#[export_name = \"name\"]%>");
+      return;
+    }
+
+  auto &attr_input = attribute.get_attr_input ();
+  if (attr_input.get_attr_input_type ()
+      == AST::AttrInput::AttrInputType::LITERAL)
+    {
+      auto &literal_expr
+       = static_cast<AST::AttrInputLiteral &> (attr_input).get_literal ();
+      auto lit_type = literal_expr.get_lit_type ();
+      switch (lit_type)
+       {
+       case AST::Literal::LitType::STRING:
+       case AST::Literal::LitType::RAW_STRING:
+       case AST::Literal::LitType::BYTE_STRING:
+         return;
+       default:
+         break;
+       }
+    }
+
+  rust_error_at (attribute.get_locus (), "attribute must be a string literal");
+}
+
+static void
+check_no_mangle_function (const AST::Attribute &attribute,
+                         const AST::Function &fun)
+{
+  if (attribute.has_attr_input ())
+    {
+      rust_error_at (attribute.get_locus (), ErrorCode::E0754,
+                    "malformed %<no_mangle%> attribute input");
+      rust_inform (attribute.get_locus (),
+                  "must be of the form: %<#[no_mangle]%>");
+    }
+  if (!is_ascii_only (fun.get_function_name ().as_string ()))
+    rust_error_at (fun.get_function_name ().get_locus (),
+                  "the %<#[no_mangle]%> attribute requires ASCII identifier");
+}
+
+static void
+check_lint_attribute (const AST::Attribute &attribute, const char *name)
+{
+  if (!attribute.has_attr_input ())
+    {
+      rust_error_at (attribute.get_locus (), "malformed %qs attribute input",
+                    name);
+      rust_inform (attribute.get_locus (),
+                  "must be of the form: %<#[%s(lint1, lint2, ...)]%>", name);
+    }
+}
+
+/**
+ * Emit an error when an attribute is attached
+ * to an incompatable item type. e.g.:
+ *
+ * #[cold]
+ * struct A(u8, u8);
+ *
+ * Note that "#[derive]" is handled
+ * explicitly in rust-derive.cc
+ */
+static void
+check_valid_attribute_for_item (const AST::Attribute &attr,
+                               const AST::Item &item)
+{
+  if (item.get_item_kind () != AST::Item::Kind::Function
+      && (attr.get_path () == Values::Attributes::TARGET_FEATURE
+         || attr.get_path () == Values::Attributes::COLD
+         || attr.get_path () == Values::Attributes::INLINE))
+    {
+      rust_error_at (attr.get_locus (),
+                    "the %<#[%s]%> attribute may only be applied to functions",
+                    attr.get_path ().as_string ().c_str ());
+    }
+  else if (attr.get_path () == Values::Attributes::REPR
+          && item.get_item_kind () != AST::Item::Kind::Enum
+          && item.get_item_kind () != AST::Item::Kind::Union
+          && item.get_item_kind () != AST::Item::Kind::Struct)
+    {
+      rust_error_at (attr.get_locus (),
+                    "the %<#[%s]%> attribute may only be applied "
+                    "to structs, enums and unions",
+                    attr.get_path ().as_string ().c_str ());
+    }
+}
+
+BuiltinAttributeChecker::BuiltinAttributeChecker () {}
+
+void
+BuiltinAttributeChecker::go (AST::Crate &crate)
+{
+  visit (crate);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::Crate &crate)
+{
+  for (auto &attr : crate.get_inner_attrs ())
+    {
+      check_inner_attribute (attr);
+    }
+
+  AST::DefaultASTVisitor::visit (crate);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::Attribute &attribute)
+{
+  if (auto builtin = lookup_builtin (attribute))
+    {
+      auto result = builtin.value ();
+      // TODO: Add checks here for each builtin attribute
+      // TODO: Have an enum of builtins as well, switching on strings is
+      // annoying and costly
+      if (result.name == Attrs::DOC)
+       check_doc_attribute (attribute);
+      else if (result.name == Attrs::DEPRECATED)
+       check_deprecated_attribute (attribute);
+    }
+
+  AST::DefaultASTVisitor::visit (attribute);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::Module &module)
+{
+  for (auto &attr : module.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, module);
+
+  AST::DefaultASTVisitor::visit (module);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::ExternCrate &crate)
+{
+  for (auto &attr : crate.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, crate);
+
+  AST::DefaultASTVisitor::visit (crate);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::UseDeclaration &declaration)
+{
+  for (auto &attr : declaration.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, declaration);
+
+  AST::DefaultASTVisitor::visit (declaration);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::Function &function)
+{
+  auto check_crate_type = [] (const char *name, AST::Attribute &attribute) {
+    if (!Session::get_instance ().options.is_proc_macro ())
+      rust_error_at (attribute.get_locus (),
+                    "the %<#[%s]%> attribute is only usable with crates of "
+                    "the %<proc-macro%> crate type",
+                    name);
+  };
+
+  BuiltinAttrDefinition result;
+  for (auto &attribute : function.get_outer_attrs ())
+    {
+      check_valid_attribute_for_item (attribute, function);
+
+      auto result = lookup_builtin (attribute);
+      if (!result)
+       return;
+
+      auto name = result->name.c_str ();
+
+      if (result->name == Attrs::PROC_MACRO_DERIVE)
+       {
+         if (!attribute.has_attr_input ())
+           {
+             rust_error_at (attribute.get_locus (),
+                            "malformed %qs attribute input", name);
+             rust_inform (
+               attribute.get_locus (),
+               "must be of the form: %<#[proc_macro_derive(TraitName, "
+               "/*opt*/ attributes(name1, name2, ...))]%>");
+           }
+         check_crate_type (name, attribute);
+       }
+      else if (result->name == Attrs::PROC_MACRO
+              || result->name == Attrs::PROC_MACRO_ATTRIBUTE)
+       {
+         check_crate_type (name, attribute);
+       }
+      else if (result->name == Attrs::TARGET_FEATURE)
+       {
+         if (!attribute.has_attr_input ())
+           {
+             rust_error_at (attribute.get_locus (),
+                            "malformed %<target_feature%> attribute input");
+             rust_inform (attribute.get_locus (),
+                          "must be of the form: %<#[target_feature(enable = "
+                          "\"name\")]%>");
+           }
+         else if (!function.get_qualifiers ().is_unsafe ())
+           {
+             rust_error_at (
+               attribute.get_locus (),
+               "the %<#[target_feature]%> attribute can only be applied "
+               "to %<unsafe%> functions");
+           }
+       }
+      else if (result->name == Attrs::NO_MANGLE)
+       {
+         if (attribute.has_attr_input ())
+           {
+             rust_error_at (attribute.get_locus (),
+                            "malformed %<no_mangle%> attribute input");
+             rust_inform (attribute.get_locus (),
+                          "must be of the form: %<#[no_mangle]%>");
+           }
+         else
+           check_no_mangle_function (attribute, function);
+       }
+      else if (result->name == Attrs::EXPORT_NAME)
+       {
+         check_export_name_attribute (attribute);
+       }
+      else if (result->name == Attrs::ALLOW || result->name == "deny"
+              || result->name == "warn" || result->name == "forbid")
+       {
+         check_lint_attribute (attribute, name);
+       }
+      else if (result->name == Attrs::LINK_NAME)
+       {
+         if (!attribute.has_attr_input ())
+           {
+             rust_error_at (attribute.get_locus (),
+                            "malformed %<link_name%> attribute input");
+             rust_inform (attribute.get_locus (),
+                          "must be of the form: %<#[link_name = \"name\"]%>");
+           }
+       }
+      else if (result->name == Attrs::LINK_SECTION)
+       {
+         check_link_section_attribute (attribute);
+       }
+    }
+
+  AST::DefaultASTVisitor::visit (function);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::TypeAlias &alias)
+{
+  for (auto &attr : alias.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, alias);
+
+  AST::DefaultASTVisitor::visit (alias);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::StructStruct &struct_item)
+{
+  for (auto &attr : struct_item.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, struct_item);
+
+  AST::DefaultASTVisitor::visit (struct_item);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::TupleStruct &tuple_struct)
+{
+  for (auto &attr : tuple_struct.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, tuple_struct);
+
+  AST::DefaultASTVisitor::visit (tuple_struct);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::Enum &enumeration)
+{
+  for (auto &attr : enumeration.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, enumeration);
+
+  AST::DefaultASTVisitor::visit (enumeration);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::Union &u)
+{
+  for (auto &attr : u.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, u);
+
+  AST::DefaultASTVisitor::visit (u);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::ConstantItem &item)
+{
+  for (auto &attr : item.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, item);
+
+  AST::DefaultASTVisitor::visit (item);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::StaticItem &item)
+{
+  for (auto &attr : item.get_outer_attrs ())
+    {
+      check_valid_attribute_for_item (attr, item);
+
+      if (auto result = lookup_builtin (attr))
+       {
+         if (result->name == Attrs::LINK_SECTION)
+           check_link_section_attribute (attr);
+         else if (result->name == Attrs::EXPORT_NAME)
+           check_export_name_attribute (attr);
+       }
+    }
+
+  AST::DefaultASTVisitor::visit (item);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::Trait &trait)
+{
+  for (auto &attr : trait.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, trait);
+
+  AST::DefaultASTVisitor::visit (trait);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::InherentImpl &impl)
+{
+  for (auto &attr : impl.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, impl);
+
+  AST::DefaultASTVisitor::visit (impl);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::TraitImpl &impl)
+{
+  for (auto &attr : impl.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, impl);
+
+  AST::DefaultASTVisitor::visit (impl);
+}
+
+void
+BuiltinAttributeChecker::visit (AST::ExternBlock &block)
+{
+  for (auto &attr : block.get_outer_attrs ())
+    check_valid_attribute_for_item (attr, block);
+
+  AST::DefaultASTVisitor::visit (block);
+}
+
+} // namespace Analysis
+} // namespace Rust
diff --git a/gcc/rust/checks/errors/rust-builtin-attribute-checker.h 
b/gcc/rust/checks/errors/rust-builtin-attribute-checker.h
new file mode 100644
index 00000000000..6717de8abf6
--- /dev/null
+++ b/gcc/rust/checks/errors/rust-builtin-attribute-checker.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2026 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_BUILTIN_ATTRIBUTE_CHECKER_H
+#define RUST_BUILTIN_ATTRIBUTE_CHECKER_H
+
+#include "rust-ast-visitor.h"
+
+namespace Rust {
+namespace Analysis {
+
+class BuiltinAttributeChecker : public AST::DefaultASTVisitor
+{
+  using AST::DefaultASTVisitor::visit;
+
+public:
+  BuiltinAttributeChecker ();
+  void go (AST::Crate &crate);
+  void visit (AST::Crate &crate) override;
+  void visit (AST::Attribute &attribute) override;
+
+  // Items
+  void visit (AST::Module &module) override;
+  void visit (AST::ExternCrate &crate) override;
+  void visit (AST::UseDeclaration &use_decl) override;
+  void visit (AST::Function &function) override;
+  void visit (AST::TypeAlias &type_alias) override;
+  void visit (AST::StructStruct &struct_item) override;
+  void visit (AST::TupleStruct &tuple_struct) override;
+  void visit (AST::Enum &enum_item) override;
+  void visit (AST::Union &union_item) override;
+  void visit (AST::ConstantItem &const_item) override;
+  void visit (AST::StaticItem &static_item) override;
+  void visit (AST::Trait &trait) override;
+  void visit (AST::InherentImpl &impl) override;
+  void visit (AST::TraitImpl &impl) override;
+  void visit (AST::ExternBlock &block) override;
+};
+
+} // namespace Analysis
+} // namespace Rust
+
+#endif /* ! RUST_BUILTIN_ATTRIBUTE_CHECKER_H */
diff --git a/gcc/rust/hir/rust-ast-lower-base.cc 
b/gcc/rust/hir/rust-ast-lower-base.cc
index 8b2fa5a2f50..6fc4463a934 100644
--- a/gcc/rust/hir/rust-ast-lower-base.cc
+++ b/gcc/rust/hir/rust-ast-lower-base.cc
@@ -832,13 +832,7 @@ void
 ASTLoweringBase::handle_doc_item_attribute (const ItemWrapper &,
                                            const AST::Attribute &attr)
 {
-  if (!attr.has_attr_input ())
-    {
-      rust_error_at (attr.get_locus (),
-                    "attribute must be of the form %qs or %qs",
-                    "#[doc(hidden|inline|...)]", "#[doc = string]");
-      return;
-    }
+  rust_assert (attr.has_attr_input ());
 
   auto simple_doc_comment = attr.get_attr_input ().get_attr_input_type ()
                            == AST::AttrInput::AttrInputType::LITERAL;
diff --git a/gcc/rust/rust-session-manager.cc b/gcc/rust/rust-session-manager.cc
index 12e076d1df0..364ca081142 100644
--- a/gcc/rust/rust-session-manager.cc
+++ b/gcc/rust/rust-session-manager.cc
@@ -60,6 +60,7 @@
 #include "rust-borrow-checker.h"
 #include "rust-ast-validation.h"
 #include "rust-tyty-variance-analysis.h"
+#include "rust-builtin-attribute-checker.h"
 
 #include "input.h"
 #include "selftest.h"
@@ -735,6 +736,8 @@ Session::compile_crate (const char *filename)
 
   expansion (parsed_crate, name_resolution_ctx);
 
+  Analysis::BuiltinAttributeChecker ().go (parsed_crate);
+
   AST::CollectLangItems ().go (parsed_crate);
 
   rust_debug ("\033[0;31mSUCCESSFULLY FINISHED EXPANSION \033[0m");
diff --git a/gcc/rust/util/rust-attributes.cc b/gcc/rust/util/rust-attributes.cc
index 3b6587ffdaa..f03fa90d9c0 100644
--- a/gcc/rust/util/rust-attributes.cc
+++ b/gcc/rust/util/rust-attributes.cc
@@ -29,40 +29,6 @@
 namespace Rust {
 namespace Analysis {
 
-bool
-Attributes::is_known (const std::string &attribute_path)
-{
-  const auto &lookup
-    = BuiltinAttributeMappings::get ()->lookup_builtin (attribute_path);
-
-  return !lookup.is_error ();
-}
-
-tl::optional<std::string>
-Attributes::extract_string_literal (const AST::Attribute &attr)
-{
-  if (!attr.has_attr_input ())
-    return tl::nullopt;
-
-  auto &attr_input = attr.get_attr_input ();
-
-  if (attr_input.get_attr_input_type ()
-      != AST::AttrInput::AttrInputType::LITERAL)
-    return tl::nullopt;
-
-  auto &literal_expr
-    = static_cast<AST::AttrInputLiteral &> (attr_input).get_literal ();
-
-  auto lit_type = literal_expr.get_lit_type ();
-
-  // TODO: bring escape sequence handling out of lexing?
-  if (lit_type != AST::Literal::LitType::STRING
-      && lit_type != AST::Literal::LitType::RAW_STRING)
-    return tl::nullopt;
-
-  return literal_expr.as_string ();
-}
-
 using Attrs = Values::Attributes;
 
 // 
https://doc.rust-lang.org/stable/nightly-rustc/src/rustc_feature/builtin_attrs.rs.html#248
@@ -151,6 +117,45 @@ static const std::set<std::string> __outer_attributes
      Attrs::LINK_NAME,
      Attrs::LINK_SECTION};
 
+bool
+Attributes::is_known (const std::string &attribute_path)
+{
+  const auto &lookup
+    = BuiltinAttributeMappings::get ()->lookup_builtin (attribute_path);
+
+  return !lookup.is_error ();
+}
+
+bool
+Attributes::valid_outer_attribute (const std::string &attribute_path)
+{
+  return __outer_attributes.find (attribute_path) != __outer_attributes.cend 
();
+}
+
+tl::optional<std::string>
+Attributes::extract_string_literal (const AST::Attribute &attr)
+{
+  if (!attr.has_attr_input ())
+    return tl::nullopt;
+
+  auto &attr_input = attr.get_attr_input ();
+
+  if (attr_input.get_attr_input_type ()
+      != AST::AttrInput::AttrInputType::LITERAL)
+    return tl::nullopt;
+
+  auto &literal_expr
+    = static_cast<AST::AttrInputLiteral &> (attr_input).get_literal ();
+
+  auto lit_type = literal_expr.get_lit_type ();
+
+  // TODO: bring escape sequence handling out of lexing?
+  if (lit_type != AST::Literal::LitType::STRING
+      && lit_type != AST::Literal::LitType::RAW_STRING)
+    return tl::nullopt;
+
+  return literal_expr.as_string ();
+}
 BuiltinAttributeMappings *
 BuiltinAttributeMappings::get ()
 {
@@ -189,20 +194,8 @@ AttributeChecker::go (AST::Crate &crate)
   visit (crate);
 }
 
-void
-AttributeChecker::visit (AST::Crate &crate)
-{
-  for (auto &attr : crate.get_inner_attrs ())
-    {
-      check_inner_attribute (attr);
-    }
-
-  for (auto &item : crate.items)
-    item->accept_vis (*this);
-}
-
 tl::optional<BuiltinAttrDefinition>
-identify_builtin (const AST::Attribute &attribute)
+lookup_builtin (const AST::Attribute &attribute)
 {
   auto &segments = attribute.get_path ().get_segments ();
 
@@ -216,224 +209,10 @@ identify_builtin (const AST::Attribute &attribute)
     segments.at (0).get_segment_name ());
 }
 
-/**
- * Check that the string given to #[doc(alias = ...)] or #[doc(alias(...))] is
- * valid.
- *
- * This means no whitespace characters other than spaces and no quoting
- * characters.
- */
-static void
-check_doc_alias (const std::string &alias_input, const location_t locus)
-{
-  // FIXME: The locus here is for the whole attribute. Can we get the locus
-  // of the alias input instead?
-  for (auto c : alias_input)
-    if ((ISSPACE (c) && c != ' ') || c == '\'' || c == '\"')
-      {
-       auto to_print = std::string (1, c);
-       switch (c)
-         {
-         case '\n':
-           to_print = "\\n";
-           break;
-         case '\t':
-           to_print = "\\t";
-           break;
-         default:
-           break;
-         }
-       rust_error_at (locus,
-                      "invalid character used in %<#[doc(alias)]%> input: %qs",
-                      to_print.c_str ());
-      }
-
-  if (alias_input.empty ())
-    return;
-
-  if (alias_input.front () == ' ' || alias_input.back () == ' ')
-    rust_error_at (locus,
-                  "%<#[doc(alias)]%> input cannot start or end with a space");
-}
-
-static void
-check_doc_attribute (const AST::Attribute &attribute)
-{
-  if (!attribute.has_attr_input ())
-    {
-      rust_error_at (
-       attribute.get_locus (),
-       "valid forms for the attribute are "
-       "%<#[doc(hidden|inline|...)]%> and %<#[doc = \" string \"]%>");
-      return;
-    }
-
-  switch (attribute.get_attr_input ().get_attr_input_type ())
-    {
-    case AST::AttrInput::LITERAL:
-    case AST::AttrInput::META_ITEM:
-    case AST::AttrInput::EXPR:
-      break;
-      // FIXME: Handle them as well
-
-    case AST::AttrInput::TOKEN_TREE:
-      {
-       // FIXME: This doesn't check for #[doc(alias(...))]
-       const auto &option = static_cast<const AST::DelimTokenTree &> (
-         attribute.get_attr_input ());
-       auto *meta_item = option.parse_to_meta_item ();
-
-       for (auto &item : meta_item->get_items ())
-         {
-           if (item->is_key_value_pair ())
-             {
-               auto name_value
-                 = static_cast<AST::MetaNameValueStr *> (item.get ())
-                     ->get_name_value_pair ();
-
-               // FIXME: Check for other stuff than #[doc(alias = ...)]
-               if (name_value.first.as_string () == "alias")
-                 check_doc_alias (name_value.second, attribute.get_locus ());
-             }
-         }
-       break;
-      }
-    }
-}
-
-static void
-check_deprecated_attribute (const AST::Attribute &attribute)
-{
-  if (!attribute.has_attr_input ())
-    return;
-
-  const auto &input = attribute.get_attr_input ();
-
-  if (input.get_attr_input_type () != AST::AttrInput::META_ITEM)
-    return;
-
-  auto &meta = static_cast<const AST::AttrInputMetaItemContainer &> (input);
-
-  for (auto &current : meta.get_items ())
-    {
-      switch (current->get_kind ())
-       {
-       case AST::MetaItemInner::Kind::MetaItem:
-         {
-           auto *meta_item = static_cast<AST::MetaItem *> (current.get ());
-
-           switch (meta_item->get_item_kind ())
-             {
-             case AST::MetaItem::ItemKind::NameValueStr:
-               {
-                 auto *nv = static_cast<AST::MetaNameValueStr *> (meta_item);
-
-                 const std::string key = nv->get_name ().as_string ();
-
-                 if (key != "since" && key != "note")
-                   {
-                     rust_error_at (nv->get_locus (), "unknown meta item %qs",
-                                    key.c_str ());
-                     rust_inform (nv->get_locus (),
-                                  "expected one of %<since%>, %<note%>");
-                   }
-               }
-               break;
-
-             case AST::MetaItem::ItemKind::Path:
-               {
-                 // #[deprecated(a,a)]
-                 auto *p = static_cast<AST::MetaItemPath *> (meta_item);
-
-                 std::string ident = p->get_path ().as_string ();
-
-                 rust_error_at (p->get_locus (), "unknown meta item %qs",
-                                ident.c_str ());
-                 rust_inform (p->get_locus (),
-                              "expected one of %<since%>, %<note%>");
-               }
-               break;
-
-             case AST::MetaItem::ItemKind::Word:
-               {
-                 // #[deprecated("a")]
-                 auto *w = static_cast<AST::MetaWord *> (meta_item);
-
-                 rust_error_at (
-                   w->get_locus (),
-                   "item in %<deprecated%> must be a key/value pair");
-               }
-               break;
-
-             case AST::MetaItem::ItemKind::PathExpr:
-               {
-                 // #[deprecated(since=a)]
-                 auto *px = static_cast<AST::MetaItemPathExpr *> (meta_item);
-
-                 rust_error_at (
-                   px->get_locus (),
-                   "expected unsuffixed literal or identifier, found %qs",
-                   px->get_expr ().as_string ().c_str ());
-               }
-               break;
-
-             case AST::MetaItem::ItemKind::Seq:
-             case AST::MetaItem::ItemKind::ListPaths:
-             case AST::MetaItem::ItemKind::ListNameValueStr:
-             default:
-               gcc_unreachable ();
-               break;
-             }
-         }
-         break;
-
-       case AST::MetaItemInner::Kind::LitExpr:
-       default:
-         gcc_unreachable ();
-         break;
-       }
-    }
-}
-
-/**
- * Emit an error when an attribute is attached
- * to an incompatable item type. e.g.:
- *
- * #[cold]
- * struct A(u8, u8);
- *
- * Note that "#[derive]" is handled
- * explicitly in rust-derive.cc
- */
-static void
-check_valid_attribute_for_item (const AST::Attribute &attr,
-                               const AST::Item &item)
-{
-  if (item.get_item_kind () != AST::Item::Kind::Function
-      && (attr.get_path () == Values::Attributes::TARGET_FEATURE
-         || attr.get_path () == Values::Attributes::COLD
-         || attr.get_path () == Values::Attributes::INLINE))
-    {
-      rust_error_at (attr.get_locus (),
-                    "the %<#[%s]%> attribute may only be applied to functions",
-                    attr.get_path ().as_string ().c_str ());
-    }
-  else if (attr.get_path () == Values::Attributes::REPR
-          && item.get_item_kind () != AST::Item::Kind::Enum
-          && item.get_item_kind () != AST::Item::Kind::Union
-          && item.get_item_kind () != AST::Item::Kind::Struct)
-    {
-      rust_error_at (attr.get_locus (),
-                    "the %<#[%s]%> attribute may only be applied "
-                    "to structs, enums and unions",
-                    attr.get_path ().as_string ().c_str ());
-    }
-}
-
 static bool
 is_proc_macro_type (const AST::Attribute &attribute)
 {
-  auto result_opt = identify_builtin (attribute);
+  auto result_opt = lookup_builtin (attribute);
   if (!result_opt.has_value ())
     return false;
   auto result = result_opt.value ();
@@ -469,77 +248,6 @@ check_proc_macro_non_root (const AST::Attribute &attr, 
location_t loc)
     }
 }
 
-void
-AttributeChecker::check_inner_attribute (const AST::Attribute &attribute)
-{
-  auto result_opt = identify_builtin (attribute);
-  if (!result_opt.has_value ())
-    return;
-  auto result = result_opt.value ();
-
-  if (__outer_attributes.find (result.name) != __outer_attributes.end ())
-    rust_error_at (attribute.get_locus (),
-                  "attribute cannot be used at crate level");
-}
-
-static void
-check_link_section_attribute (const AST::Attribute &attribute)
-{
-  if (!attribute.has_attr_input ())
-    {
-      rust_error_at (attribute.get_locus (),
-                    "malformed %<link_section%> attribute input");
-      rust_inform (attribute.get_locus (),
-                  "must be of the form: %<#[link_section = \"name\"]%>");
-    }
-}
-
-static void
-check_export_name_attribute (const AST::Attribute &attribute)
-{
-  if (!attribute.has_attr_input ())
-    {
-      rust_error_at (attribute.get_locus (),
-                    "malformed %<export_name%> attribute input");
-      rust_inform (attribute.get_locus (),
-                  "must be of the form: %<#[export_name = \"name\"]%>");
-      return;
-    }
-
-  if (attribute.has_attr_input ())
-    {
-      auto &attr_input = attribute.get_attr_input ();
-      if (attr_input.get_attr_input_type ()
-         == AST::AttrInput::AttrInputType::LITERAL)
-       {
-         auto &literal_expr
-           = static_cast<AST::AttrInputLiteral &> (attr_input).get_literal ();
-         auto lit_type = literal_expr.get_lit_type ();
-         switch (lit_type)
-           {
-           case AST::Literal::LitType::STRING:
-           case AST::Literal::LitType::RAW_STRING:
-           case AST::Literal::LitType::BYTE_STRING:
-             return;
-           default:
-             break;
-           }
-       }
-    }
-}
-
-static void
-check_lint_attribute (const AST::Attribute &attribute, const char *name)
-{
-  if (!attribute.has_attr_input ())
-    {
-      rust_error_at (attribute.get_locus (), "malformed %qs attribute input",
-                    name);
-      rust_inform (attribute.get_locus (),
-                  "must be of the form: %<#[%s(lint1, lint2, ...)]%>", name);
-    }
-}
-
 void
 AttributeChecker::visit (AST::Attribute &attribute)
 {
@@ -552,18 +260,6 @@ AttributeChecker::visit (AST::Attribute &attribute)
        return; // Do not emit errors for attribute that'll get stripped.
     }
 
-  if (auto builtin = identify_builtin (attribute))
-    {
-      auto result = builtin.value ();
-      // TODO: Add checks here for each builtin attribute
-      // TODO: Have an enum of builtins as well, switching on strings is
-      // annoying and costly
-      if (result.name == Attrs::DOC)
-       check_doc_attribute (attribute);
-      else if (result.name == Attrs::DEPRECATED)
-       check_deprecated_attribute (attribute);
-    }
-
   AST::DefaultASTVisitor::visit (attribute);
 }
 
@@ -870,10 +566,7 @@ void
 AttributeChecker::visit (AST::Module &module)
 {
   for (auto &attr : module.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, module);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 
   for (auto &item : module.get_items ())
     for (auto &attr : item->get_outer_attrs ())
@@ -886,10 +579,7 @@ void
 AttributeChecker::visit (AST::ExternCrate &crate)
 {
   for (auto &attr : crate.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, crate);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 }
 
 void
@@ -908,124 +598,12 @@ void
 AttributeChecker::visit (AST::UseDeclaration &declaration)
 {
   for (auto &attr : declaration.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, declaration);
-      check_proc_macro_non_function (attr);
-    }
-}
-
-static void
-check_no_mangle_function (const AST::Attribute &attribute,
-                         const AST::Function &fun)
-{
-  if (attribute.has_attr_input ())
-    {
-      rust_error_at (attribute.get_locus (), ErrorCode::E0754,
-                    "malformed %<no_mangle%> attribute input");
-      rust_inform (attribute.get_locus (),
-                  "must be of the form: %<#[no_mangle]%>");
-    }
-  if (!is_ascii_only (fun.get_function_name ().as_string ()))
-    rust_error_at (fun.get_function_name ().get_locus (),
-                  "the %<#[no_mangle]%> attribute requires ASCII identifier");
+    check_proc_macro_non_function (attr);
 }
 
 void
 AttributeChecker::visit (AST::Function &fun)
 {
-  for (auto &attr : fun.get_outer_attrs ())
-    check_valid_attribute_for_item (attr, fun);
-
-  auto check_crate_type = [] (const char *name, AST::Attribute &attribute) {
-    if (!Session::get_instance ().options.is_proc_macro ())
-      rust_error_at (attribute.get_locus (),
-                    "the %<#[%s]%> attribute is only usable with crates of "
-                    "the %<proc-macro%> crate type",
-                    name);
-  };
-
-  BuiltinAttrDefinition result;
-  for (auto &attribute : fun.get_outer_attrs ())
-    {
-      auto result = identify_builtin (attribute);
-      if (!result)
-       return;
-
-      auto name = result->name.c_str ();
-
-      if (result->name == Attrs::PROC_MACRO_DERIVE)
-       {
-         if (!attribute.has_attr_input ())
-           {
-             rust_error_at (attribute.get_locus (),
-                            "malformed %qs attribute input", name);
-             rust_inform (
-               attribute.get_locus (),
-               "must be of the form: %<#[proc_macro_derive(TraitName, "
-               "/*opt*/ attributes(name1, name2, ...))]%>");
-           }
-         check_crate_type (name, attribute);
-       }
-      else if (result->name == Attrs::PROC_MACRO
-              || result->name == Attrs::PROC_MACRO_ATTRIBUTE)
-       {
-         check_crate_type (name, attribute);
-       }
-      else if (result->name == Attrs::TARGET_FEATURE)
-       {
-         if (!attribute.has_attr_input ())
-           {
-             rust_error_at (attribute.get_locus (),
-                            "malformed %<target_feature%> attribute input");
-             rust_inform (attribute.get_locus (),
-                          "must be of the form: %<#[target_feature(enable = "
-                          "\"name\")]%>");
-           }
-         else if (!fun.get_qualifiers ().is_unsafe ())
-           {
-             rust_error_at (
-               attribute.get_locus (),
-               "the %<#[target_feature]%> attribute can only be applied "
-               "to %<unsafe%> functions");
-           }
-       }
-      else if (result->name == Attrs::NO_MANGLE)
-       {
-         if (attribute.has_attr_input ())
-           {
-             rust_error_at (attribute.get_locus (),
-                            "malformed %<no_mangle%> attribute input");
-             rust_inform (attribute.get_locus (),
-                          "must be of the form: %<#[no_mangle]%>");
-           }
-         else
-           check_no_mangle_function (attribute, fun);
-       }
-      else if (result->name == Attrs::EXPORT_NAME)
-       {
-         check_export_name_attribute (attribute);
-       }
-      else if (result->name == Attrs::ALLOW || result->name == "deny"
-              || result->name == "warn" || result->name == "forbid")
-       {
-         check_lint_attribute (attribute, name);
-       }
-      else if (result->name == Attrs::LINK_NAME)
-       {
-         if (!attribute.has_attr_input ())
-           {
-             rust_error_at (attribute.get_locus (),
-                            "malformed %<link_name%> attribute input");
-             rust_inform (attribute.get_locus (),
-                          "must be of the form: %<#[link_name = \"name\"]%>");
-           }
-       }
-      else if (result->name == Attrs::LINK_SECTION)
-       {
-         check_link_section_attribute (attribute);
-       }
-    }
-
   if (fun.has_body ())
     fun.get_definition ().value ()->accept_vis (*this);
 }
@@ -1034,10 +612,7 @@ void
 AttributeChecker::visit (AST::TypeAlias &alias)
 {
   for (auto &attr : alias.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, alias);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 }
 
 void
@@ -1045,7 +620,6 @@ AttributeChecker::visit (AST::StructStruct &struct_item)
 {
   for (auto &attr : struct_item.get_outer_attrs ())
     {
-      check_valid_attribute_for_item (attr, struct_item);
       check_proc_macro_non_function (attr);
     }
 
@@ -1056,10 +630,7 @@ void
 AttributeChecker::visit (AST::TupleStruct &tuplestruct)
 {
   for (auto &attr : tuplestruct.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, tuplestruct);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 }
 
 void
@@ -1082,48 +653,28 @@ void
 AttributeChecker::visit (AST::Enum &enumeration)
 {
   for (auto &attr : enumeration.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, enumeration);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 }
 
 void
 AttributeChecker::visit (AST::Union &u)
 {
   for (auto &attr : u.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, u);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 }
 
 void
 AttributeChecker::visit (AST::ConstantItem &item)
 {
   for (auto &attr : item.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, item);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 }
 
 void
 AttributeChecker::visit (AST::StaticItem &item)
 {
   for (auto &attr : item.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, item);
-      check_proc_macro_non_function (attr);
-
-      if (auto result = identify_builtin (attr))
-       {
-         if (result->name == Attrs::LINK_SECTION)
-           check_link_section_attribute (attr);
-         else if (result->name == Attrs::EXPORT_NAME)
-           check_export_name_attribute (attr);
-       }
-    }
+    check_proc_macro_non_function (attr);
 }
 
 void
@@ -1134,10 +685,8 @@ void
 AttributeChecker::visit (AST::Trait &trait)
 {
   for (auto &attr : trait.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, trait);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
+
   AST::DefaultASTVisitor::visit (trait);
 }
 
@@ -1145,10 +694,7 @@ void
 AttributeChecker::visit (AST::InherentImpl &impl)
 {
   for (auto &attr : impl.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, impl);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 
   AST::DefaultASTVisitor::visit (impl);
 }
@@ -1157,10 +703,8 @@ void
 AttributeChecker::visit (AST::TraitImpl &impl)
 {
   for (auto &attr : impl.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, impl);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
+
   AST::DefaultASTVisitor::visit (impl);
 }
 
@@ -1176,10 +720,7 @@ void
 AttributeChecker::visit (AST::ExternBlock &block)
 {
   for (auto &attr : block.get_outer_attrs ())
-    {
-      check_valid_attribute_for_item (attr, block);
-      check_proc_macro_non_function (attr);
-    }
+    check_proc_macro_non_function (attr);
 }
 
 // rust-macro.h
diff --git a/gcc/rust/util/rust-attributes.h b/gcc/rust/util/rust-attributes.h
index 4bac820120c..3e5aea815e8 100644
--- a/gcc/rust/util/rust-attributes.h
+++ b/gcc/rust/util/rust-attributes.h
@@ -29,6 +29,7 @@ class Attributes
 {
 public:
   static bool is_known (const std::string &attribute_path);
+  static bool valid_outer_attribute (const std::string &attribute_path);
   static tl::optional<std::string>
   extract_string_literal (const AST::Attribute &attr);
 };
@@ -109,7 +110,6 @@ private:
 
   // rust-ast.h
   void visit (AST::Attribute &attribute) override;
-  void visit (AST::Crate &crate) override;
   void visit (AST::Token &tok) override;
   void visit (AST::DelimTokenTree &delim_tok_tree) override;
   void visit (AST::IdentifierExpr &ident_expr) override;
@@ -277,7 +277,7 @@ private:
 };
 
 tl::optional<BuiltinAttrDefinition>
-identify_builtin (const AST::Attribute &attribute);
+lookup_builtin (const AST::Attribute &attribute);
 
 } // namespace Analysis
 } // namespace Rust
diff --git a/gcc/testsuite/rust/compile/issue-4226.rs 
b/gcc/testsuite/rust/compile/issue-4226.rs
index ec49606cfac..e0763eca02d 100644
--- a/gcc/testsuite/rust/compile/issue-4226.rs
+++ b/gcc/testsuite/rust/compile/issue-4226.rs
@@ -1,6 +1,6 @@
 #![feature(no_core)]
 #![no_core]
 
+// { dg-error "valid forms for the attribute are ...doc.hidden.inline....... 
and ...doc ... string ..." "" { target *-*-* } .+1 }
 #[doc]
-// { dg-error "attribute must be of the form ...doc.hidden.inline....... or 
...doc = string.." "" { target *-*-* } .-1 }
-pub fn a(){}
+pub fn a() {}
-- 
2.50.1

Reply via email to