From aff1c8aade712105701bba0fc22726cde2c99443 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Patry Date: Fri, 6 Feb 2026 14:21:29 +0100 Subject: [PATCH] gccrs: Collect feature gate error at parse time Some nightly features change the parser's behavior, it may accepts syntax that should be rejected when the feature is missing. But the complete list of enabled features can only be found once the parsing is complete. We should therefore not emit any error at parse time and instead collect a potential error and emit it later during the feature gating step. gcc/rust/ChangeLog: * checks/errors/feature/rust-feature-gate.cc (FeatureGate::check): Check all parse time errors. * checks/errors/feature/rust-feature-gate.h: Update function prototype with parse time errors. * parse/rust-parse-impl-attribute.hxx: Collect potential gating error with non literal attribute values. Remove error emission. * parse/rust-parse.h: Add a function to gather potential feature gating errors as well as a getter for collected errors. * rust-session-manager.cc (Session::compile_crate): Retrieve potential feature gating errors and check them later during the feature gating step. * util/rust-attributes.cc (check_export_name_attribute): Change attribute checking error emission to prevent errors with macro inputs. gcc/testsuite/ChangeLog: * rust/compile/doc_macro.rs: Enable feature to use a macro within an attribute input. * rust/compile/parse_time_feature_gate.rs: New test. Signed-off-by: Pierre-Emmanuel Patry --- .../errors/feature/rust-feature-gate.cc | 6 +++- .../checks/errors/feature/rust-feature-gate.h | 4 ++- gcc/rust/parse/rust-parse-impl-attribute.hxx | 31 ++++++++--------- gcc/rust/parse/rust-parse.h | 20 +++++++++++ gcc/rust/rust-session-manager.cc | 3 +- gcc/rust/util/rust-attributes.cc | 33 +++++++++++++++++-- gcc/testsuite/rust/compile/doc_macro.rs | 2 +- .../rust/compile/parse_time_feature_gate.rs | 6 ++++ 8 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 gcc/testsuite/rust/compile/parse_time_feature_gate.rs diff --git a/gcc/rust/checks/errors/feature/rust-feature-gate.cc b/gcc/rust/checks/errors/feature/rust-feature-gate.cc index 515b753a43b..2c0ee08e4dd 100644 --- a/gcc/rust/checks/errors/feature/rust-feature-gate.cc +++ b/gcc/rust/checks/errors/feature/rust-feature-gate.cc @@ -27,8 +27,12 @@ namespace Rust { void -FeatureGate::check (AST::Crate &crate) +FeatureGate::check ( + AST::Crate &crate, + std::vector> &parsing_feature_gate_errors) { + for (auto &pair : parsing_feature_gate_errors) + gate (pair.first, pair.second.locus, pair.second.message); visit (crate); } diff --git a/gcc/rust/checks/errors/feature/rust-feature-gate.h b/gcc/rust/checks/errors/feature/rust-feature-gate.h index 120808339f0..33cb2758ae8 100644 --- a/gcc/rust/checks/errors/feature/rust-feature-gate.h +++ b/gcc/rust/checks/errors/feature/rust-feature-gate.h @@ -32,7 +32,9 @@ public: using AST::DefaultASTVisitor::visit; - void check (AST::Crate &crate); + void check ( + AST::Crate &crate, + std::vector> &parsing_feature_gate_errors); void visit (AST::Crate &crate) override; void visit (AST::LifetimeParam &lifetime_param) override; diff --git a/gcc/rust/parse/rust-parse-impl-attribute.hxx b/gcc/rust/parse/rust-parse-impl-attribute.hxx index 85fab52c997..5c7bd99bf3e 100644 --- a/gcc/rust/parse/rust-parse-impl-attribute.hxx +++ b/gcc/rust/parse/rust-parse-impl-attribute.hxx @@ -293,6 +293,16 @@ Parser::parse_attr_input () t = lexer.peek_token (); + /* Ensure token is a "literal expression" (literally only a literal + * token of any type) */ + if (!t->is_literal ()) + { + Error error ( + t->get_locus (), + "arbitrary expressions in key-value attributes are unstable"); + collect_potential_gating_error ( + Feature::Name::EXTENDED_KEY_VALUE_ATTRIBUTES, std::move (error)); + } // attempt to parse macro // TODO: macros may/may not be allowed in attributes // this is needed for "#[doc = include_str!(...)]" @@ -308,20 +318,6 @@ Parser::parse_attr_input () new AST::AttrInputMacro (std::move (invoke))); } - /* Ensure token is a "literal expression" (literally only a literal - * token of any type) */ - if (!t->is_literal ()) - { - Error error ( - t->get_locus (), - "unknown token %qs in attribute body - literal expected", - t->get_token_description ()); - add_error (std::move (error)); - - skip_after_end_attribute (); - return Parse::Error::AttrInput::make_malformed (); - } - AST::Literal::LitType lit_type = AST::Literal::STRING; // Crappy mapping of token type to literal type switch (t->get_id ()) @@ -345,9 +341,14 @@ Parser::parse_attr_input () lit_type = AST::Literal::RAW_STRING; break; case STRING_LITERAL: - default: lit_type = AST::Literal::STRING; break; // TODO: raw string? don't eliminate it from lexer? + default: + rust_sorry_at (t->get_locus (), + "Unsupported attribute input, only literals and " + "macros are supported for now"); + skip_after_end_attribute (); + return Parse::Error::AttrInput::make_malformed (); } // create actual LiteralExpr diff --git a/gcc/rust/parse/rust-parse.h b/gcc/rust/parse/rust-parse.h index 7d632391c7a..07d08333b7f 100644 --- a/gcc/rust/parse/rust-parse.h +++ b/gcc/rust/parse/rust-parse.h @@ -24,6 +24,7 @@ along with GCC; see the file COPYING3. If not see #include "rust-diagnostics.h" #include "rust-parse-error.h" #include "rust-parse-utils.h" +#include "rust-feature.h" #include "expected.h" @@ -852,6 +853,17 @@ private: void add_error (Error error) { error_table.push_back (std::move (error)); } + // We don't know the crate's valid feature set since we may not have parsed + // all feature declaration attributes yet, some features are not available and + // we can't decide at parse time whether we should reject the syntax. + // + // To fix this we collect the feature gating errors now and will emit the + // errors later. + void collect_potential_gating_error (Feature::Name feature, Error error) + { + gating_errors.emplace_back (feature, error); + } + public: // Construct parser with specified "managed" token source. Parser (ManagedTokenSource &tokenSource) : lexer (tokenSource) {} @@ -874,6 +886,12 @@ public: // Get a reference to the list of errors encountered std::vector &get_errors () { return error_table; } + std::vector> & + get_potential_feature_gate_errors () + { + return gating_errors; + } + const ManagedTokenSource &get_token_source () const { return lexer; } const_TokenPtr peek_current_token () { return lexer.peek_token (0); } @@ -884,6 +902,8 @@ private: ManagedTokenSource &lexer; // The error list. std::vector error_table; + + std::vector> gating_errors; // The names of inline modules while parsing. std::vector inline_module_stack; diff --git a/gcc/rust/rust-session-manager.cc b/gcc/rust/rust-session-manager.cc index cf3237ccd71..a87f0078047 100644 --- a/gcc/rust/rust-session-manager.cc +++ b/gcc/rust/rust-session-manager.cc @@ -601,6 +601,7 @@ Session::compile_crate (const char *filename) // generate crate from parser std::unique_ptr ast_crate = parser.parse_crate (); + auto &feature_gate_errors = parser.get_potential_feature_gate_errors (); // handle crate name handle_crate_name (filename, *ast_crate.get ()); @@ -712,7 +713,7 @@ Session::compile_crate (const char *filename) if (last_step == CompileOptions::CompileStep::FeatureGating) return; - FeatureGate (parsed_crate_features).check (parsed_crate); + FeatureGate (parsed_crate_features).check (parsed_crate, feature_gate_errors); if (last_step == CompileOptions::CompileStep::NameResolution) return; diff --git a/gcc/rust/util/rust-attributes.cc b/gcc/rust/util/rust-attributes.cc index af6f5fdac08..b3d60791432 100644 --- a/gcc/rust/util/rust-attributes.cc +++ b/gcc/rust/util/rust-attributes.cc @@ -498,11 +498,38 @@ check_export_name_attribute (const AST::Attribute &attribute) return; } - if (!Attributes::extract_string_literal (attribute)) + // We don't support the whole extended_key_value_attributes feature, we only + // support a subset for macros. We need to emit an error message when the + // attribute does not contain a macro or a string literal. + if (attribute.has_attr_input ()) { - rust_error_at (attribute.get_locus (), - "attribute must be a string literal"); + auto &attr_input = attribute.get_attr_input (); + switch (attr_input.get_attr_input_type ()) + { + case AST::AttrInput::AttrInputType::LITERAL: + { + auto &literal_expr + = static_cast (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; + } + break; + } + case AST::AttrInput::AttrInputType::MACRO: + return; + default: + break; + } } + rust_error_at (attribute.get_locus (), "attribute must be a string literal"); } static void diff --git a/gcc/testsuite/rust/compile/doc_macro.rs b/gcc/testsuite/rust/compile/doc_macro.rs index f71bd7ca04c..179455ddf95 100644 --- a/gcc/testsuite/rust/compile/doc_macro.rs +++ b/gcc/testsuite/rust/compile/doc_macro.rs @@ -1,4 +1,4 @@ #![feature(no_core)] #![no_core] - +#![feature(extended_key_value_attributes)] #![doc = concat!("AB")] diff --git a/gcc/testsuite/rust/compile/parse_time_feature_gate.rs b/gcc/testsuite/rust/compile/parse_time_feature_gate.rs new file mode 100644 index 00000000000..238b2cfd5c7 --- /dev/null +++ b/gcc/testsuite/rust/compile/parse_time_feature_gate.rs @@ -0,0 +1,6 @@ +#![feature(no_core)] +#![no_core] + +// { dg-error "arbitrary expressions in key-value attributes are unstable" "" { target *-*-* } .+1 } +#[export_name = concat!(stringify!(non), stringify!(literal))] +pub extern "C" fn attribute_test_function() {}