diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index 3a7c1ea65f3d8..c50d9db8be639 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -121,20 +121,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // discriminants are int-like. let discr_val = self.int_to_int_or_float(&tag_val, discr_layout).unwrap(); let discr_bits = discr_val.to_scalar().to_bits(discr_layout.size)?; - // Convert discriminant to variant index. Since we validated the tag against the - // layout range above, this cannot fail. + // Convert discriminant to variant index. The tag may pass the layout range + // check above but still not match any actual variant discriminant (e.g., + // non-contiguous discriminants with a wrapping valid_range). let index = match *ty.kind() { ty::Adt(adt, _) => { - adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits).unwrap() + adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits) } ty::Coroutine(def_id, args) => { let args = args.as_coroutine(); - args.discriminants(def_id, *self.tcx) - .find(|(_, var)| var.val == discr_bits) - .unwrap() + args.discriminants(def_id, *self.tcx).find(|(_, var)| var.val == discr_bits) } _ => span_bug!(self.cur_span(), "tagged layout for non-adt non-coroutine"), - }; + } + .ok_or_else(|| err_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))))?; // Return the cast value, and the index. index.0 } diff --git a/compiler/rustc_errors/src/decorate_diag.rs b/compiler/rustc_errors/src/decorate_diag.rs index 61dd8f0493f91..3215914698896 100644 --- a/compiler/rustc_errors/src/decorate_diag.rs +++ b/compiler/rustc_errors/src/decorate_diag.rs @@ -1,3 +1,5 @@ +use std::any::Any; + /// This module provides types and traits for buffering lints until later in compilation. use rustc_ast::node_id::NodeId; use rustc_data_structures::fx::FxIndexMap; @@ -10,9 +12,11 @@ use crate::{Diag, DiagCtxtHandle, Diagnostic, Level}; /// We can't implement `Diagnostic` for `BuiltinLintDiag`, because decorating some of its /// variants requires types we don't have yet. So, handle that case separately. pub enum DecorateDiagCompat { + /// The third argument of the closure is a `Session`. However, due to the dependency tree, + /// we don't have access to `rustc_session` here, so we downcast it when needed. Dynamic( Box< - dyn for<'a> FnOnce(DiagCtxtHandle<'a>, Level) -> Diag<'a, ()> + dyn for<'a> FnOnce(DiagCtxtHandle<'a>, Level, &dyn Any) -> Diag<'a, ()> + DynSync + DynSend + 'static, @@ -30,7 +34,7 @@ impl std::fmt::Debug for DecorateDiagCompat { impl Diagnostic<'a, ()> + DynSync + DynSend + 'static> From for DecorateDiagCompat { #[inline] fn from(d: D) -> Self { - Self::Dynamic(Box::new(|dcx, level| d.into_diag(dcx, level))) + Self::Dynamic(Box::new(|dcx, level, _| d.into_diag(dcx, level))) } } @@ -97,6 +101,26 @@ impl LintBuffer { node_id: NodeId, span: impl Into, callback: F, + ) { + self.add_early_lint(BufferedEarlyLint { + lint_id: LintId::of(lint), + node_id, + span: Some(span.into()), + diagnostic: DecorateDiagCompat::Dynamic(Box::new(|dcx, level, _| callback(dcx, level))), + }); + } + + pub fn dyn_buffer_lint_any< + F: for<'a> FnOnce(DiagCtxtHandle<'a>, Level, &dyn Any) -> Diag<'a, ()> + + DynSend + + DynSync + + 'static, + >( + &mut self, + lint: &'static Lint, + node_id: NodeId, + span: impl Into, + callback: F, ) { self.add_early_lint(BufferedEarlyLint { lint_id: LintId::of(lint), diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 9525a45d55f1b..7acf95d77d40e 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -7,7 +7,6 @@ use std::panic; use std::path::PathBuf; use std::thread::panicking; -use rustc_data_structures::sync::{DynSend, DynSync}; use rustc_error_messages::{DiagArgMap, DiagArgName, DiagArgValue, IntoDiagArg}; use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; @@ -119,16 +118,6 @@ where } } -impl<'a> Diagnostic<'a, ()> - for Box< - dyn for<'b> FnOnce(DiagCtxtHandle<'b>, Level) -> Diag<'b, ()> + DynSync + DynSend + 'static, - > -{ - fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { - self(dcx, level) - } -} - /// Type used to emit diagnostic through a closure instead of implementing the `Diagnostic` trait. pub struct DiagDecorator)>(pub F); diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 7d9c2e8327b91..635185be1cb76 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -15,9 +15,9 @@ use rustc_session::lint::LintPass; use rustc_span::{DUMMY_SP, Ident, Span}; use tracing::debug; -use crate::DecorateBuiltinLint; use crate::context::{EarlyContext, LintContext, LintStore}; use crate::passes::{EarlyLintPass, EarlyLintPassObject}; +use crate::{DecorateBuiltinLint, DiagAndSess}; pub(super) mod diagnostics; @@ -49,8 +49,12 @@ impl<'ecx, 'tcx, T: EarlyLintPass> EarlyContextAndPass<'ecx, 'tcx, T> { }, ); } - DecorateDiagCompat::Dynamic(d) => { - self.context.opt_span_lint(lint_id.lint, span, d); + DecorateDiagCompat::Dynamic(callback) => { + self.context.opt_span_lint( + lint_id.lint, + span, + DiagAndSess { callback, sess: self.context.sess() }, + ); } } } diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 75824670d2496..aee16526dfc0b 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -1,5 +1,7 @@ +use std::any::Any; use std::borrow::Cow; +use rustc_data_structures::sync::DynSend; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, Level, elided_lifetime_in_path_suggestion, @@ -8,12 +10,24 @@ use rustc_hir::lints::{AttributeLintKind, FormatWarning}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_session::lint::BuiltinLintDiag; -use tracing::debug; use crate::lints; mod check_cfg; +pub struct DiagAndSess<'sess> { + pub callback: Box< + dyn for<'b> FnOnce(DiagCtxtHandle<'b>, Level, &dyn Any) -> Diag<'b, ()> + DynSend + 'static, + >, + pub sess: &'sess Session, +} + +impl<'a> Diagnostic<'a, ()> for DiagAndSess<'_> { + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { + (self.callback)(dcx, level, self.sess) + } +} + /// This is a diagnostic struct that will decorate a `BuiltinLintDiag` /// Directly creating the lint structs is expensive, using this will only decorate the lint structs when needed. pub struct DecorateBuiltinLint<'sess, 'tcx> { @@ -25,28 +39,6 @@ pub struct DecorateBuiltinLint<'sess, 'tcx> { impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { match self.diagnostic { - BuiltinLintDiag::AbsPathWithModule(mod_span) => { - let (replacement, applicability) = - match self.sess.source_map().span_to_snippet(mod_span) { - Ok(ref s) => { - // FIXME(Manishearth) ideally the emitting code - // can tell us whether or not this is global - let opt_colon = - if s.trim_start().starts_with("::") { "" } else { "::" }; - - (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) - } - Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), - }; - lints::AbsPathWithModule { - sugg: lints::AbsPathWithModuleSugg { - span: mod_span, - applicability, - replacement, - }, - } - .into_diag(dcx, level) - } BuiltinLintDiag::ElidedLifetimesInPaths( n, path_span, @@ -87,36 +79,6 @@ impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { } .into_diag(dcx, level) } - BuiltinLintDiag::SingleUseLifetime { - param_span, - use_span, - elidable, - deletion_span, - ident, - } => { - debug!(?param_span, ?use_span, ?deletion_span); - let suggestion = if let Some(deletion_span) = deletion_span { - let (use_span, replace_lt) = if elidable { - let use_span = - self.sess.source_map().span_extend_while_whitespace(use_span); - (use_span, String::new()) - } else { - (use_span, "'_".to_owned()) - }; - debug!(?deletion_span, ?use_span); - - // issue 107998 for the case such as a wrong function pointer type - // `deletion_span` is empty and there is no need to report lifetime uses here - let deletion_span = - if deletion_span.is_empty() { None } else { Some(deletion_span) }; - Some(lints::SingleUseLifetimeSugg { deletion_span, use_span, replace_lt }) - } else { - None - }; - - lints::SingleUseLifetime { suggestion, param_span, use_span, ident } - .into_diag(dcx, level) - } BuiltinLintDiag::NamedArgumentUsedPositionally { position_sp_to_replace, position_sp_for_msg, diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index e4106cbdfb797..c76fafcde2ee1 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -129,7 +129,7 @@ use unused::*; #[rustfmt::skip] pub use builtin::{MissingDoc, SoftLints}; pub use context::{EarlyContext, LateContext, LintContext, LintStore}; -pub use early::diagnostics::{DecorateAttrLint, DecorateBuiltinLint}; +pub use early::diagnostics::{DecorateAttrLint, DecorateBuiltinLint, DiagAndSess}; pub use early::{EarlyCheckNode, check_ast_node}; pub use late::{check_crate, late_lint_mod, unerased_lint_store}; pub use levels::LintLevelsBuilder; diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index b77225db4f0eb..5819f2bc151f9 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3011,25 +3011,6 @@ pub(crate) struct IllFormedAttributeInput { pub docs: &'static str, } -#[derive(Diagnostic)] -#[diag( - "absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition" -)] -pub(crate) struct AbsPathWithModule { - #[subdiagnostic] - pub sugg: AbsPathWithModuleSugg, -} - -#[derive(Subdiagnostic)] -#[suggestion("use `crate`", code = "{replacement}")] -pub(crate) struct AbsPathWithModuleSugg { - #[primary_span] - pub span: Span, - #[applicability] - pub applicability: Applicability, - pub replacement: String, -} - #[derive(Diagnostic)] #[diag("hidden lifetime parameters in types are deprecated")] pub(crate) struct ElidedLifetimesInPaths { @@ -3081,30 +3062,6 @@ pub(crate) enum UnusedImportsSugg { }, } -#[derive(Diagnostic)] -#[diag("lifetime parameter `{$ident}` only used once")] -pub(crate) struct SingleUseLifetime { - #[label("this lifetime...")] - pub param_span: Span, - #[label("...is used only here")] - pub use_span: Span, - #[subdiagnostic] - pub suggestion: Option, - - pub ident: Ident, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion("elide the single-use lifetime", applicability = "machine-applicable")] -pub(crate) struct SingleUseLifetimeSugg { - #[suggestion_part(code = "")] - pub deletion_span: Option, - #[suggestion_part(code = "{replace_lt}")] - pub use_span: Span, - - pub replace_lt: String, -} - #[derive(Diagnostic)] #[diag("named argument `{$named_arg_name}` is not used by name")] pub(crate) struct NamedArgumentUsedPositionally { diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 71b507f67e8f2..7e51029b02bb6 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -656,7 +656,6 @@ pub enum DeprecatedSinceKind { // becomes hacky (and it gets allocated). #[derive(Debug)] pub enum BuiltinLintDiag { - AbsPathWithModule(Span), ElidedLifetimesInPaths(usize, Span, bool, Span), UnusedImports { remove_whole_use: bool, @@ -665,18 +664,6 @@ pub enum BuiltinLintDiag { test_module_span: Option, span_snippets: Vec, }, - SingleUseLifetime { - /// Span of the parameter which declares this lifetime. - param_span: Span, - /// Span of the code that should be removed when eliding this lifetime. - /// This span should include leading or trailing comma. - deletion_span: Option, - /// Span of the single use, or None if the lifetime is never used. - /// If true, the lifetime will be fully elided. - use_span: Span, - elidable: bool, - ident: Ident, - }, NamedArgumentUsedPositionally { /// Span where the named argument is used by position and will be replaced with the named /// argument name diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index cab37f5c0faa6..9e0f04e82b472 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -11,7 +11,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, MultiSpan, SuggestionStyle, + Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, struct_span_code_err, }; use rustc_feature::BUILTIN_ATTRIBUTES; @@ -23,7 +23,6 @@ use rustc_hir::{PrimTy, Stability, StabilityLevel, find_attr}; use rustc_middle::bug; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::{ ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_IMPORT_VISIBILITIES, AMBIGUOUS_PANIC_IMPORTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, @@ -510,12 +509,35 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { return; } - let diag = BuiltinLintDiag::AbsPathWithModule(root_span); - self.lint_buffer.buffer_lint( + self.lint_buffer.dyn_buffer_lint_any( ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, node_id, root_span, - diag, + move |dcx, level, sess| { + let (replacement, applicability) = match sess + .downcast_ref::() + .expect("expected a `Session`") + .source_map() + .span_to_snippet(root_span) + { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) + } + Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), + }; + errors::AbsPathWithModule { + sugg: errors::AbsPathWithModuleSugg { + span: root_span, + applicability, + replacement, + }, + } + .into_diag(dcx, level) + }, ); } diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 43b006d72e500..305231a3ea0d9 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1697,3 +1697,46 @@ pub(crate) struct AssociatedConstElidedLifetime { #[note("cannot automatically infer `'static` because of other lifetimes in scope")] pub lifetimes_in_scope: MultiSpan, } + +#[derive(Diagnostic)] +#[diag("lifetime parameter `{$ident}` only used once")] +pub(crate) struct SingleUseLifetime { + #[label("this lifetime...")] + pub param_span: Span, + #[label("...is used only here")] + pub use_span: Span, + #[subdiagnostic] + pub suggestion: Option, + + pub ident: Ident, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("elide the single-use lifetime", applicability = "machine-applicable")] +pub(crate) struct SingleUseLifetimeSugg { + #[suggestion_part(code = "")] + pub deletion_span: Option, + #[suggestion_part(code = "{replace_lt}")] + pub use_span: Span, + + pub replace_lt: String, +} + +#[derive(Diagnostic)] +#[diag( + "absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition" +)] +pub(crate) struct AbsPathWithModule { + #[subdiagnostic] + pub sugg: AbsPathWithModuleSugg, +} + +#[derive(Subdiagnostic)] +#[suggestion("use `crate`", code = "{replacement}")] +pub(crate) struct AbsPathWithModuleSugg { + #[primary_span] + pub span: Span, + #[applicability] + pub applicability: Applicability, + pub replacement: String, +} diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 7539e3c4f499f..a78e6ce7482c0 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -14,7 +14,7 @@ use rustc_ast_pretty::pprust::{path_to_string, where_bound_predicate_to_string}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize, + Applicability, Diag, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize, struct_span_code_err, }; use rustc_hir as hir; @@ -3641,22 +3641,52 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { match use_set { Some(LifetimeUseSet::Many) => {} Some(LifetimeUseSet::One { use_span, use_ctxt }) => { - debug!(?param.ident, ?param.ident.span, ?use_span); - - let elidable = matches!(use_ctxt, LifetimeCtxt::Ref); + let param_ident = param.ident; let deletion_span = if param.bounds.is_empty() { deletion_span() } else { None }; - - self.r.lint_buffer.buffer_lint( + self.r.lint_buffer.dyn_buffer_lint_any( lint::builtin::SINGLE_USE_LIFETIMES, param.id, - param.ident.span, - lint::BuiltinLintDiag::SingleUseLifetime { - param_span: param.ident.span, - use_span, - elidable, - deletion_span, - ident: param.ident, + param_ident.span, + move |dcx, level, sess| { + debug!(?param_ident, ?param_ident.span, ?use_span); + + let elidable = matches!(use_ctxt, LifetimeCtxt::Ref); + let suggestion = if let Some(deletion_span) = deletion_span { + let (use_span, replace_lt) = if elidable { + let use_span = sess + .downcast_ref::() + .expect("expected a `Session`") + .source_map() + .span_extend_while_whitespace(use_span); + (use_span, String::new()) + } else { + (use_span, "'_".to_owned()) + }; + debug!(?deletion_span, ?use_span); + + // issue 107998 for the case such as a wrong function pointer type + // `deletion_span` is empty and there is no need to report lifetime uses here + let deletion_span = if deletion_span.is_empty() { + None + } else { + Some(deletion_span) + }; + Some(errors::SingleUseLifetimeSugg { + deletion_span, + use_span, + replace_lt, + }) + } else { + None + }; + errors::SingleUseLifetime { + suggestion, + param_span: param_ident.span, + use_span, + ident: param_ident, + } + .into_diag(dcx, level) }, ); } @@ -4064,6 +4094,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }; let mut spans_suggs: Vec<_> = Vec::new(); + let source_map = self.r.tcx.sess.source_map(); let build_sugg = |lt: MissingLifetime| match lt.kind { MissingLifetimeKind::Underscore => { debug_assert_eq!(lt.count, 1); @@ -4074,9 +4105,12 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { (lt.span.shrink_to_hi(), format!("{existing_name} ")) } MissingLifetimeKind::Comma => { - let sugg: String = std::iter::repeat_n([existing_name.as_str(), ", "], lt.count) - .flatten() + let sugg: String = std::iter::repeat_n(existing_name.as_str(), lt.count) + .intersperse(", ") .collect(); + let is_empty_brackets = + source_map.span_look_ahead(lt.span, ">", Some(50)).is_some(); + let sugg = if is_empty_brackets { sugg } else { format!("{sugg}, ") }; (lt.span.shrink_to_hi(), sugg) } MissingLifetimeKind::Brackets => { diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 65a15dba42873..8251050b6aead 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -344,7 +344,7 @@ impl ParseSess { lint, Some(span.into()), node_id, - DecorateDiagCompat::Dynamic(Box::new(callback)), + DecorateDiagCompat::Dynamic(Box::new(|dcx, level, _| callback(dcx, level))), ) } diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs index 951bb5d0029f6..adc561879ebd3 100644 --- a/library/core/src/iter/range.rs +++ b/library/core/src/iter/range.rs @@ -15,6 +15,7 @@ macro_rules! unsafe_impl_trusted_step { )*}; } unsafe_impl_trusted_step![AsciiChar char i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize Ipv4Addr Ipv6Addr]; +unsafe_impl_trusted_step![NonZero NonZero NonZero NonZero NonZero NonZero]; /// Objects that have a notion of *successor* and *predecessor* operations. /// @@ -255,10 +256,8 @@ macro_rules! step_identical_methods { macro_rules! step_integer_impls { { - narrower than or same width as usize: - $( [ $u_narrower:ident $i_narrower:ident ] ),+; - wider than usize: - $( [ $u_wider:ident $i_wider:ident ] ),+; + [ $( [ $u_narrower:ident $i_narrower:ident ] ),+ ] <= usize < + [ $( [ $u_wider:ident $i_wider:ident ] ),+ ] } => { $( #[allow(unreachable_patterns)] @@ -437,20 +436,138 @@ macro_rules! step_integer_impls { #[cfg(target_pointer_width = "64")] step_integer_impls! { - narrower than or same width as usize: [u8 i8], [u16 i16], [u32 i32], [u64 i64], [usize isize]; - wider than usize: [u128 i128]; + [ [u8 i8], [u16 i16], [u32 i32], [u64 i64], [usize isize] ] <= usize < [ [u128 i128] ] } #[cfg(target_pointer_width = "32")] step_integer_impls! { - narrower than or same width as usize: [u8 i8], [u16 i16], [u32 i32], [usize isize]; - wider than usize: [u64 i64], [u128 i128]; + [ [u8 i8], [u16 i16], [u32 i32], [usize isize] ] <= usize < [ [u64 i64], [u128 i128] ] } #[cfg(target_pointer_width = "16")] step_integer_impls! { - narrower than or same width as usize: [u8 i8], [u16 i16], [usize isize]; - wider than usize: [u32 i32], [u64 i64], [u128 i128]; + [ [u8 i8], [u16 i16], [usize isize] ] <= usize < [ [u32 i32], [u64 i64], [u128 i128] ] +} + +// These are still macro-generated because the integer literals resolve to different types. +macro_rules! step_nonzero_identical_methods { + ($int:ident) => { + #[inline] + unsafe fn forward_unchecked(start: Self, n: usize) -> Self { + // SAFETY: the caller has to guarantee that `start + n` doesn't overflow. + unsafe { Self::new_unchecked(start.get().unchecked_add(n as $int)) } + } + + #[inline] + unsafe fn backward_unchecked(start: Self, n: usize) -> Self { + // SAFETY: the caller has to guarantee that `start - n` doesn't overflow or hit zero. + unsafe { Self::new_unchecked(start.get().unchecked_sub(n as $int)) } + } + + #[inline] + #[allow(arithmetic_overflow)] + #[rustc_inherit_overflow_checks] + fn forward(start: Self, n: usize) -> Self { + // In debug builds, trigger a panic on overflow. + // This should optimize completely out in release builds. + if Self::forward_checked(start, n).is_none() { + let _ = $int::MAX + 1; + } + // Do saturating math (wrapping math causes UB if it wraps to Zero) + start.saturating_add(n as $int) + } + + #[inline] + #[allow(arithmetic_overflow)] + #[rustc_inherit_overflow_checks] + fn backward(start: Self, n: usize) -> Self { + // In debug builds, trigger a panic on overflow. + // This should optimize completely out in release builds. + if Self::backward_checked(start, n).is_none() { + let _ = $int::MIN - 1; + } + // Do saturating math (wrapping math causes UB if it wraps to Zero) + Self::new(start.get().saturating_sub(n as $int)).unwrap_or(Self::MIN) + } + + #[inline] + fn steps_between(start: &Self, end: &Self) -> (usize, Option) { + if *start <= *end { + #[allow(irrefutable_let_patterns, reason = "happens on usize or narrower")] + if let Ok(steps) = usize::try_from(end.get() - start.get()) { + (steps, Some(steps)) + } else { + (usize::MAX, None) + } + } else { + (0, None) + } + } + }; +} + +macro_rules! step_nonzero_impls { + { + [$( $narrower:ident ),+] <= usize < [$( $wider:ident ),+] + } => { + $( + #[allow(unreachable_patterns)] + #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")] + impl Step for NonZero<$narrower> { + step_nonzero_identical_methods!($narrower); + + #[inline] + fn forward_checked(start: Self, n: usize) -> Option { + match $narrower::try_from(n) { + Ok(n) => start.checked_add(n), + Err(_) => None, // if n is out of range, `unsigned_start + n` is too + } + } + + #[inline] + fn backward_checked(start: Self, n: usize) -> Option { + match $narrower::try_from(n) { + // *_sub() is not implemented on NonZero + Ok(n) => start.get().checked_sub(n).and_then(Self::new), + Err(_) => None, // if n is out of range, `unsigned_start - n` is too + } + } + } + )+ + + $( + #[allow(unreachable_patterns)] + #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")] + impl Step for NonZero<$wider> { + step_nonzero_identical_methods!($wider); + + #[inline] + fn forward_checked(start: Self, n: usize) -> Option { + start.checked_add(n as $wider) + } + + #[inline] + fn backward_checked(start: Self, n: usize) -> Option { + start.get().checked_sub(n as $wider).and_then(Self::new) + } + } + )+ + }; +} + +#[cfg(target_pointer_width = "64")] +step_nonzero_impls! { + [u8, u16, u32, u64, usize] <= usize < [u128] +} + +#[cfg(target_pointer_width = "32")] +step_nonzero_impls! { + [u8, u16, u32, usize] <= usize < [u64, u128] +} + +#[cfg(target_pointer_width = "16")] +step_nonzero_impls! { + [u8, u16, usize] <= usize < [u32, u64, u128] } #[unstable(feature = "step_trait", issue = "42168")] @@ -944,6 +1061,7 @@ impl Iterator for ops::Range { range_exact_iter_impl! { usize u8 u16 isize i8 i16 + NonZero NonZero NonZero // These are incorrect per the reasoning above, // but removing them would be a breaking change as they were stabilized in Rust 1.0.0. @@ -956,22 +1074,30 @@ range_exact_iter_impl! { unsafe_range_trusted_random_access_impl! { usize u8 u16 isize i8 i16 + NonZero NonZero NonZero } #[cfg(target_pointer_width = "32")] unsafe_range_trusted_random_access_impl! { u32 i32 + NonZero } #[cfg(target_pointer_width = "64")] unsafe_range_trusted_random_access_impl! { u32 i32 u64 i64 + NonZero + NonZero } range_incl_exact_iter_impl! { u8 i8 + NonZero + // Since RangeInclusive> can only be 1..=uN::MAX the length of this range is always + // <= uN::MAX, so they are always valid ExactSizeIterator unlike the ranges that include zero. + NonZero NonZero // These are incorrect per the reasoning above, // but removing them would be a breaking change as they were stabilized in Rust 1.26.0. diff --git a/library/coretests/tests/iter/range.rs b/library/coretests/tests/iter/range.rs index d5d2b8bf2b047..4a00e6f96bda8 100644 --- a/library/coretests/tests/iter/range.rs +++ b/library/coretests/tests/iter/range.rs @@ -502,3 +502,142 @@ fn test_double_ended_range() { panic!("unreachable"); } } + +macro_rules! nz { + (NonZero<$type:ident>($val:literal)) => { + ::core::num::NonZero::<$type>::new($val).unwrap() + }; + (NonZero<$type:ident>::MAX) => { + ::core::num::NonZero::<$type>::new($type::MAX).unwrap() + }; +} + +macro_rules! nonzero_array { + (NonZero<$type:ident>[$($val:literal),*]) => { + [$(nz!(NonZero<$type>($val))),*] + } +} + +macro_rules! nonzero_range { + (NonZero<$type:ident>($($left:literal)?..$($right:literal)?)) => { + nz!(NonZero<$type>($($left)?))..nz!(NonZero<$type>($($right)?)) + }; + (NonZero<$type:ident>($($left:literal)?..MAX)) => { + nz!(NonZero<$type>($($left)?))..nz!(NonZero<$type>::MAX) + }; + (NonZero<$type:ident>($($left:literal)?..=$right:literal)) => { + nz!(NonZero<$type>($($left)?))..=nz!(NonZero<$type>($right)) + }; + (NonZero<$type:ident>($($left:literal)?..=MAX)) => { + nz!(NonZero<$type>($($left)?))..=nz!(NonZero<$type>::MAX) + }; +} + +#[test] +fn test_nonzero_range() { + assert_eq!( + nonzero_range!(NonZero(1..=21)).step_by(5).collect::>(), + nonzero_array!(NonZero[1, 6, 11, 16, 21]) + ); + assert_eq!( + nonzero_range!(NonZero(1..=20)).step_by(5).collect::>(), + nonzero_array!(NonZero[1, 6, 11, 16]) + ); + assert_eq!( + nonzero_range!(NonZero(1..20)).step_by(5).collect::>(), + nonzero_array!(NonZero[1, 6, 11, 16]) + ); + assert_eq!( + nonzero_range!(NonZero(1..21)).rev().step_by(5).collect::>(), + nonzero_array!(NonZero[20, 15, 10, 5]) + ); + assert_eq!( + nonzero_range!(NonZero(1..21)).rev().step_by(6).collect::>(), + nonzero_array!(NonZero[20, 14, 8, 2]) + ); + assert_eq!( + nonzero_range!(NonZero(200..255)).step_by(50).collect::>(), + nonzero_array!(NonZero[200, 250]) + ); + assert_eq!( + nonzero_range!(NonZero(200..5)).step_by(1).collect::>(), + nonzero_array!(NonZero[]) + ); + assert_eq!( + nonzero_range!(NonZero(200..200)).step_by(1).collect::>(), + nonzero_array!(NonZero[]) + ); + + assert_eq!(nonzero_range!(NonZero(10..20)).step_by(1).size_hint(), (10, Some(10))); + assert_eq!(nonzero_range!(NonZero(10..20)).step_by(5).size_hint(), (2, Some(2))); + assert_eq!(nonzero_range!(NonZero(1..21)).rev().step_by(5).size_hint(), (4, Some(4))); + assert_eq!(nonzero_range!(NonZero(1..21)).rev().step_by(6).size_hint(), (4, Some(4))); + assert_eq!(nonzero_range!(NonZero(20..1)).step_by(1).size_hint(), (0, Some(0))); + assert_eq!(nonzero_range!(NonZero(20..20)).step_by(1).size_hint(), (0, Some(0))); + + // ExactSizeIterator + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).len(), usize::from(u8::MAX)); + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).len(), usize::from(u16::MAX)); + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).len(), usize::MAX); + + // Limits (next) + let mut range = nonzero_range!(NonZero(254..=MAX)); + assert_eq!(range.next(), Some(nz!(NonZero(254)))); + assert_eq!(range.next(), Some(nz!(NonZero(255)))); + assert_eq!(range.next(), None); + + let mut range = nonzero_range!(NonZero(65534..=MAX)); + assert_eq!(range.next(), Some(nz!(NonZero(65534)))); + assert_eq!(range.next(), Some(nz!(NonZero(65535)))); + assert_eq!(range.next(), None); + + // Limits (size_hint, exclusive range) + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u8::MAX as usize - 1, Some(u8::MAX as usize - 1)) + ); + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u16::MAX as usize - 1, Some(u16::MAX as usize - 1)) + ); + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u32::MAX as usize - 1, Some(u32::MAX as usize - 1)) + ); + #[cfg(target_pointer_width = "64")] + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u64::MAX as usize - 1, Some(u64::MAX as usize - 1)) + ); + assert_eq!(nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), (usize::MAX, None)); + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (usize::MAX - 1, Some(usize::MAX - 1)) + ); + + // Limits (size_hint, inclusive range) + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u8::MAX as usize, Some(u8::MAX as usize)) + ); + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u16::MAX as usize, Some(u16::MAX as usize)) + ); + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u32::MAX as usize, Some(u32::MAX as usize)) + ); + #[cfg(target_pointer_width = "64")] + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u64::MAX as usize, Some(u64::MAX as usize)) + ); + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), (usize::MAX, None)); + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (usize::MAX, Some(usize::MAX)) + ); +} diff --git a/tests/codegen-llvm/array-equality.rs b/tests/codegen-llvm/array-equality.rs index fa0475bf4809c..8e4c170e4e674 100644 --- a/tests/codegen-llvm/array-equality.rs +++ b/tests/codegen-llvm/array-equality.rs @@ -1,3 +1,6 @@ +//@ revisions: llvm-current llvm-next +//@[llvm-current] ignore-llvm-version: 23-99 +//@[llvm-next] min-llvm-version: 23 //@ compile-flags: -Copt-level=3 -Z merge-functions=disabled //@ only-x86_64 #![crate_type = "lib"] @@ -26,9 +29,18 @@ pub fn array_eq_ref(a: &[u16; 3], b: &[u16; 3]) -> bool { #[no_mangle] pub fn array_eq_value_still_passed_by_pointer(a: [u16; 9], b: [u16; 9]) -> bool { // CHECK-NEXT: start: - // CHECK: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(ptr {{.*}} dereferenceable(18) %{{.+}}, ptr {{.*}} dereferenceable(18) %{{.+}}, i64 18) - // CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0 - // CHECK-NEXT: ret i1 %[[EQ]] + // CHECK-NOT: alloca + // llvm-current-NEXT: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(ptr + // llvm-current-SAME: {{.*}} dereferenceable(18) %{{.+}}, ptr {{.*}} dereferenceable(18) + // llvm-current-SAME: %{{.+}}, i64 18) + // llvm-current-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0 + // llvm-current-NEXT: ret i1 %[[EQ]] + // CHECK-NOT: call + // New LLVM expands the bcmp earlier, so this becomes wide reads + icmp + // No allocas or calls, and at least one icmp + // llvm-next: icmp + // CHECK-NOT: alloca + // CHECK-NOT: call a == b } diff --git a/tests/ui/consts/const-eval/ub-enum.rs b/tests/ui/consts/const-eval/ub-enum.rs index 61ab3581e3479..8feb78e0b11e8 100644 --- a/tests/ui/consts/const-eval/ub-enum.rs +++ b/tests/ui/consts/const-eval/ub-enum.rs @@ -108,4 +108,17 @@ const TEST_ICE_89765: () = { }; }; +// # Regression test for https://github.com/rust-lang/rust/issues/153758 +// Discriminants at i64::MIN and i64::MAX produce a wrapping valid_range that covers +// all values. A value like 0 passes the range check but doesn't match any variant. +#[repr(i64)] +#[derive(Copy, Clone)] +enum WideRangeDiscriminants { + A = i64::MIN, + B = i64::MAX, +} + +const BAD_WIDE_RANGE_ENUM: WideRangeDiscriminants = unsafe { mem::transmute(0_i64) }; +//~^ ERROR expected a valid enum tag + fn main() {} diff --git a/tests/ui/consts/const-eval/ub-enum.stderr b/tests/ui/consts/const-eval/ub-enum.stderr index a5ac10c7922c1..bb2c58796b1c4 100644 --- a/tests/ui/consts/const-eval/ub-enum.stderr +++ b/tests/ui/consts/const-eval/ub-enum.stderr @@ -129,6 +129,17 @@ LL | std::mem::discriminant(&*(&() as *const () as *const Never)); note: inside `discriminant::` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL -error: aborting due to 14 previous errors +error[E0080]: constructing invalid value of type WideRangeDiscriminants: at ., encountered 0x0, but expected a valid enum tag + --> $DIR/ub-enum.rs:121:1 + | +LL | const BAD_WIDE_RANGE_ENUM: WideRangeDiscriminants = unsafe { mem::transmute(0_i64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value + | + = note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) { + HEX_DUMP + } + +error: aborting due to 15 previous errors For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/generics/wrong-number-of-args.stderr b/tests/ui/generics/wrong-number-of-args.stderr index 554d017d67e33..953cd273eb764 100644 --- a/tests/ui/generics/wrong-number-of-args.stderr +++ b/tests/ui/generics/wrong-number-of-args.stderr @@ -28,8 +28,8 @@ LL | type E = Ty<>; | help: consider introducing a named lifetime parameter | -LL | type E<'a> = Ty<'a, >; - | ++++ +++ +LL | type E<'a> = Ty<'a>; + | ++++ ++ error[E0106]: missing lifetime specifier --> $DIR/wrong-number-of-args.rs:120:22 @@ -55,12 +55,12 @@ LL | type F = Box>; | help: consider making the bound lifetime-generic with a new `'a` lifetime | -LL | type F = Box GenericLifetime<'a, >>; - | +++++++ +++ +LL | type F = Box GenericLifetime<'a>>; + | +++++++ ++ help: consider introducing a named lifetime parameter | -LL | type F<'a> = Box>; - | ++++ +++ +LL | type F<'a> = Box>; + | ++++ ++ error[E0106]: missing lifetime specifier --> $DIR/wrong-number-of-args.rs:163:43 diff --git a/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs new file mode 100644 index 0000000000000..acf93ae51cc4d --- /dev/null +++ b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs @@ -0,0 +1,18 @@ +// Regression test for . +// +// When suggesting lifetime parameters for empty angle brackets like `Foo<>`, +// the suggestion should not include a trailing comma (e.g., `Foo<'a>` not `Foo<'a, >`). +// When there are other generic arguments like `Foo`, the trailing comma is needed +// (e.g., `Foo<'a, T>`). + +#![crate_type = "lib"] + +struct Foo<'a>(&'a ()); + +type A = Foo<>; +//~^ ERROR missing lifetime specifier [E0106] + +struct Bar<'a, T>(&'a T); + +type B = Bar; +//~^ ERROR missing lifetime specifier [E0106] diff --git a/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr new file mode 100644 index 0000000000000..f189c259d65f9 --- /dev/null +++ b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr @@ -0,0 +1,25 @@ +error[E0106]: missing lifetime specifier + --> $DIR/E0106-trailing-comma-in-lifetime-suggestion.rs:12:13 + | +LL | type A = Foo<>; + | ^ expected named lifetime parameter + | +help: consider introducing a named lifetime parameter + | +LL | type A<'a> = Foo<'a>; + | ++++ ++ + +error[E0106]: missing lifetime specifier + --> $DIR/E0106-trailing-comma-in-lifetime-suggestion.rs:17:13 + | +LL | type B = Bar; + | ^ expected named lifetime parameter + | +help: consider introducing a named lifetime parameter + | +LL | type B<'a> = Bar<'a, u8>; + | ++++ +++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0106`.