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 ¤t : 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 ¤t : 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