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

Feature collection should happen before expansion because some feature
gating happens with macros. This commit does no move the feature
collection before the expansion pass, it simply split the collection part
from the gating part.

gcc/rust/ChangeLog:

        * Make-lang.in: Add new feature collector file.
        * checks/errors/feature/rust-feature-gate.cc (FeatureGate::visit):
        Remove feature collection from gating visitor.
        (FeatureGate::gate): Use features from the crate feature entity instead
        of old class members.
        * checks/errors/feature/rust-feature-gate.h: Get features from a
        separate CrateFeatures struct instead of keeping them in the class.
        * rust-session-manager.cc (Session::compile_crate): Collect features
        before gating them.
        * util/rust-attribute-values.h: Add feature attribute value.
        * checks/errors/feature/rust-feature-collector.cc: New file.
        * checks/errors/feature/rust-feature-collector.h: New file.

Signed-off-by: Pierre-Emmanuel Patry <[email protected]>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.


Commit on github: 
https://github.com/Rust-GCC/gccrs/commit/6234365a3b4654ea60ba4bf59d0b5b8071c55f2c

The commit has been mentioned in the following pull-request(s):
 - https://github.com/Rust-GCC/gccrs/pull/4425

 gcc/rust/Make-lang.in                         |   1 +
 .../errors/feature/rust-feature-collector.cc  | 117 ++++++++++++++++++
 .../errors/feature/rust-feature-collector.h   |  62 ++++++++++
 .../errors/feature/rust-feature-gate.cc       |  59 +--------
 .../checks/errors/feature/rust-feature-gate.h |   6 +-
 gcc/rust/rust-session-manager.cc              |   5 +-
 gcc/rust/util/rust-attribute-values.h         |   1 +
 7 files changed, 191 insertions(+), 60 deletions(-)
 create mode 100644 gcc/rust/checks/errors/feature/rust-feature-collector.cc
 create mode 100644 gcc/rust/checks/errors/feature/rust-feature-collector.h

diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in
index 5c8f0d902..b5d837f46 100644
--- a/gcc/rust/Make-lang.in
+++ b/gcc/rust/Make-lang.in
@@ -229,6 +229,7 @@ GRS_OBJS = \
     rust/rust-builtins.o \
     rust/rust-feature.o \
     rust/rust-feature-gate.o \
+    rust/rust-feature-collector.o \
     rust/rust-ast-validation.o \
     rust/rust-dir-owner.o \
     rust/rust-unicode.o \
diff --git a/gcc/rust/checks/errors/feature/rust-feature-collector.cc 
b/gcc/rust/checks/errors/feature/rust-feature-collector.cc
new file mode 100644
index 000000000..6a3a14792
--- /dev/null
+++ b/gcc/rust/checks/errors/feature/rust-feature-collector.cc
@@ -0,0 +1,117 @@
+// 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-feature-collector.h"
+#include "rust-attribute-values.h"
+
+namespace Rust {
+namespace Features {
+
+FeatureCollector::FeatureCollector () : features 
(CrateFeatures{UNKNOWN_NODEID})
+{}
+
+CrateFeatures
+FeatureCollector::collect (AST::Crate &crate)
+{
+  features.valid_lang_features.clear ();
+  features.valid_lib_features.clear ();
+  features.crate_id = crate.get_node_id ();
+
+  visit (crate);
+
+  return features;
+}
+
+namespace {
+bool
+is_feature_attribute (const AST::Attribute &attribute)
+{
+  return Values::Attributes::FEATURE == attribute.get_path ().as_string ();
+}
+
+// check for empty feature, such as `#![feature], this is an error
+bool
+is_valid_feature_attribute (const AST::Attribute &attribute)
+{
+  return !attribute.empty_input ();
+}
+} // namespace
+
+void
+FeatureCollector::add_features_from_token_tree (
+  const AST::DelimTokenTree &delim_ttree)
+{
+  std::unique_ptr<AST::AttrInputMetaItemContainer> meta_item (
+    delim_ttree.parse_to_meta_item ());
+  add_features_from_meta_item_container (*meta_item);
+}
+
+void
+FeatureCollector::add_features_from_meta_item_container (
+  const AST::AttrInputMetaItemContainer &meta_item_container)
+{
+  for (const auto &item : meta_item_container.get_items ())
+    {
+      const auto &name_str = item->as_string ();
+
+      // TODO: detect duplicates
+      if (auto tname = Feature::as_name (name_str))
+       features.valid_lang_features.insert (*tname);
+      else
+       features.valid_lib_features.emplace (name_str, item->get_locus ());
+    }
+}
+
+void
+FeatureCollector::identify_feature (const AST::Attribute &attribute)
+{
+  if (is_feature_attribute (attribute))
+    {
+      if (!is_valid_feature_attribute (attribute))
+       {
+         rust_error_at (attribute.get_locus (), ErrorCode::E0556,
+                        "malformed %<feature%> attribute input");
+         return;
+       }
+      const auto &attr_input = attribute.get_attr_input ();
+      auto type = attr_input.get_attr_input_type ();
+      if (type == AST::AttrInput::AttrInputType::TOKEN_TREE)
+       {
+         const auto &delim_ttree = static_cast<const AST::DelimTokenTree &> (
+           attribute.get_attr_input ());
+         add_features_from_token_tree (delim_ttree);
+       }
+      else if (type == AST::AttrInput::AttrInputType::META_ITEM)
+       {
+         // We can find a meta item in #[cfg(toto),feature(xxxx)]
+         const auto &meta_item_container
+           = static_cast<const AST::AttrInputMetaItemContainer &> (attr_input);
+         add_features_from_meta_item_container (meta_item_container);
+       }
+    }
+}
+
+void
+FeatureCollector::visit (AST::Crate &crate)
+{
+  for (const auto &attribute : crate.inner_attrs)
+    identify_feature (attribute);
+}
+
+} // namespace Features
+} // namespace Rust
diff --git a/gcc/rust/checks/errors/feature/rust-feature-collector.h 
b/gcc/rust/checks/errors/feature/rust-feature-collector.h
new file mode 100644
index 000000000..79fa34d27
--- /dev/null
+++ b/gcc/rust/checks/errors/feature/rust-feature-collector.h
@@ -0,0 +1,62 @@
+// 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_FEATURE_COLLECTOR_H
+#define RUST_FEATURE_COLLECTOR_H
+
+#include "rust-feature.h"
+#include "rust-ast-visitor.h"
+
+namespace Rust {
+namespace Features {
+
+/** Helper structure gathering all features enabled within a given crate
+ * using the #![feature(XXXXX)] syntax.
+ **/
+struct CrateFeatures
+{
+  // The node id identifying the crate those features belong to.
+  NodeId crate_id;
+  // Language features that have been declared within the crate.
+  std::set<Feature::Name> valid_lang_features;
+  // Library features that have been declared within the crate.
+  std::map<std::string, location_t> valid_lib_features;
+
+  CrateFeatures (NodeId crate_id) : crate_id (crate_id) {}
+};
+
+class FeatureCollector : public AST::DefaultASTVisitor
+{
+public:
+  FeatureCollector ();
+
+  CrateFeatures collect (AST::Crate &crate);
+
+private:
+  CrateFeatures features;
+
+  using AST::DefaultASTVisitor::visit;
+  void visit (AST::Crate &crate) override;
+  void identify_feature (const AST::Attribute &attribute);
+  void add_features_from_token_tree (const AST::DelimTokenTree &delim_ttree);
+  void add_features_from_meta_item_container (
+    const AST::AttrInputMetaItemContainer &meta_item_container);
+};
+} // namespace Features
+} // namespace Rust
+
+#endif /* ! RUST_FEATURE_COLLECTOR_H */
diff --git a/gcc/rust/checks/errors/feature/rust-feature-gate.cc 
b/gcc/rust/checks/errors/feature/rust-feature-gate.cc
index a346e1a3d..515b753a4 100644
--- a/gcc/rust/checks/errors/feature/rust-feature-gate.cc
+++ b/gcc/rust/checks/errors/feature/rust-feature-gate.cc
@@ -19,6 +19,7 @@
 #include "rust-feature-gate.h"
 #include "rust-abi.h"
 #include "rust-attribute-values.h"
+#include "rust-attributes.h"
 #include "rust-ast-visitor.h"
 #include "rust-feature.h"
 #include "rust-ast-full.h"
@@ -34,63 +35,9 @@ FeatureGate::check (AST::Crate &crate)
 void
 FeatureGate::visit (AST::Crate &crate)
 {
-  valid_lang_features.clear ();
-  valid_lib_features.clear ();
-
-  // avoid clearing defined features (?)
-
-  for (const auto &attr : crate.inner_attrs)
-    {
-      if (attr.get_path ().as_string () == "feature")
-       {
-         // check for empty feature, such as `#![feature], this is an error
-         if (attr.empty_input ())
-           {
-             rust_error_at (attr.get_locus (), ErrorCode::E0556,
-                            "malformed %<feature%> attribute input");
-             continue;
-           }
-         const auto &attr_input = attr.get_attr_input ();
-         auto type = attr_input.get_attr_input_type ();
-         if (type == AST::AttrInput::AttrInputType::TOKEN_TREE)
-           {
-             const auto &option = static_cast<const AST::DelimTokenTree &> (
-               attr.get_attr_input ());
-             std::unique_ptr<AST::AttrInputMetaItemContainer> meta_item (
-               option.parse_to_meta_item ());
-             for (const auto &item : meta_item->get_items ())
-               {
-                 const auto &name_str = item->as_string ();
-
-                 // TODO: detect duplicates
-                 if (auto tname = Feature::as_name (name_str))
-                   valid_lang_features.insert (tname.value ());
-                 else
-                   valid_lib_features.emplace (name_str, item->get_locus ());
-               }
-           }
-         else if (type == AST::AttrInput::AttrInputType::META_ITEM)
-           {
-             const auto &meta_item
-               = static_cast<const AST::AttrInputMetaItemContainer &> (
-                 attr_input);
-             for (const auto &item : meta_item.get_items ())
-               {
-                 const auto &name_str = item->as_string ();
-
-                 // TODO: detect duplicates
-                 if (auto tname = Feature::as_name (name_str))
-                   valid_lang_features.insert (tname.value ());
-                 else
-                   valid_lib_features.emplace (name_str, item->get_locus ());
-               }
-           }
-       }
-    }
-
   AST::DefaultASTVisitor::visit (crate);
 
-  for (auto &ent : valid_lib_features)
+  for (auto &ent : features.valid_lib_features)
     {
       const std::string &feature = ent.first;
       location_t locus = ent.second;
@@ -115,7 +62,7 @@ void
 FeatureGate::gate (Feature::Name name, location_t loc,
                   const std::string &error_msg)
 {
-  if (!valid_lang_features.count (name))
+  if (!features.valid_lang_features.count (name))
     {
       auto &feature = Feature::lookup (name);
       if (auto issue = feature.issue ())
diff --git a/gcc/rust/checks/errors/feature/rust-feature-gate.h 
b/gcc/rust/checks/errors/feature/rust-feature-gate.h
index 60b8645f8..120808339 100644
--- a/gcc/rust/checks/errors/feature/rust-feature-gate.h
+++ b/gcc/rust/checks/errors/feature/rust-feature-gate.h
@@ -21,13 +21,14 @@
 
 #include "rust-ast-visitor.h"
 #include "rust-feature.h"
+#include "rust-feature-collector.h"
 
 namespace Rust {
 
 class FeatureGate : public AST::DefaultASTVisitor
 {
 public:
-  FeatureGate () {}
+  FeatureGate (Features::CrateFeatures &features) : features (features) {}
 
   using AST::DefaultASTVisitor::visit;
 
@@ -61,8 +62,7 @@ private:
   check_lang_item_attribute (const std::vector<AST::Attribute> &attributes);
   void note_stability_attribute (const std::vector<AST::Attribute> 
&attributes);
 
-  std::set<Feature::Name> valid_lang_features;
-  std::map<std::string, location_t> valid_lib_features;
+  Features::CrateFeatures &features;
 
   enum class Stability
   {
diff --git a/gcc/rust/rust-session-manager.cc b/gcc/rust/rust-session-manager.cc
index 4a2f5a5ad..67adb103c 100644
--- a/gcc/rust/rust-session-manager.cc
+++ b/gcc/rust/rust-session-manager.cc
@@ -34,6 +34,7 @@
 #include "rust-hir-type-check.h"
 #include "rust-privacy-check.h"
 #include "rust-const-checker.h"
+#include "rust-feature-collector.h"
 #include "rust-feature-gate.h"
 #include "rust-compile.h"
 #include "rust-cfg-parser.h"
@@ -701,7 +702,9 @@ Session::compile_crate (const char *filename)
   // feature gating
   if (last_step == CompileOptions::CompileStep::FeatureGating)
     return;
-  FeatureGate ().check (parsed_crate);
+  auto parsed_crate_features
+    = Features::FeatureCollector{}.collect (parsed_crate);
+  FeatureGate (parsed_crate_features).check (parsed_crate);
 
   if (last_step == CompileOptions::CompileStep::NameResolution)
     return;
diff --git a/gcc/rust/util/rust-attribute-values.h 
b/gcc/rust/util/rust-attribute-values.h
index 85d3bac92..34e938025 100644
--- a/gcc/rust/util/rust-attribute-values.h
+++ b/gcc/rust/util/rust-attribute-values.h
@@ -52,6 +52,7 @@ public:
   static constexpr auto &PROC_MACRO_ATTRIBUTE = "proc_macro_attribute";
 
   static constexpr auto &TARGET_FEATURE = "target_feature";
+  static constexpr auto &FEATURE = "feature";
   // From now on, these are reserved by the compiler and gated through
   // #![feature(rustc_attrs)]
   static constexpr auto &RUSTC_DEPRECATED = "rustc_deprecated";
-- 
2.53.0

Reply via email to