[COMMITTED 22/43] gccrs: Move builtin attribute checking to its own pass

arthur.cohen@embecosm.com arthur.cohen@embecosm.com
Tue Apr 14 23:18:43 GMT 2026


From: Pierre-Emmanuel Patry <pierre-emmanuel.patry@embecosm.com>

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 <pierre-emmanuel.patry@embecosm.com>
---
 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



More information about the Gcc-rust mailing list