diff options
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_ast_passes/src/feature_gate.rs | 14 | ||||
| -rw-r--r-- | compiler/rustc_feature/src/builtin_attrs.rs | 212 | ||||
| -rw-r--r-- | compiler/rustc_feature/src/lib.rs | 13 | ||||
| -rw-r--r-- | compiler/rustc_lint/messages.ftl | 3 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/builtin.rs | 52 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lib.rs | 1 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lints.rs | 26 | ||||
| -rw-r--r-- | compiler/rustc_parse_format/src/lib.rs | 299 | ||||
| -rw-r--r-- | compiler/rustc_parse_format/src/tests.rs | 42 | ||||
| -rw-r--r-- | compiler/rustc_session/src/parse.rs | 9 | ||||
| -rw-r--r-- | compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs | 41 | ||||
| -rw-r--r-- | compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs | 87 |
13 files changed, 368 insertions, 433 deletions
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 3682d25d341..1ec56868f37 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -36,6 +36,16 @@ macro_rules! gate_alt { feature_err(&$visitor.sess, $name, $span, $explain).emit(); } }}; + ($visitor:expr, $has_feature:expr, $name:expr, $span:expr, $explain:expr, $notes: expr) => {{ + if !$has_feature && !$span.allows_unstable($name) { + #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable + let mut diag = feature_err(&$visitor.sess, $name, $span, $explain); + for note in $notes { + diag.note(*note); + } + diag.emit(); + } + }}; } /// The case involving a multispan. @@ -154,11 +164,11 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { let attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)); // Check feature gates for built-in attributes. if let Some(BuiltinAttribute { - gate: AttributeGate::Gated(_, name, descr, has_feature), + gate: AttributeGate::Gated { feature, message, check, notes, .. }, .. }) = attr_info { - gate_alt!(self, has_feature(self.features), *name, attr.span, *descr); + gate_alt!(self, check(self.features), *feature, attr.span, *message, *notes); } // Check unstable flavors of the `#[doc]` attribute. if attr.has_name(sym::doc) { diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index c117e0fcf7c..73a21789c5d 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_span::edition::Edition; use rustc_span::{Symbol, sym}; -use crate::{Features, Stability}; +use crate::Features; type GateFn = fn(&Features) -> bool; @@ -94,34 +94,23 @@ pub enum AttributeSafety { Unsafe { unsafe_since: Option<Edition> }, } -#[derive(Clone, Copy)] +#[derive(Clone, Debug, Copy)] pub enum AttributeGate { - /// Is gated by a given feature gate, reason - /// and function to check if enabled - Gated(Stability, Symbol, &'static str, fn(&Features) -> bool), - + /// A gated attribute which requires a feature gate to be enabled. + Gated { + /// The feature gate, for example `#![feature(rustc_attrs)]` for rustc_* attributes. + feature: Symbol, + /// The error message displayed when an attempt is made to use the attribute without its feature gate. + message: &'static str, + /// Check function to be called during the `PostExpansionVisitor` pass. + check: fn(&Features) -> bool, + /// Notes to be displayed when an attempt is made to use the attribute without its feature gate. + notes: &'static [&'static str], + }, /// Ungated attribute, can be used on all release channels Ungated, } -// fn() is not Debug -impl std::fmt::Debug for AttributeGate { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Self::Gated(ref stab, name, expl, _) => { - write!(fmt, "Gated({stab:?}, {name}, {expl})") - } - Self::Ungated => write!(fmt, "Ungated"), - } - } -} - -impl AttributeGate { - fn is_deprecated(&self) -> bool { - matches!(*self, Self::Gated(Stability::Deprecated(_, _), ..)) - } -} - /// A template that the attribute input must match. /// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now. #[derive(Clone, Copy, Default)] @@ -247,7 +236,7 @@ macro_rules! ungated { } macro_rules! gated { - (unsafe $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $gate:ident, $msg:expr $(,)?) => { + (unsafe $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $gate:ident, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, encode_cross_crate: $encode_cross_crate, @@ -255,10 +244,15 @@ macro_rules! gated { safety: AttributeSafety::Unsafe { unsafe_since: None }, template: $tpl, duplicates: $duplicates, - gate: Gated(Stability::Unstable, sym::$gate, $msg, Features::$gate), + gate: Gated { + feature: sym::$gate, + message: $message, + check: Features::$gate, + notes: &[], + }, } }; - (unsafe $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $msg:expr $(,)?) => { + (unsafe $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, encode_cross_crate: $encode_cross_crate, @@ -266,10 +260,15 @@ macro_rules! gated { safety: AttributeSafety::Unsafe { unsafe_since: None }, template: $tpl, duplicates: $duplicates, - gate: Gated(Stability::Unstable, sym::$attr, $msg, Features::$attr), + gate: Gated { + feature: sym::$attr, + message: $message, + check: Features::$attr, + notes: &[], + }, } }; - ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $gate:ident, $msg:expr $(,)?) => { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $gate:ident, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, encode_cross_crate: $encode_cross_crate, @@ -277,10 +276,15 @@ macro_rules! gated { safety: AttributeSafety::Normal, template: $tpl, duplicates: $duplicates, - gate: Gated(Stability::Unstable, sym::$gate, $msg, Features::$gate), + gate: Gated { + feature: sym::$gate, + message: $message, + check: Features::$gate, + notes: &[], + }, } }; - ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $msg:expr $(,)?) => { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, encode_cross_crate: $encode_cross_crate, @@ -288,7 +292,12 @@ macro_rules! gated { safety: AttributeSafety::Normal, template: $tpl, duplicates: $duplicates, - gate: Gated(Stability::Unstable, sym::$attr, $msg, Features::$attr), + gate: Gated { + feature: sym::$attr, + message: $message, + check: Features::$attr, + notes: &[], + }, } }; } @@ -304,12 +313,11 @@ macro_rules! rustc_attr { concat!( "the `#[", stringify!($attr), - "]` attribute is just used for rustc unit tests \ - and will never be stable", + "]` attribute is used for rustc unit tests" ), ) }; - ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $msg:expr $(,)?) => { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $($notes:expr),* $(,)?) => { BuiltinAttribute { name: sym::$attr, encode_cross_crate: $encode_cross_crate, @@ -317,7 +325,17 @@ macro_rules! rustc_attr { safety: AttributeSafety::Normal, template: $tpl, duplicates: $duplicates, - gate: Gated(Stability::Unstable, sym::rustc_attrs, $msg, Features::rustc_attrs), + gate: Gated { + feature: sym::rustc_attrs, + message: "use of an internal attribute", + check: Features::rustc_attrs, + notes: &[ + concat!("the `#[", + stringify!($attr), + "]` attribute is an internal implementation detail that will never be stable"), + $($notes),* + ] + }, } }; } @@ -328,9 +346,6 @@ macro_rules! experimental { }; } -const IMPL_DETAIL: &str = "internal implementation detail"; -const INTERNAL_UNSTABLE: &str = "this is an internal attribute that will never be stable"; - #[derive(PartialEq)] pub enum EncodeCrossCrate { Yes, @@ -668,7 +683,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_deprecated_safe_2024, Normal, template!(List: r#"audit_that = "...""#), ErrorFollowing, EncodeCrossCrate::Yes, - "rustc_deprecated_safe_2024 is supposed to be used in libstd only", + "`#[rustc_deprecated_safe_2024]` is used to declare functions unsafe across the edition 2024 boundary", ), rustc_attr!( rustc_pub_transparent, Normal, template!(Word), @@ -695,7 +710,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ErrorFollowing, EncodeCrossCrate::No, "`rustc_never_type_options` is used to experiment with never type fallback and work on \ - never type stabilization, and will never be stable" + never type stabilization" ), // ========================================================================== @@ -704,23 +719,23 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_allocator, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, IMPL_DETAIL + EncodeCrossCrate::No, ), rustc_attr!( rustc_nounwind, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, IMPL_DETAIL + EncodeCrossCrate::No, ), rustc_attr!( rustc_reallocator, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, IMPL_DETAIL + EncodeCrossCrate::No, ), rustc_attr!( rustc_deallocator, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, IMPL_DETAIL + EncodeCrossCrate::No, ), rustc_attr!( rustc_allocator_zeroed, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, IMPL_DETAIL + EncodeCrossCrate::No, ), gated!( default_lib_allocator, Normal, template!(Word), WarnFollowing, @@ -762,7 +777,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), rustc_attr!( rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, INTERNAL_UNSTABLE + EncodeCrossCrate::No, ), // ========================================================================== @@ -772,11 +787,11 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_builtin_macro, Normal, template!(Word, List: "name, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing, - EncodeCrossCrate::Yes, IMPL_DETAIL + EncodeCrossCrate::Yes, ), rustc_attr!( rustc_proc_macro_decls, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, INTERNAL_UNSTABLE + EncodeCrossCrate::No, ), rustc_attr!( rustc_macro_transparency, Normal, @@ -786,7 +801,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_autodiff, Normal, template!(Word, List: r#""...""#), DuplicatesOk, - EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + EncodeCrossCrate::Yes, ), // Traces that are left when `cfg` and `cfg_attr` attributes are expanded. // The attributes are not gated, to avoid stability errors, but they cannot be used in stable @@ -812,54 +827,53 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ NameValueStr: "message" ), ErrorFollowing, EncodeCrossCrate::Yes, - INTERNAL_UNSTABLE + "see `#[diagnostic::on_unimplemented]` for the stable equivalent of this attribute" ), rustc_attr!( rustc_confusables, Normal, template!(List: r#""name1", "name2", ..."#), ErrorFollowing, EncodeCrossCrate::Yes, - INTERNAL_UNSTABLE, ), // Enumerates "identity-like" conversion methods to suggest on type mismatch. rustc_attr!( rustc_conversion_suggestion, Normal, template!(Word), - WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + WarnFollowing, EncodeCrossCrate::Yes, ), // Prevents field reads in the marked trait or method to be considered // during dead code analysis. rustc_attr!( rustc_trivial_field_reads, Normal, template!(Word), - WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + WarnFollowing, EncodeCrossCrate::Yes, ), // Used by the `rustc::potential_query_instability` lint to warn methods which // might not be stable during incremental compilation. rustc_attr!( rustc_lint_query_instability, Normal, template!(Word), - WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + WarnFollowing, EncodeCrossCrate::Yes, ), // Used by the `rustc::untracked_query_information` lint to warn methods which // might not be stable during incremental compilation. rustc_attr!( rustc_lint_untracked_query_information, Normal, template!(Word), - WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + WarnFollowing, EncodeCrossCrate::Yes, ), // Used by the `rustc::diagnostic_outside_of_impl` lints to assist in changes to diagnostic // APIs. Any function with this attribute will be checked by that lint. rustc_attr!( rustc_lint_diagnostics, Normal, template!(Word), - WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + WarnFollowing, EncodeCrossCrate::Yes, ), // Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions` // types (as well as any others in future). rustc_attr!( rustc_lint_opt_ty, Normal, template!(Word), - WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + WarnFollowing, EncodeCrossCrate::Yes, ), // Used by the `rustc::bad_opt_access` lint on fields // types (as well as any others in future). rustc_attr!( rustc_lint_opt_deny_field_access, Normal, template!(List: "message"), - WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + WarnFollowing, EncodeCrossCrate::Yes, ), // ========================================================================== @@ -868,28 +882,30 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_promotable, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::No, IMPL_DETAIL), + EncodeCrossCrate::No, ), rustc_attr!( rustc_legacy_const_generics, Normal, template!(List: "N"), ErrorFollowing, - EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + EncodeCrossCrate::Yes, ), // Do not const-check this function's body. It will always get replaced during CTFE. rustc_attr!( rustc_do_not_const_check, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + EncodeCrossCrate::Yes, "`#[rustc_do_not_const_check]` skips const-check for this function's body", ), - // Ensure the argument to this function is &&str during const-check. rustc_attr!( rustc_const_panic_str, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::Yes, INTERNAL_UNSTABLE + EncodeCrossCrate::Yes, "`#[rustc_const_panic_str]` ensures the argument to this function is &&str during const-check", ), rustc_attr!( rustc_const_stable_indirect, Normal, - template!(Word), WarnFollowing, EncodeCrossCrate::No, IMPL_DETAIL, + template!(Word), + WarnFollowing, + EncodeCrossCrate::No, + "this is an internal implementation detail", ), rustc_attr!( rustc_intrinsic_const_stable_indirect, Normal, - template!(Word), WarnFollowing, EncodeCrossCrate::No, IMPL_DETAIL, + template!(Word), WarnFollowing, EncodeCrossCrate::No, "this is an internal implementation detail", ), gated!( rustc_allow_const_fn_unstable, Normal, @@ -905,21 +921,21 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_layout_scalar_valid_range_start, Normal, template!(List: "value"), ErrorFollowing, EncodeCrossCrate::Yes, "the `#[rustc_layout_scalar_valid_range_start]` attribute is just used to enable \ - niche optimizations in libcore and libstd and will never be stable", + niche optimizations in the standard library", ), rustc_attr!( rustc_layout_scalar_valid_range_end, Normal, template!(List: "value"), ErrorFollowing, EncodeCrossCrate::Yes, "the `#[rustc_layout_scalar_valid_range_end]` attribute is just used to enable \ - niche optimizations in libcore and libstd and will never be stable", + niche optimizations in the standard library", ), rustc_attr!( rustc_nonnull_optimization_guaranteed, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, "the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to document \ - guaranteed niche optimizations in libcore and libstd and will never be stable\n\ - (note that the compiler does not even check whether the type indeed is being non-null-optimized; \ - it is your responsibility to ensure that the attribute is only used on types that are optimized)", + guaranteed niche optimizations in the standard library", + "the compiler does not even check whether the type indeed is being non-null-optimized; \ + it is your responsibility to ensure that the attribute is only used on types that are optimized", ), // ========================================================================== @@ -932,17 +948,17 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_as_ptr, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, - "#[rustc_as_ptr] is used to mark functions returning pointers to their inner allocations." + "`#[rustc_as_ptr]` is used to mark functions returning pointers to their inner allocations." ), rustc_attr!( rustc_pass_by_value, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, - "#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference." + "`#[rustc_pass_by_value]` is used to mark types that must be passed by value instead of reference." ), rustc_attr!( rustc_never_returns_null_ptr, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, - "#[rustc_never_returns_null_ptr] is used to mark functions returning non-null pointers." + "`#[rustc_never_returns_null_ptr]` is used to mark functions returning non-null pointers." ), rustc_attr!( rustc_no_implicit_autorefs, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, @@ -950,15 +966,15 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), rustc_attr!( rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, EncodeCrossCrate::No, - "#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`." + "`#![rustc_coherence_is_core]` allows inherent methods on builtin types, only intended to be used in `core`." ), rustc_attr!( rustc_coinductive, AttributeType::Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, - "#![rustc_coinductive] changes a trait to be coinductive, allowing cycles in the trait solver." + "`#[rustc_coinductive]` changes a trait to be coinductive, allowing cycles in the trait solver." ), rustc_attr!( rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::No, - "#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl." + "`#[rustc_allow_incoherent_impl]` has to be added to all impl items of an incoherent inherent impl." ), rustc_attr!( rustc_preserve_ub_checks, AttributeType::CrateLevel, template!(Word), ErrorFollowing, EncodeCrossCrate::No, @@ -970,7 +986,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ template!(Word), ErrorFollowing, EncodeCrossCrate::No, - "#[rustc_deny_explicit_impl] enforces that a trait can have no user-provided impls" + "`#[rustc_deny_explicit_impl]` enforces that a trait can have no user-provided impls" ), rustc_attr!( rustc_do_not_implement_via_object, @@ -978,14 +994,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ template!(Word), ErrorFollowing, EncodeCrossCrate::No, - "#[rustc_do_not_implement_via_object] opts out of the automatic trait impl for trait objects \ + "`#[rustc_do_not_implement_via_object]` opts out of the automatic trait impl for trait objects \ (`impl Trait for dyn Trait`)" ), rustc_attr!( rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, - "#[rustc_has_incoherent_inherent_impls] allows the addition of incoherent inherent impls for \ - the given type by annotating all impl items with #[rustc_allow_incoherent_impl]." + "`#[rustc_has_incoherent_inherent_impls]` allows the addition of incoherent inherent impls for \ + the given type by annotating all impl items with `#[rustc_allow_incoherent_impl]`." ), BuiltinAttribute { @@ -996,12 +1012,13 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ safety: AttributeSafety::Normal, template: template!(NameValueStr: "name"), duplicates: ErrorFollowing, - gate: Gated( - Stability::Unstable, - sym::rustc_attrs, - "diagnostic items compiler internal support for linting", - Features::rustc_attrs, - ), + gate: Gated{ + feature: sym::rustc_attrs, + message: "use of an internal attribute", + check: Features::rustc_attrs, + notes: &["the `#[rustc_diagnostic_item]` attribute allows the compiler to reference types \ + from the standard library for diagnostic purposes"], + }, }, gated!( // Used in resolve: @@ -1015,14 +1032,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_inherit_overflow_checks, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, "the `#[rustc_inherit_overflow_checks]` attribute is just used to control \ - overflow checking behavior of several libcore functions that are inlined \ - across crates and will never be stable", + overflow checking behavior of several functions in the standard library that are inlined \ + across crates", ), rustc_attr!( rustc_reservation_impl, Normal, template!(NameValueStr: "reservation message"), ErrorFollowing, EncodeCrossCrate::Yes, "the `#[rustc_reservation_impl]` attribute is internally used \ - for reserving for `for<T> From<!> for T` impl" + for reserving `impl<T> From<!> for T` as part of the effort to stabilize `!`" ), rustc_attr!( rustc_test_marker, Normal, template!(NameValueStr: "name"), WarnFollowing, @@ -1053,12 +1070,13 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), ErrorFollowing, EncodeCrossCrate::No, "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ - definition of a trait, it's currently in experimental form and should be changed before \ - being exposed outside of the std" + definition of a trait. Its syntax and semantics are highly experimental and will be \ + subject to change before stabilization", ), rustc_attr!( rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing, - EncodeCrossCrate::Yes, r#"`rustc_doc_primitive` is a rustc internal attribute"#, + EncodeCrossCrate::Yes, "the `#[rustc_doc_primitive]` attribute is used by the standard library \ + to provide a way to generate documentation for primitive types", ), gated!( rustc_intrinsic, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, intrinsics, @@ -1066,11 +1084,11 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), rustc_attr!( rustc_no_mir_inline, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, - "#[rustc_no_mir_inline] prevents the MIR inliner from inlining a function while not affecting codegen" + "`#[rustc_no_mir_inline]` prevents the MIR inliner from inlining a function while not affecting codegen" ), rustc_attr!( rustc_force_inline, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing, EncodeCrossCrate::Yes, - "#[rustc_force_inline] forces a free function to be inlined" + "`#[rustc_force_inline]` forces a free function to be inlined" ), // ========================================================================== @@ -1209,10 +1227,6 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), ]; -pub fn deprecated_attributes() -> Vec<&'static BuiltinAttribute> { - BUILTIN_ATTRIBUTES.iter().filter(|attr| attr.gate.is_deprecated()).collect() -} - pub fn is_builtin_attr_name(name: Symbol) -> bool { BUILTIN_ATTRIBUTE_MAP.get(&name).is_some() } diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 25764755a8f..dbc0daa3d83 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -40,14 +40,6 @@ pub struct Feature { issue: Option<NonZero<u32>>, } -#[derive(Copy, Clone, Debug)] -pub enum Stability { - Unstable, - // First argument is tracking issue link; second argument is an optional - // help message, which defaults to "remove this attribute". - Deprecated(&'static str, Option<&'static str>), -} - #[derive(Clone, Copy, Debug, Hash)] pub enum UnstableFeatures { /// Disallow use of unstable features, as on beta/stable channels. @@ -144,9 +136,8 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option<NonZero<u pub use accepted::ACCEPTED_LANG_FEATURES; pub use builtin_attrs::{ AttributeDuplicates, AttributeGate, AttributeSafety, AttributeTemplate, AttributeType, - BUILTIN_ATTRIBUTE_MAP, BUILTIN_ATTRIBUTES, BuiltinAttribute, GatedCfg, deprecated_attributes, - encode_cross_crate, find_gated_cfg, is_builtin_attr_name, is_stable_diagnostic_attribute, - is_valid_for_get_attr, + BUILTIN_ATTRIBUTE_MAP, BUILTIN_ATTRIBUTES, BuiltinAttribute, GatedCfg, encode_cross_crate, + find_gated_cfg, is_builtin_attr_name, is_stable_diagnostic_attribute, is_valid_for_get_attr, }; pub use removed::REMOVED_LANG_FEATURES; pub use unstable::{ diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index fd2e2ba39ac..142069c338a 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -72,9 +72,6 @@ lint_builtin_const_no_mangle = const items should never be `#[no_mangle]` lint_builtin_decl_unsafe_fn = declaration of an `unsafe` function lint_builtin_decl_unsafe_method = declaration of an `unsafe` method -lint_builtin_deprecated_attr_link = use of deprecated attribute `{$name}`: {$reason}. See {$link} - .msg_suggestion = {$msg} - .default_suggestion = remove this attribute lint_builtin_deref_nullptr = dereferencing a null pointer .label = this code causes undefined behavior when executed diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 69e9f8e1b2c..dedea54f8e0 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -22,7 +22,7 @@ use rustc_ast::visit::{FnCtxt, FnKind}; use rustc_ast::{self as ast, *}; use rustc_ast_pretty::pprust::expr_to_string; use rustc_errors::{Applicability, LintDiagnostic}; -use rustc_feature::{AttributeGate, BuiltinAttribute, GateIssue, Stability, deprecated_attributes}; +use rustc_feature::GateIssue; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId}; @@ -48,8 +48,7 @@ use rustc_trait_selection::traits::{self}; use crate::errors::BuiltinEllipsisInclusiveRangePatterns; use crate::lints::{ - BuiltinAnonymousParams, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink, - BuiltinDeprecatedAttrLinkSuggestion, BuiltinDerefNullptr, BuiltinDoubleNegations, + BuiltinAnonymousParams, BuiltinConstNoMangle, BuiltinDerefNullptr, BuiltinDoubleNegations, BuiltinDoubleNegationsAddParens, BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures, BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, @@ -799,53 +798,6 @@ impl EarlyLintPass for AnonymousParameters { } } -/// Check for use of attributes which have been deprecated. -#[derive(Clone)] -pub struct DeprecatedAttr { - // This is not free to compute, so we want to keep it around, rather than - // compute it for every attribute. - depr_attrs: Vec<&'static BuiltinAttribute>, -} - -impl_lint_pass!(DeprecatedAttr => []); - -impl Default for DeprecatedAttr { - fn default() -> Self { - DeprecatedAttr { depr_attrs: deprecated_attributes() } - } -} - -impl EarlyLintPass for DeprecatedAttr { - fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) { - for BuiltinAttribute { name, gate, .. } in &self.depr_attrs { - if attr.ident().map(|ident| ident.name) == Some(*name) { - if let &AttributeGate::Gated( - Stability::Deprecated(link, suggestion), - name, - reason, - _, - ) = gate - { - let suggestion = match suggestion { - Some(msg) => { - BuiltinDeprecatedAttrLinkSuggestion::Msg { suggestion: attr.span, msg } - } - None => { - BuiltinDeprecatedAttrLinkSuggestion::Default { suggestion: attr.span } - } - }; - cx.emit_span_lint( - DEPRECATED, - attr.span, - BuiltinDeprecatedAttrLink { name, reason, link, suggestion }, - ); - } - return; - } - } - } -} - fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &[ast::Attribute]) { use rustc_ast::token::CommentKind; diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 72bfeaddbf1..df1e6f8eb1f 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -174,7 +174,6 @@ early_lint_methods!( AnonymousParameters: AnonymousParameters, EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), NonCamelCaseTypes: NonCamelCaseTypes, - DeprecatedAttr: DeprecatedAttr::default(), WhileTrue: WhileTrue, NonAsciiIdents: NonAsciiIdents, IncompleteInternalFeatures: IncompleteInternalFeatures, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 9d3c74a9a2b..f91ff9dc916 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -199,32 +199,6 @@ pub(crate) struct BuiltinAnonymousParams<'a> { pub ty_snip: &'a str, } -// FIXME(davidtwco) translatable deprecated attr -#[derive(LintDiagnostic)] -#[diag(lint_builtin_deprecated_attr_link)] -pub(crate) struct BuiltinDeprecatedAttrLink<'a> { - pub name: Symbol, - pub reason: &'a str, - pub link: &'a str, - #[subdiagnostic] - pub suggestion: BuiltinDeprecatedAttrLinkSuggestion<'a>, -} - -#[derive(Subdiagnostic)] -pub(crate) enum BuiltinDeprecatedAttrLinkSuggestion<'a> { - #[suggestion(lint_msg_suggestion, code = "", applicability = "machine-applicable")] - Msg { - #[primary_span] - suggestion: Span, - msg: &'a str, - }, - #[suggestion(lint_default_suggestion, code = "", applicability = "machine-applicable")] - Default { - #[primary_span] - suggestion: Span, - }, -} - #[derive(LintDiagnostic)] #[diag(lint_builtin_unused_doc_comment)] pub(crate) struct BuiltinUnusedDocComment<'a> { diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs index 9dd064aca66..42bd0f5d847 100644 --- a/compiler/rustc_parse_format/src/lib.rs +++ b/compiler/rustc_parse_format/src/lib.rs @@ -29,58 +29,45 @@ pub enum ParseMode { Format, /// An inline assembly template string for `asm!`. InlineAsm, + /// A format string for use in diagnostic attributes. + /// + /// Similar to `format_args!`, however only named ("captured") arguments + /// are allowed, and no format modifiers are permitted. + Diagnostic, } /// A piece is a portion of the format string which represents the next part /// to emit. These are emitted as a stream by the `Parser` class. #[derive(Clone, Debug, PartialEq)] -pub enum Piece<'a> { +pub enum Piece<'input> { /// A literal string which should directly be emitted - Lit(&'a str), + Lit(&'input str), /// This describes that formatting should process the next argument (as /// specified inside) for emission. - NextArgument(Box<Argument<'a>>), + NextArgument(Box<Argument<'input>>), } /// Representation of an argument specification. #[derive(Clone, Debug, PartialEq)] -pub struct Argument<'a> { +pub struct Argument<'input> { /// Where to find this argument - pub position: Position<'a>, + pub position: Position<'input>, /// The span of the position indicator. Includes any whitespace in implicit /// positions (`{ }`). pub position_span: Range<usize>, /// How to format the argument - pub format: FormatSpec<'a>, + pub format: FormatSpec<'input>, } -impl<'a> Argument<'a> { +impl<'input> Argument<'input> { pub fn is_identifier(&self) -> bool { - matches!(self.position, Position::ArgumentNamed(_)) - && matches!( - self.format, - FormatSpec { - fill: None, - fill_span: None, - align: AlignUnknown, - sign: None, - alternate: false, - zero_pad: false, - debug_hex: None, - precision: CountImplied, - precision_span: None, - width: CountImplied, - width_span: None, - ty: "", - ty_span: None, - }, - ) + matches!(self.position, Position::ArgumentNamed(_)) && self.format == FormatSpec::default() } } /// Specification for the formatting of an argument in the format string. -#[derive(Clone, Debug, PartialEq)] -pub struct FormatSpec<'a> { +#[derive(Clone, Debug, PartialEq, Default)] +pub struct FormatSpec<'input> { /// Optionally specified character to fill alignment with. pub fill: Option<char>, /// Span of the optionally specified fill character. @@ -96,30 +83,30 @@ pub struct FormatSpec<'a> { /// The `x` or `X` flag. (Only for `Debug`.) pub debug_hex: Option<DebugHex>, /// The integer precision to use. - pub precision: Count<'a>, + pub precision: Count<'input>, /// The span of the precision formatting flag (for diagnostics). pub precision_span: Option<Range<usize>>, /// The string width requested for the resulting format. - pub width: Count<'a>, + pub width: Count<'input>, /// The span of the width formatting flag (for diagnostics). pub width_span: Option<Range<usize>>, /// The descriptor string representing the name of the format desired for /// this argument, this can be empty or any number of characters, although /// it is required to be one word. - pub ty: &'a str, + pub ty: &'input str, /// The span of the descriptor string (for diagnostics). pub ty_span: Option<Range<usize>>, } /// Enum describing where an argument for a format can be located. #[derive(Clone, Debug, PartialEq)] -pub enum Position<'a> { +pub enum Position<'input> { /// The argument is implied to be located at an index ArgumentImplicitlyIs(usize), /// The argument is located at a specific index given in the format, ArgumentIs(usize), /// The argument has a name. - ArgumentNamed(&'a str), + ArgumentNamed(&'input str), } impl Position<'_> { @@ -132,7 +119,7 @@ impl Position<'_> { } /// Enum of alignments which are supported. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Default)] pub enum Alignment { /// The value will be aligned to the left. AlignLeft, @@ -141,6 +128,7 @@ pub enum Alignment { /// The value will be aligned in the center. AlignCenter, /// The value will take on a default alignment. + #[default] AlignUnknown, } @@ -164,17 +152,18 @@ pub enum DebugHex { /// A count is used for the precision and width parameters of an integer, and /// can reference either an argument or a literal integer. -#[derive(Clone, Debug, PartialEq)] -pub enum Count<'a> { +#[derive(Clone, Debug, PartialEq, Default)] +pub enum Count<'input> { /// The count is specified explicitly. CountIs(u16), /// The count is specified by the argument with the given name. - CountIsName(&'a str, Range<usize>), + CountIsName(&'input str, Range<usize>), /// The count is specified by the argument at the given index. CountIsParam(usize), /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index. CountIsStar(usize), /// The count is implied and cannot be explicitly specified. + #[default] CountImplied, } @@ -208,10 +197,10 @@ pub enum Suggestion { /// /// This is a recursive-descent parser for the sake of simplicity, and if /// necessary there's probably lots of room for improvement performance-wise. -pub struct Parser<'a> { +pub struct Parser<'input> { mode: ParseMode, /// Input to be parsed - input: &'a str, + input: &'input str, /// Tuples of the span in the code snippet (input as written before being unescaped), the pos in input, and the char in input input_vec: Vec<(Range<usize>, usize, char)>, /// Index into input_vec @@ -237,15 +226,15 @@ pub struct Parser<'a> { pub line_spans: Vec<Range<usize>>, } -impl<'a> Iterator for Parser<'a> { - type Item = Piece<'a>; +impl<'input> Iterator for Parser<'input> { + type Item = Piece<'input>; - fn next(&mut self) -> Option<Piece<'a>> { - if let Some(&(Range { start, end }, idx, ch)) = self.input_vec.get(self.input_vec_index) { + fn next(&mut self) -> Option<Piece<'input>> { + if let Some((Range { start, end }, idx, ch)) = self.peek() { match ch { '{' => { self.input_vec_index += 1; - if let Some(&(_, i, '{')) = self.input_vec.get(self.input_vec_index) { + if let Some((_, i, '{')) = self.peek() { self.input_vec_index += 1; // double open brace escape: "{{" // next state after this is either end-of-input or seen-a-brace @@ -254,25 +243,21 @@ impl<'a> Iterator for Parser<'a> { // single open brace self.last_open_brace = Some(start..end); let arg = self.argument(); - if let Some(close_brace_range) = self.consume_closing_brace(&arg) { + self.ws(); + if let Some((close_brace_range, _)) = self.consume_pos('}') { if self.is_source_literal { self.arg_places.push(start..close_brace_range.end); } - } else if let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) { - match c { - '?' => self.suggest_format_debug(), - '<' | '^' | '>' => self.suggest_format_align(c), - _ => { - self.suggest_positional_arg_instead_of_captured_arg(arg.clone()) - } - } + } else { + self.missing_closing_brace(&arg); } + Some(Piece::NextArgument(Box::new(arg))) } } '}' => { self.input_vec_index += 1; - if let Some(&(_, i, '}')) = self.input_vec.get(self.input_vec_index) { + if let Some((_, i, '}')) = self.peek() { self.input_vec_index += 1; // double close brace escape: "}}" // next state after this is either end-of-input or start @@ -307,14 +292,14 @@ impl<'a> Iterator for Parser<'a> { } } -impl<'a> Parser<'a> { +impl<'input> Parser<'input> { /// Creates a new parser for the given unescaped input string and /// optional code snippet (the input as written before being unescaped), /// where `style` is `Some(nr_hashes)` when the snippet is a raw string with that many hashes. /// If the input comes via `println` or `panic`, then it has a newline already appended, /// which is reflected in the `appended_newline` parameter. pub fn new( - input: &'a str, + input: &'input str, style: Option<usize>, snippet: Option<String>, appended_newline: bool, @@ -406,6 +391,16 @@ impl<'a> Parser<'a> { } } + /// Peeks at the current position, without incrementing the pointer. + pub fn peek(&self) -> Option<(Range<usize>, usize, char)> { + self.input_vec.get(self.input_vec_index).cloned() + } + + /// Peeks at the current position + 1, without incrementing the pointer. + pub fn peek_ahead(&self) -> Option<(Range<usize>, usize, char)> { + self.input_vec.get(self.input_vec_index + 1).cloned() + } + /// Optionally consumes the specified character. If the character is not at /// the current position, then the current iterator isn't moved and `false` is /// returned, otherwise the character is consumed and `true` is returned. @@ -418,27 +413,19 @@ impl<'a> Parser<'a> { /// returned, otherwise the character is consumed and the current position is /// returned. fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> { - if let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) { - if ch == *c { - self.input_vec_index += 1; - return Some((r.clone(), *i)); - } + if let Some((r, i, c)) = self.peek() + && ch == c + { + self.input_vec_index += 1; + return Some((r, i)); } + None } - /// Forces consumption of the specified character. If the character is not - /// found, an error is emitted. - fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<Range<usize>> { - self.ws(); - - let (range, description) = if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index) - { - if *c == '}' { - self.input_vec_index += 1; - return Some(r.clone()); - } - // or r.clone()? + /// Called if a closing brace was not found. + fn missing_closing_brace(&mut self, arg: &Argument<'_>) { + let (range, description) = if let Some((r, _, c)) = self.peek() { (r.start..r.start, format!("expected `}}`, found `{}`", c.escape_debug())) } else { ( @@ -471,7 +458,13 @@ impl<'a> Parser<'a> { suggestion: Suggestion::None, }); - None + if let Some((_, _, c)) = self.peek() { + match c { + '?' => self.suggest_format_debug(), + '<' | '^' | '>' => self.suggest_format_align(c), + _ => self.suggest_positional_arg_instead_of_captured_arg(arg), + } + } } /// Consumes all whitespace characters until the first non-whitespace character @@ -483,11 +476,11 @@ impl<'a> Parser<'a> { /// Parses all of a string which is to be considered a "raw literal" in a /// format string. This is everything outside of the braces. - fn string(&mut self, start: usize) -> &'a str { - while let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) { + fn string(&mut self, start: usize) -> &'input str { + while let Some((r, i, c)) = self.peek() { match c { '{' | '}' => { - return &self.input[start..*i]; + return &self.input[start..i]; } '\n' if self.is_source_literal => { self.input_vec_index += 1; @@ -507,7 +500,7 @@ impl<'a> Parser<'a> { } /// Parses an `Argument` structure, or what's contained within braces inside the format string. - fn argument(&mut self) -> Argument<'a> { + fn argument(&mut self) -> Argument<'input> { let start_idx = self.input_vec_index; let position = self.position(); @@ -518,6 +511,7 @@ impl<'a> Parser<'a> { let format = match self.mode { ParseMode::Format => self.format(), ParseMode::InlineAsm => self.inline_asm(), + ParseMode::Diagnostic => self.diagnostic(), }; // Resolve position after parsing format spec. @@ -536,31 +530,27 @@ impl<'a> Parser<'a> { /// integer index of an argument, a named argument, or a blank string. /// Returns `Some(parsed_position)` if the position is not implicitly /// consuming a macro argument, `None` if it's the case. - fn position(&mut self) -> Option<Position<'a>> { + fn position(&mut self) -> Option<Position<'input>> { if let Some(i) = self.integer() { Some(ArgumentIs(i.into())) } else { - match self.input_vec.get(self.input_vec_index) { - Some((range, _, c)) if rustc_lexer::is_id_start(*c) => { + match self.peek() { + Some((range, _, c)) if rustc_lexer::is_id_start(c) => { let start = range.start; let word = self.word(); // Recover from `r#ident` in format strings. - // FIXME: use a let chain - if word == "r" { - if let Some((r, _, '#')) = self.input_vec.get(self.input_vec_index) { - if self - .input_vec - .get(self.input_vec_index + 1) - .is_some_and(|(_, _, c)| rustc_lexer::is_id_start(*c)) - { - self.input_vec_index += 1; - let prefix_end = r.end; - let word = self.word(); - let prefix_span = start..prefix_end; - let full_span = - start..self.input_vec_index2range(self.input_vec_index).start; - self.errors.insert(0, ParseError { + if word == "r" + && let Some((r, _, '#')) = self.peek() + && self.peek_ahead().is_some_and(|(_, _, c)| rustc_lexer::is_id_start(c)) + { + self.input_vec_index += 1; + let prefix_end = r.end; + let word = self.word(); + let prefix_span = start..prefix_end; + let full_span = + start..self.input_vec_index2range(self.input_vec_index).start; + self.errors.insert(0, ParseError { description: "raw identifiers are not supported".to_owned(), note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()), label: "raw identifier used here".to_owned(), @@ -568,9 +558,7 @@ impl<'a> Parser<'a> { secondary_label: None, suggestion: Suggestion::RemoveRawIdent(prefix_span), }); - return Some(ArgumentNamed(word)); - } - } + return Some(ArgumentNamed(word)); } Some(ArgumentNamed(word)) @@ -584,7 +572,7 @@ impl<'a> Parser<'a> { } fn input_vec_index2pos(&self, index: usize) -> usize { - if let Some(&(_, pos, _)) = self.input_vec.get(index) { pos } else { self.input.len() } + if let Some((_, pos, _)) = self.input_vec.get(index) { *pos } else { self.input.len() } } fn input_vec_index2range(&self, index: usize) -> Range<usize> { @@ -597,33 +585,18 @@ impl<'a> Parser<'a> { /// Parses a format specifier at the current position, returning all of the /// relevant information in the `FormatSpec` struct. - fn format(&mut self) -> FormatSpec<'a> { - let mut spec = FormatSpec { - fill: None, - fill_span: None, - align: AlignUnknown, - sign: None, - alternate: false, - zero_pad: false, - debug_hex: None, - precision: CountImplied, - precision_span: None, - width: CountImplied, - width_span: None, - ty: &self.input[..0], - ty_span: None, - }; + fn format(&mut self) -> FormatSpec<'input> { + let mut spec = FormatSpec::default(); + if !self.consume(':') { return spec; } // fill character - if let Some(&(ref r, _, c)) = self.input_vec.get(self.input_vec_index) { - if let Some((_, _, '>' | '<' | '^')) = self.input_vec.get(self.input_vec_index + 1) { - self.input_vec_index += 1; - spec.fill = Some(c); - spec.fill_span = Some(r.clone()); - } + if let (Some((r, _, c)), Some((_, _, '>' | '<' | '^'))) = (self.peek(), self.peek_ahead()) { + self.input_vec_index += 1; + spec.fill = Some(c); + spec.fill_span = Some(r); } // Alignment if self.consume('<') { @@ -701,24 +674,21 @@ impl<'a> Parser<'a> { } } else if let Some((range, _)) = self.consume_pos('?') { spec.ty = "?"; - if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index) { - match c { - '#' | 'x' | 'X' => self.errors.insert( - 0, - ParseError { - description: format!("expected `}}`, found `{c}`"), - note: None, - label: "expected `'}'`".into(), - span: r.clone(), - secondary_label: None, - suggestion: Suggestion::ReorderFormatParameter( - range.start..r.end, - format!("{c}?"), - ), - }, - ), - _ => (), - } + if let Some((r, _, c @ ('#' | 'x' | 'X'))) = self.peek() { + self.errors.insert( + 0, + ParseError { + description: format!("expected `}}`, found `{c}`"), + note: None, + label: "expected `'}'`".into(), + span: r.clone(), + secondary_label: None, + suggestion: Suggestion::ReorderFormatParameter( + range.start..r.end, + format!("{c}?"), + ), + }, + ); } } else { spec.ty = self.word(); @@ -733,22 +703,9 @@ impl<'a> Parser<'a> { /// Parses an inline assembly template modifier at the current position, returning the modifier /// in the `ty` field of the `FormatSpec` struct. - fn inline_asm(&mut self) -> FormatSpec<'a> { - let mut spec = FormatSpec { - fill: None, - fill_span: None, - align: AlignUnknown, - sign: None, - alternate: false, - zero_pad: false, - debug_hex: None, - precision: CountImplied, - precision_span: None, - width: CountImplied, - width_span: None, - ty: &self.input[..0], - ty_span: None, - }; + fn inline_asm(&mut self) -> FormatSpec<'input> { + let mut spec = FormatSpec::default(); + if !self.consume(':') { return spec; } @@ -764,10 +721,26 @@ impl<'a> Parser<'a> { spec } + /// Always returns an empty `FormatSpec` + fn diagnostic(&mut self) -> FormatSpec<'input> { + let mut spec = FormatSpec::default(); + + let Some((Range { start, .. }, start_idx)) = self.consume_pos(':') else { + return spec; + }; + + spec.ty = self.string(start_idx); + spec.ty_span = { + let end = self.input_vec_index2range(self.input_vec_index).start; + Some(start..end) + }; + spec + } + /// Parses a `Count` parameter at the current position. This does not check /// for 'CountIsNextParam' because that is only used in precision, not /// width. - fn count(&mut self) -> Count<'a> { + fn count(&mut self) -> Count<'input> { if let Some(i) = self.integer() { if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) } } else { @@ -786,10 +759,10 @@ impl<'a> Parser<'a> { /// Parses a word starting at the current position. A word is the same as a /// Rust identifier, except that it can't start with `_` character. - fn word(&mut self) -> &'a str { + fn word(&mut self) -> &'input str { let index = self.input_vec_index; - match self.input_vec.get(self.input_vec_index) { - Some(&(ref r, i, c)) if rustc_lexer::is_id_start(c) => { + match self.peek() { + Some((ref r, i, c)) if rustc_lexer::is_id_start(c) => { self.input_vec_index += 1; (r.start, i) } @@ -798,7 +771,7 @@ impl<'a> Parser<'a> { } }; let (err_end, end): (usize, usize) = loop { - if let Some(&(ref r, i, c)) = self.input_vec.get(self.input_vec_index) { + if let Some((ref r, i, c)) = self.peek() { if rustc_lexer::is_id_continue(c) { self.input_vec_index += 1; } else { @@ -828,7 +801,7 @@ impl<'a> Parser<'a> { let mut found = false; let mut overflow = false; let start_index = self.input_vec_index; - while let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) { + while let Some((_, _, c)) = self.peek() { if let Some(i) = c.to_digit(10) { self.input_vec_index += 1; let (tmp, mul_overflow) = cur.overflowing_mul(10); @@ -897,7 +870,7 @@ impl<'a> Parser<'a> { } } - fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) { + fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: &Argument<'_>) { // If the argument is not an identifier, it is not a field access. if !arg.is_identifier() { return; diff --git a/compiler/rustc_parse_format/src/tests.rs b/compiler/rustc_parse_format/src/tests.rs index e6a7f24034a..a6c7e1890ab 100644 --- a/compiler/rustc_parse_format/src/tests.rs +++ b/compiler/rustc_parse_format/src/tests.rs @@ -553,3 +553,45 @@ fn asm_concat() { assert_eq!(parser.by_ref().collect::<Vec<Piece<'static>>>(), &[Lit(asm)]); assert_eq!(parser.line_spans, &[]); } + +#[test] +fn diagnostic_format_flags() { + let lit = "{thing:blah}"; + let mut parser = Parser::new(lit, None, None, false, ParseMode::Diagnostic); + assert!(!parser.is_source_literal); + + let [NextArgument(arg)] = &*parser.by_ref().collect::<Vec<Piece<'static>>>() else { panic!() }; + + assert_eq!( + **arg, + Argument { + position: ArgumentNamed("thing"), + position_span: 2..7, + format: FormatSpec { ty: ":blah", ty_span: Some(7..12), ..Default::default() }, + } + ); + + assert_eq!(parser.line_spans, &[]); + assert!(parser.errors.is_empty()); +} + +#[test] +fn diagnostic_format_mod() { + let lit = "{thing:+}"; + let mut parser = Parser::new(lit, None, None, false, ParseMode::Diagnostic); + assert!(!parser.is_source_literal); + + let [NextArgument(arg)] = &*parser.by_ref().collect::<Vec<Piece<'static>>>() else { panic!() }; + + assert_eq!( + **arg, + Argument { + position: ArgumentNamed("thing"), + position_span: 2..7, + format: FormatSpec { ty: ":+", ty_span: Some(7..9), ..Default::default() }, + } + ); + + assert_eq!(parser.line_spans, &[]); + assert!(parser.errors.is_empty()); +} diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 1fe521bd32d..87c848cf857 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -17,7 +17,7 @@ use rustc_feature::{GateIssue, UnstableFeatures, find_feature_issue}; use rustc_span::edition::Edition; use rustc_span::hygiene::ExpnId; use rustc_span::source_map::{FilePathMapping, SourceMap}; -use rustc_span::{Span, Symbol}; +use rustc_span::{Span, Symbol, sym}; use crate::Session; use crate::config::{Cfg, CheckCfg}; @@ -192,8 +192,11 @@ pub fn add_feature_diagnostics_for_issue<G: EmissionGuarantee>( } else { err.subdiagnostic(FeatureDiagnosticHelp { feature }); } - - if sess.opts.unstable_opts.ui_testing { + if feature == sym::rustc_attrs { + // We're unlikely to stabilize something out of `rustc_attrs` + // without at least renaming it, so pointing out how old + // the compiler is will do little good. + } else if sess.opts.unstable_opts.ui_testing { err.subdiagnostic(SuggestUpgradeCompiler::ui_testing()); } else if let Some(suggestion) = SuggestUpgradeCompiler::new() { err.subdiagnostic(suggestion); diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 37968386e9a..89dab90dc68 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -810,7 +810,8 @@ impl<'tcx> OnUnimplementedFormatString { let mut result = Ok(()); - match FormatString::parse(self.symbol, self.span, &ctx) { + let snippet = tcx.sess.source_map().span_to_snippet(self.span).ok(); + match FormatString::parse(self.symbol, snippet, self.span, &ctx) { // Warnings about format specifiers, deprecated parameters, wrong parameters etc. // In other words we'd like to let the author know, but we can still try to format the string later Ok(FormatString { warnings, .. }) => { @@ -848,34 +849,27 @@ impl<'tcx> OnUnimplementedFormatString { } } } - // Errors from the underlying `rustc_parse_format::Parser` - Err(errors) => { + // Error from the underlying `rustc_parse_format::Parser` + Err(e) => { // we cannot return errors from processing the format string as hard error here // as the diagnostic namespace guarantees that malformed input cannot cause an error // // if we encounter any error while processing we nevertheless want to show it as warning // so that users are aware that something is not correct - for e in errors { - if self.is_diagnostic_namespace_variant { - if let Some(trait_def_id) = trait_def_id.as_local() { - tcx.emit_node_span_lint( - UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(trait_def_id), - self.span, - WrappedParserError { description: e.description, label: e.label }, - ); - } - } else { - let reported = struct_span_code_err!( - tcx.dcx(), + if self.is_diagnostic_namespace_variant { + if let Some(trait_def_id) = trait_def_id.as_local() { + tcx.emit_node_span_lint( + UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, + tcx.local_def_id_to_hir_id(trait_def_id), self.span, - E0231, - "{}", - e.description, - ) - .emit(); - result = Err(reported); + WrappedParserError { description: e.description, label: e.label }, + ); } + } else { + let reported = + struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,) + .emit(); + result = Err(reported); } } } @@ -896,7 +890,8 @@ impl<'tcx> OnUnimplementedFormatString { Ctx::RustcOnUnimplemented { tcx, trait_def_id } }; - if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) { + // No point passing a snippet here, we already did that in `verify` + if let Ok(s) = FormatString::parse(self.symbol, None, self.span, &ctx) { s.format(args) } else { // we cannot return errors from processing the format string as hard error here diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs index e8ea9f2d23e..171d05230d4 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs @@ -198,7 +198,7 @@ enum LitOrArg { impl FilterFormatString { fn parse(input: Symbol) -> Self { - let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Format) + let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic) .map(|p| match p { Piece::Lit(s) => LitOrArg::Lit(s.to_owned()), // We just ignore formatspecs here diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs index 7c1dfc1728f..3e8b906fa93 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs @@ -5,12 +5,11 @@ use errors::*; use rustc_middle::ty::print::TraitRefPrintSugared; use rustc_middle::ty::{GenericParamDefKind, TyCtxt}; use rustc_parse_format::{ - Alignment, Argument, Count, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, - Position, + Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, }; use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::def_id::DefId; -use rustc_span::{BytePos, Pos, Span, Symbol, kw, sym}; +use rustc_span::{InnerSpan, Span, Symbol, kw, sym}; /// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces", /// either as string pieces or dynamic arguments. @@ -160,32 +159,32 @@ impl FormatString { pub fn parse<'tcx>( input: Symbol, + snippet: Option<String>, span: Span, ctx: &Ctx<'tcx>, - ) -> Result<Self, Vec<ParseError>> { + ) -> Result<Self, ParseError> { let s = input.as_str(); - let mut parser = Parser::new(s, None, None, false, ParseMode::Format); - let mut pieces = Vec::new(); + let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic); + let pieces: Vec<_> = parser.by_ref().collect(); + + if let Some(err) = parser.errors.into_iter().next() { + return Err(err); + } let mut warnings = Vec::new(); - for piece in &mut parser { - match piece { - RpfPiece::Lit(lit) => { - pieces.push(Piece::Lit(lit.into())); - } + let pieces = pieces + .into_iter() + .map(|piece| match piece { + RpfPiece::Lit(lit) => Piece::Lit(lit.into()), RpfPiece::NextArgument(arg) => { - warn_on_format_spec(arg.format.clone(), &mut warnings, span); - let arg = parse_arg(&arg, ctx, &mut warnings, span); - pieces.push(Piece::Arg(arg)); + warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal); + let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal); + Piece::Arg(arg) } - } - } + }) + .collect(); - if parser.errors.is_empty() { - Ok(FormatString { input, pieces, span, warnings }) - } else { - Err(parser.errors) - } + Ok(FormatString { input, pieces, span, warnings }) } pub fn format(&self, args: &FormatArgs<'_>) -> String { @@ -229,11 +228,12 @@ fn parse_arg<'tcx>( ctx: &Ctx<'tcx>, warnings: &mut Vec<FormatWarning>, input_span: Span, + is_source_literal: bool, ) -> FormatArg { let (Ctx::RustcOnUnimplemented { tcx, trait_def_id } | Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }) = ctx; - let span = slice_span(input_span, arg.position_span.clone()); + let span = slice_span(input_span, arg.position_span.clone(), is_source_literal); match arg.position { // Something like "hello {name}" @@ -283,39 +283,24 @@ fn parse_arg<'tcx>( /// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything /// with specifiers, so emit a warning if they are used. -fn warn_on_format_spec(spec: FormatSpec<'_>, warnings: &mut Vec<FormatWarning>, input_span: Span) { - if !matches!( - spec, - FormatSpec { - fill: None, - fill_span: None, - align: Alignment::AlignUnknown, - sign: None, - alternate: false, - zero_pad: false, - debug_hex: None, - precision: Count::CountImplied, - precision_span: None, - width: Count::CountImplied, - width_span: None, - ty: _, - ty_span: _, - }, - ) { - let span = spec.ty_span.map(|inner| slice_span(input_span, inner)).unwrap_or(input_span); +fn warn_on_format_spec( + spec: &FormatSpec<'_>, + warnings: &mut Vec<FormatWarning>, + input_span: Span, + is_source_literal: bool, +) { + if spec.ty != "" { + let span = spec + .ty_span + .as_ref() + .map(|inner| slice_span(input_span, inner.clone(), is_source_literal)) + .unwrap_or(input_span); warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() }) } } -fn slice_span(input: Span, range: Range<usize>) -> Span { - let span = input.data(); - - Span::new( - span.lo + BytePos::from_usize(range.start), - span.lo + BytePos::from_usize(range.end), - span.ctxt, - span.parent, - ) +fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal: bool) -> Span { + if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input } } pub mod errors { |
