diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 8d187a4be8aee..37a4bf5fdcad7 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -1,44 +1,299 @@ -use rustc_ast::{ptr::P, Expr, Path}; +use crate::assert::expr_if_not; +use rustc_ast::{ + attr, + ptr::P, + token, + tokenstream::{DelimSpan, TokenStream, TokenTree}, + BorrowKind, Expr, ExprKind, ItemKind, MacArgs, MacCall, MacDelimiter, Mutability, Path, + PathSegment, Stmt, UseTree, UseTreeKind, DUMMY_NODE_ID, +}; +use rustc_ast_pretty::pprust; +use rustc_data_structures::fx::FxHashSet; use rustc_expand::base::ExtCtxt; -use rustc_span::Span; +use rustc_span::{ + symbol::{sym, Ident, Symbol}, + Span, +}; pub(super) struct Context<'cx, 'a> { + // Top-level `let captureN = Capture::new()` statements + capture_decls: Vec, cx: &'cx ExtCtxt<'a>, + // Formatting string used for debugging + fmt_string: String, + // Top-level `let __local_bindN = &expr` statements + local_bind_decls: Vec, + // Used to avoid capturing duplicated paths + // + // ```rust + // let a = 1i32; + // assert!(add(a, a) == 3); + // ``` + paths: FxHashSet, span: Span, } impl<'cx, 'a> Context<'cx, 'a> { pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self { - Self { cx, span } + Self { + capture_decls: <_>::default(), + cx, + fmt_string: <_>::default(), + local_bind_decls: <_>::default(), + paths: <_>::default(), + span, + } } - /// Builds the whole `assert!` expression. + /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to: /// + /// ```rust + /// let elem = 1; /// { - /// use ::core::asserting::{ ... }; + /// #[allow(unused_imports)] + /// use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable}; + /// let mut __capture0 = ::core::asserting::Capture::new(); + /// let __local_bind0 = &elem; + /// if !( + /// *{ + /// (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); + /// __local_bind0 + /// } == 1 + /// ) { + /// panic!("Assertion failed: elem == 1\nWith captures:\n elem = {}", __capture0) + /// } + /// } + /// ``` + pub(super) fn build(mut self, mut cond_expr: P, panic_path: Path) -> P { + let expr_str = pprust::expr_to_string(&cond_expr); + self.manage_cond_expr(&mut cond_expr); + let initial_imports = self.build_initial_imports(); + let panic = self.build_panic(&expr_str, panic_path); + + let Self { capture_decls, cx, local_bind_decls, span, .. } = self; + + let mut stmts = Vec::with_capacity(4); + stmts.push(initial_imports); + stmts.extend(capture_decls.into_iter().map(|c| c.decl)); + stmts.extend(local_bind_decls); + stmts.push(cx.stmt_expr(expr_if_not(cx, span, cond_expr, panic, None))); + cx.expr_block(cx.block(span, stmts)) + } + + /// Initial **trait** imports + /// + /// use ::core::asserting::{ ... }; + fn build_initial_imports(&self) -> Stmt { + let nested_tree = |this: &Self, sym| { + ( + UseTree { + prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]), + kind: UseTreeKind::Simple(None, DUMMY_NODE_ID, DUMMY_NODE_ID), + span: this.span, + }, + DUMMY_NODE_ID, + ) + }; + self.cx.stmt_item( + self.span, + self.cx.item( + self.span, + Ident::empty(), + vec![self.cx.attribute(attr::mk_list_item( + Ident::new(sym::allow, self.span), + vec![attr::mk_nested_word_item(Ident::new(sym::unused_imports, self.span))], + ))], + ItemKind::Use(UseTree { + prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])), + kind: UseTreeKind::Nested(vec![ + nested_tree(self, sym::TryCaptureGeneric), + nested_tree(self, sym::TryCapturePrintable), + ]), + span: self.span, + }), + ), + ) + } + + /// The necessary custom `panic!(...)` expression. + /// + /// panic!( + /// "Assertion failed: ... \n With expansion: ...", + /// __capture0, + /// ... + /// ); + fn build_panic(&self, expr_str: &str, panic_path: Path) -> P { + let escaped_expr_str = escape_to_fmt(expr_str); + let initial = [ + TokenTree::token( + token::Literal(token::Lit { + kind: token::LitKind::Str, + symbol: Symbol::intern(&if self.fmt_string.is_empty() { + format!("Assertion failed: {escaped_expr_str}") + } else { + format!( + "Assertion failed: {escaped_expr_str}\nWith captures:\n{}", + &self.fmt_string + ) + }), + suffix: None, + }), + self.span, + ), + TokenTree::token(token::Comma, self.span), + ]; + let captures = self.capture_decls.iter().flat_map(|cap| { + [ + TokenTree::token(token::Ident(cap.ident.name, false), cap.ident.span), + TokenTree::token(token::Comma, self.span), + ] + }); + self.cx.expr( + self.span, + ExprKind::MacCall(MacCall { + path: panic_path, + args: P(MacArgs::Delimited( + DelimSpan::from_single(self.span), + MacDelimiter::Parenthesis, + initial.into_iter().chain(captures).collect::(), + )), + prior_type_ascription: None, + }), + ) + } + + /// Recursive function called until `cond_expr` and `fmt_str` are fully modified. + /// + /// See [Self::manage_initial_capture] and [Self::manage_try_capture] + fn manage_cond_expr(&mut self, expr: &mut P) { + match (*expr).kind { + ExprKind::Binary(_, ref mut lhs, ref mut rhs) => { + self.manage_cond_expr(lhs); + self.manage_cond_expr(rhs); + } + ExprKind::Path(_, Path { ref segments, .. }) if let &[ref path_segment] = &segments[..] => { + let path_ident = path_segment.ident; + self.manage_initial_capture(expr, path_ident); + } + _ => {} + } + } + + /// Pushes the top-level declarations and modifies `expr` to try capturing variables. /// - /// let mut __capture0 = Capture::new(); - /// ... - /// ... - /// ... + /// `fmt_str`, the formatting string used for debugging, is constructed to show possible + /// captured variables. + fn manage_initial_capture(&mut self, expr: &mut P, path_ident: Ident) { + if self.paths.contains(&path_ident) { + return; + } else { + self.fmt_string.push_str(" "); + self.fmt_string.push_str(path_ident.as_str()); + self.fmt_string.push_str(" = {:?}\n"); + let _ = self.paths.insert(path_ident); + } + let curr_capture_idx = self.capture_decls.len(); + let capture_string = format!("__capture{curr_capture_idx}"); + let ident = Ident::new(Symbol::intern(&capture_string), self.span); + let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]); + let init = self.cx.expr_call( + self.span, + self.cx.expr_path(self.cx.path(self.span, init_std_path)), + vec![], + ); + let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident }; + self.capture_decls.push(capture); + self.manage_try_capture(ident, curr_capture_idx, expr); + } + + /// Tries to copy `__local_bindN` into `__captureN`. /// - /// if !{ - /// ... - /// ... - /// ... - /// } { - /// panic!( - /// "Assertion failed: ... \n With expansion: ...", - /// __capture0, - /// ... - /// ... - /// ... - /// ); - /// } + /// *{ + /// (&Wrapper(__local_bindN)).try_capture(&mut __captureN); + /// __local_bindN /// } - pub(super) fn build(self, _cond_expr: P, _panic_path: Path) -> P { - let Self { cx, span, .. } = self; - let stmts = Vec::new(); - cx.expr_block(cx.block(span, stmts)) + fn manage_try_capture(&mut self, capture: Ident, curr_capture_idx: usize, expr: &mut P) { + let local_bind_string = format!("__local_bind{curr_capture_idx}"); + let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span); + self.local_bind_decls.push(self.cx.stmt_let( + self.span, + false, + local_bind, + self.cx.expr_addr_of(self.span, expr.clone()), + )); + let wrapper = self.cx.expr_call( + self.span, + self.cx.expr_path( + self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])), + ), + vec![self.cx.expr_path(Path::from_ident(local_bind))], + ); + let try_capture_call = self + .cx + .stmt_expr(expr_method_call( + self.cx, + PathSegment { + args: None, + id: DUMMY_NODE_ID, + ident: Ident::new(sym::try_capture, self.span), + }, + vec![ + expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)), + expr_addr_of_mut( + self.cx, + self.span, + self.cx.expr_path(Path::from_ident(capture)), + ), + ], + self.span, + )) + .add_trailing_semicolon(); + let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind)); + let ret = self.cx.stmt_expr(local_bind_path); + let block = self.cx.expr_block(self.cx.block(self.span, vec![try_capture_call, ret])); + *expr = self.cx.expr_deref(self.span, block); + } +} + +/// Information about a captured element. +#[derive(Debug)] +struct Capture { + // Generated indexed `Capture` statement. + // + // `let __capture{} = Capture::new();` + decl: Stmt, + // The name of the generated indexed `Capture` variable. + // + // `__capture{}` + ident: Ident, +} + +/// Escapes to use as a formatting string. +fn escape_to_fmt(s: &str) -> String { + let mut rslt = String::with_capacity(s.len()); + for c in s.chars() { + rslt.extend(c.escape_debug()); + match c { + '{' | '}' => rslt.push(c), + _ => {} + } } + rslt +} + +fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P) -> P { + cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e)) +} + +fn expr_method_call( + cx: &ExtCtxt<'_>, + path: PathSegment, + args: Vec>, + span: Span, +) -> P { + cx.expr(span, ExprKind::MethodCall(path, args, span)) +} + +fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P) -> P { + cx.expr(sp, ExprKind::Paren(e)) } diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 124d0d18cdbe2..11565ba72d755 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -6,6 +6,7 @@ #![feature(array_windows)] #![feature(box_patterns)] #![feature(decl_macro)] +#![feature(if_let_guard)] #![feature(is_sorted)] #![feature(let_chains)] #![feature(let_else)] diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index 02d076c95ca52..fd4b2daae9c13 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -1,6 +1,7 @@ #![feature(let_chains)] #![feature(once_cell)] #![feature(path_try_exists)] +#![feature(rustc_attrs)] #![feature(type_alias_impl_trait)] use fluent_bundle::FluentResource; @@ -241,6 +242,7 @@ type FluentId = Cow<'static, str>; /// message so messages of this type must be combined with a `DiagnosticMessage` (using /// `DiagnosticMessage::with_subdiagnostic_message`) before rendering. However, subdiagnostics from /// the `SessionSubdiagnostic` derive refer to Fluent identifiers directly. +#[rustc_diagnostic_item = "SubdiagnosticMessage"] pub enum SubdiagnosticMessage { /// Non-translatable diagnostic message. // FIXME(davidtwco): can a `Cow<'static, str>` be used here? @@ -281,6 +283,7 @@ impl> From for SubdiagnosticMessage { /// /// Intended to be removed once diagnostics are entirely translatable. #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] +#[rustc_diagnostic_item = "DiagnosticMessage"] pub enum DiagnosticMessage { /// Non-translatable diagnostic message. // FIXME(davidtwco): can a `Cow<'static, str>` be used here? diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index eaceecc166778..00c0ff8bcaf9c 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -80,6 +80,7 @@ impl<'source> Into> for DiagnosticArgValue<'source> { /// Trait implemented by error types. This should not be implemented manually. Instead, use /// `#[derive(SessionSubdiagnostic)]` -- see [rustc_macros::SessionSubdiagnostic]. +#[rustc_diagnostic_item = "AddSubdiagnostic"] pub trait AddSubdiagnostic { /// Add a subdiagnostic to an existing diagnostic. fn add_to_diagnostic(self, diag: &mut Diagnostic); @@ -283,6 +284,7 @@ impl Diagnostic { /// /// This span is *not* considered a ["primary span"][`MultiSpan`]; only /// the `Span` supplied when creating the diagnostic is primary. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_label(&mut self, span: Span, label: impl Into) -> &mut Self { self.span.push_span_label(span, self.subdiagnostic_message_to_diagnostic_message(label)); self @@ -401,6 +403,7 @@ impl Diagnostic { } /// Add a note attached to this diagnostic. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn note(&mut self, msg: impl Into) -> &mut Self { self.sub(Level::Note, msg, MultiSpan::new(), None); self @@ -423,6 +426,7 @@ impl Diagnostic { /// Prints the span with a note above it. /// This is like [`Diagnostic::note()`], but it gets its own span. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_note>( &mut self, sp: S, @@ -444,6 +448,7 @@ impl Diagnostic { } /// Add a warning attached to this diagnostic. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn warn(&mut self, msg: impl Into) -> &mut Self { self.sub(Level::Warning, msg, MultiSpan::new(), None); self @@ -451,6 +456,7 @@ impl Diagnostic { /// Prints the span with a warning above it. /// This is like [`Diagnostic::warn()`], but it gets its own span. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_warn>( &mut self, sp: S, @@ -461,6 +467,7 @@ impl Diagnostic { } /// Add a help message attached to this diagnostic. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn help(&mut self, msg: impl Into) -> &mut Self { self.sub(Level::Help, msg, MultiSpan::new(), None); self @@ -474,6 +481,7 @@ impl Diagnostic { /// Prints the span with some help above it. /// This is like [`Diagnostic::help()`], but it gets its own span. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_help>( &mut self, sp: S, diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 83fe2a2df892f..8b6eba122f8f0 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -9,6 +9,7 @@ #![feature(let_else)] #![feature(never_type)] #![feature(adt_const_params)] +#![feature(rustc_attrs)] #![allow(incomplete_features)] #![allow(rustc::potential_query_instability)] @@ -648,6 +649,7 @@ impl Handler { /// Attempting to `.emit()` the builder will only emit if either: /// * `can_emit_warnings` is `true` /// * `is_force_warn` was set in `DiagnosticId::Lint` + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_span_warn( &self, span: impl Into, @@ -659,6 +661,7 @@ impl Handler { } /// Construct a builder at the `Allow` level at the given `span` and with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_span_allow( &self, span: impl Into, @@ -671,6 +674,7 @@ impl Handler { /// Construct a builder at the `Warning` level at the given `span` and with the `msg`. /// Also include a code. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_span_warn_with_code( &self, span: impl Into, @@ -687,16 +691,19 @@ impl Handler { /// Attempting to `.emit()` the builder will only emit if either: /// * `can_emit_warnings` is `true` /// * `is_force_warn` was set in `DiagnosticId::Lint` + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_warn(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Warning, msg) } /// Construct a builder at the `Allow` level with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_allow(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Allow, msg) } /// Construct a builder at the `Expect` level with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_expect( &self, msg: impl Into, @@ -706,6 +713,7 @@ impl Handler { } /// Construct a builder at the `Error` level at the given `span` and with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_span_err( &self, span: impl Into, @@ -717,6 +725,7 @@ impl Handler { } /// Construct a builder at the `Error` level at the given `span`, with the `msg`, and `code`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_span_err_with_code( &self, span: impl Into, @@ -730,6 +739,7 @@ impl Handler { /// Construct a builder at the `Error` level with the `msg`. // FIXME: This method should be removed (every error should have an associated error code). + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_err( &self, msg: impl Into, @@ -744,6 +754,7 @@ impl Handler { } /// Construct a builder at the `Error` level with the `msg` and the `code`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_err_with_code( &self, msg: impl Into, @@ -755,6 +766,7 @@ impl Handler { } /// Construct a builder at the `Warn` level with the `msg` and the `code`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_warn_with_code( &self, msg: impl Into, @@ -766,6 +778,7 @@ impl Handler { } /// Construct a builder at the `Fatal` level at the given `span` and with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_span_fatal( &self, span: impl Into, @@ -777,6 +790,7 @@ impl Handler { } /// Construct a builder at the `Fatal` level at the given `span`, with the `msg`, and `code`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_span_fatal_with_code( &self, span: impl Into, @@ -789,16 +803,19 @@ impl Handler { } /// Construct a builder at the `Error` level with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_fatal(&self, msg: impl Into) -> DiagnosticBuilder<'_, !> { DiagnosticBuilder::new_fatal(self, msg) } /// Construct a builder at the `Help` level with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_help(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { DiagnosticBuilder::new(self, Level::Help, msg) } /// Construct a builder at the `Note` level with the `msg`. + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_note_without_error( &self, msg: impl Into, @@ -806,11 +823,13 @@ impl Handler { DiagnosticBuilder::new(self, Level::Note, msg) } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_fatal(&self, span: impl Into, msg: impl Into) -> ! { self.emit_diag_at_span(Diagnostic::new(Fatal, msg), span); FatalError.raise() } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_fatal_with_code( &self, span: impl Into, @@ -821,6 +840,7 @@ impl Handler { FatalError.raise() } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_err( &self, span: impl Into, @@ -829,6 +849,7 @@ impl Handler { self.emit_diag_at_span(Diagnostic::new(Error { lint: false }, msg), span).unwrap() } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_err_with_code( &self, span: impl Into, @@ -841,10 +862,12 @@ impl Handler { ); } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_warn(&self, span: impl Into, msg: impl Into) { self.emit_diag_at_span(Diagnostic::new(Warning, msg), span); } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn span_warn_with_code( &self, span: impl Into, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 5eb2be97f8b92..34c53597dde6b 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -615,6 +615,9 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // 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, INTERNAL_UNSTABLE), + // Used by the `rustc::untranslatable_diagnostic` and `rustc::diagnostic_outside_of_impl` lints + // to assist in changes to diagnostic APIs. + rustc_attr!(rustc_lint_diagnostics, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), // ========================================================================== // Internal attributes, Const related: diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 5088fdf130e87..fadb1c8793398 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -5,12 +5,14 @@ use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext} use rustc_ast as ast; use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::{Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath}; -use rustc_hir::{HirId, Item, ItemKind, Node, Pat, Ty, TyKind}; +use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath}; +use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::Span; +use tracing::debug; declare_tool_lint! { pub rustc::DEFAULT_HASH_TYPES, @@ -46,6 +48,41 @@ impl LateLintPass<'_> for DefaultHashTypes { } } +/// Helper function for lints that check for expressions with calls and use typeck results to +/// get the `DefId` and `SubstsRef` of the function. +fn typeck_results_of_method_fn<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'_>, +) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> { + // FIXME(rustdoc): Lints which use this function use typecheck results which can cause + // `rustdoc` to error if there are resolution failures. + // + // As internal lints are currently always run if there are `unstable_options`, they are added + // to the lint store of rustdoc. Internal lints are also not used via the `lint_mod` query. + // Crate lints run outside of a query so rustdoc currently doesn't disable them. + // + // Instead of relying on this, either change crate lints to a query disabled by rustdoc, only + // run internal lints if the user is explicitly opting in or figure out a different way to + // avoid running lints for rustdoc. + if cx.tcx.sess.opts.actually_rustdoc { + return None; + } + + match expr.kind { + ExprKind::MethodCall(segment, _, _) + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => + { + Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id))) + }, + _ => { + match cx.typeck_results().node_type(expr.hir_id).kind() { + &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)), + _ => None, + } + } + } +} + declare_tool_lint! { pub rustc::POTENTIAL_QUERY_INSTABILITY, Allow, @@ -57,35 +94,7 @@ declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]); impl LateLintPass<'_> for QueryStability { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - // FIXME(rustdoc): This lint uses typecheck results, causing rustdoc to - // error if there are resolution failures. - // - // As internal lints are currently always run if there are `unstable_options`, - // they are added to the lint store of rustdoc. Internal lints are also - // not used via the `lint_mod` query. Crate lints run outside of a query - // so rustdoc currently doesn't disable them. - // - // Instead of relying on this, either change crate lints to a query disabled by - // rustdoc, only run internal lints if the user is explicitly opting in - // or figure out a different way to avoid running lints for rustdoc. - if cx.tcx.sess.opts.actually_rustdoc { - return; - } - - let (span, def_id, substs) = match expr.kind { - ExprKind::MethodCall(segment, _, _) - if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => - { - (segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id)) - }, - _ => { - let &ty::FnDef(def_id, substs) = - cx.typeck_results() - .node_type(expr.hir_id) - .kind() else { return }; - (expr.span, def_id, substs) - } - }; + let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) { let def_id = instance.def_id(); if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) { @@ -376,3 +385,70 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword { } } } + +declare_tool_lint! { + pub rustc::UNTRANSLATABLE_DIAGNOSTIC, + Allow, + "prevent creation of diagnostics which cannot be translated", + report_in_external_macro: true +} + +declare_tool_lint! { + pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL, + Allow, + "prevent creation of diagnostics outside of `SessionDiagnostic`/`AddSubdiagnostic` impls", + report_in_external_macro: true +} + +declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]); + +impl LateLintPass<'_> for Diagnostics { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; + debug!(?span, ?def_id, ?substs); + if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) && + !cx.tcx.has_attr(instance.def_id(), sym::rustc_lint_diagnostics) + { + return; + } + + let mut found_impl = false; + for (_, parent) in cx.tcx.hir().parent_iter(expr.hir_id) { + debug!(?parent); + if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent && + let Impl { of_trait: Some(of_trait), .. } = impl_ && + let Some(def_id) = of_trait.trait_def_id() && + let Some(name) = cx.tcx.get_diagnostic_name(def_id) && + matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic) + { + found_impl = true; + break; + } + } + debug!(?found_impl); + if !found_impl { + cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| { + lint.build("diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls") + .emit(); + }) + } + + let mut found_diagnostic_message = false; + for ty in substs.types() { + debug!(?ty); + if let Some(adt_def) = ty.ty_adt_def() && + let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) && + matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage) + { + found_diagnostic_message = true; + break; + } + } + debug!(?found_diagnostic_message); + if !found_diagnostic_message { + cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| { + lint.build("diagnostics should be created using translatable messages").emit(); + }) + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index ff4ed94fab339..f0182883d2b46 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -508,6 +508,8 @@ fn register_internals(store: &mut LintStore) { store.register_late_pass(|| Box::new(ExistingDocKeyword)); store.register_lints(&TyTyKind::get_lints()); store.register_late_pass(|| Box::new(TyTyKind)); + store.register_lints(&Diagnostics::get_lints()); + store.register_late_pass(|| Box::new(Diagnostics)); store.register_lints(&PassByValue::get_lints()); store.register_late_pass(|| Box::new(PassByValue)); store.register_group( diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a3b586a41452d..5c3e7918aa3ff 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -118,6 +118,9 @@ impl CheckAttrVisitor<'_> { sym::rustc_lint_query_instability => { self.check_rustc_lint_query_instability(&attr, span, target) } + sym::rustc_lint_diagnostics => { + self.check_rustc_lint_diagnostics(&attr, span, target) + } sym::rustc_clean | sym::rustc_dirty | sym::rustc_if_this_changed @@ -1624,12 +1627,9 @@ impl CheckAttrVisitor<'_> { } } - fn check_rustc_lint_query_instability( - &self, - attr: &Attribute, - span: Span, - target: Target, - ) -> bool { + /// Helper function for checking that the provided attribute is only applied to a function or + /// method. + fn check_applied_to_fn_or_method(&self, attr: &Attribute, span: Span, target: Target) -> bool { let is_function = matches!(target, Target::Fn | Target::Method(..)); if !is_function { self.tcx @@ -1643,6 +1643,23 @@ impl CheckAttrVisitor<'_> { } } + /// Checks that the `#[rustc_lint_query_instability]` attribute is only applied to a function + /// or method. + fn check_rustc_lint_query_instability( + &self, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + self.check_applied_to_fn_or_method(attr, span, target) + } + + /// Checks that the `#[rustc_lint_diagnostics]` attribute is only applied to a function or + /// method. + fn check_rustc_lint_diagnostics(&self, attr: &Attribute, span: Span, target: Target) -> bool { + self.check_applied_to_fn_or_method(attr, span, target) + } + /// Checks that the dep-graph debugging attributes are only present when the query-dep-graph /// option is passed to the compiler. fn check_rustc_dirty_clean(&self, attr: &Attribute) -> bool { diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index 59b4981dd0120..044be906b5507 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -5,6 +5,7 @@ #![feature(never_type)] #![feature(once_cell)] #![feature(option_get_or_insert_default)] +#![feature(rustc_attrs)] #![recursion_limit = "256"] #![allow(rustc::potential_query_instability)] diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 6fb87e15a3303..a5ccae047fcc3 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -311,6 +311,7 @@ impl ParseSess { self.create_warning(warning).emit() } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_err( &self, msg: impl Into, @@ -318,6 +319,7 @@ impl ParseSess { self.span_diagnostic.struct_err(msg) } + #[cfg_attr(not(bootstrap), rustc_lint_diagnostics)] pub fn struct_warn(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { self.span_diagnostic.struct_warn(msg) } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index b2c23cda6aae5..b1d1f9e7a6ce0 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -209,6 +209,7 @@ pub struct PerfStats { /// Trait implemented by error types. This should not be implemented manually. Instead, use /// `#[derive(SessionDiagnostic)]` -- see [rustc_macros::SessionDiagnostic]. +#[rustc_diagnostic_item = "SessionDiagnostic"] pub trait SessionDiagnostic<'a, T: EmissionGuarantee = ErrorGuaranteed> { /// Write out as a diagnostic out of `sess`. #[must_use] diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7b0fa65e8086b..c35a11ff7619e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -125,6 +125,7 @@ symbols! { Symbols { AcqRel, Acquire, + AddSubdiagnostic, Alignment, Any, Arc, @@ -156,6 +157,7 @@ symbols! { C, CStr, CString, + Capture, Center, Clone, Continue, @@ -169,6 +171,7 @@ symbols! { Decoder, Default, Deref, + DiagnosticMessage, DirBuilder, Display, DoubleEndedIterator, @@ -253,16 +256,20 @@ symbols! { RustcEncodable, Send, SeqCst, + SessionDiagnostic, SliceIndex, Some, String, StructuralEq, StructuralPartialEq, + SubdiagnosticMessage, Sync, Target, ToOwned, ToString, Try, + TryCaptureGeneric, + TryCapturePrintable, TryFrom, TryInto, Ty, @@ -272,6 +279,7 @@ symbols! { UnsafeArg, Vec, VecDeque, + Wrapper, Yield, _DECLS, _Self, @@ -354,6 +362,7 @@ symbols! { assert_receiver_is_total_eq, assert_uninit_valid, assert_zero_valid, + asserting, associated_const_equality, associated_consts, associated_type_bounds, @@ -1205,6 +1214,7 @@ symbols! { rustc_layout_scalar_valid_range_end, rustc_layout_scalar_valid_range_start, rustc_legacy_const_generics, + rustc_lint_diagnostics, rustc_lint_query_instability, rustc_macro_transparency, rustc_main, @@ -1432,6 +1442,7 @@ symbols! { truncf32, truncf64, try_blocks, + try_capture, try_from, try_into, try_trait_v2, @@ -1494,6 +1505,7 @@ symbols! { unsized_tuple_coercion, unstable, untagged_unions, + unused_imports, unused_qualifications, unwind, unwind_attributes, diff --git a/library/alloc/src/collections/btree/fix.rs b/library/alloc/src/collections/btree/fix.rs index c4861817dd05d..fd73fde2acb2d 100644 --- a/library/alloc/src/collections/btree/fix.rs +++ b/library/alloc/src/collections/btree/fix.rs @@ -91,8 +91,8 @@ impl Root { } } - /// Stock up any underfull nodes on the right border of the tree. - /// The other nodes, those that are not the root nor a rightmost edge, + /// Stocks up any underfull nodes on the right border of the tree. + /// The other nodes, those that are neither the root nor a rightmost edge, /// must be prepared to have up to MIN_LEN elements stolen. pub fn fix_right_border_of_plentiful(&mut self) { let mut cur_node = self.borrow_mut(); diff --git a/library/alloc/src/collections/btree/map/entry.rs b/library/alloc/src/collections/btree/map/entry.rs index cacd06b5df153..e2749aac694c8 100644 --- a/library/alloc/src/collections/btree/map/entry.rs +++ b/library/alloc/src/collections/btree/map/entry.rs @@ -315,7 +315,7 @@ impl<'a, K: Ord, V> VacantEntry<'a, K, V> { pub fn insert(self, value: V) -> &'a mut V { let out_ptr = match self.handle { None => { - // SAFETY: We have consumed self.handle and the reference returned. + // SAFETY: There is no tree yet so no reference to it exists. let map = unsafe { self.dormant_map.awaken() }; let mut root = NodeRef::new_leaf(); let val_ptr = root.borrow_mut().push(self.key, value) as *mut V; @@ -325,16 +325,17 @@ impl<'a, K: Ord, V> VacantEntry<'a, K, V> { } Some(handle) => match handle.insert_recursing(self.key, value) { (None, val_ptr) => { - // SAFETY: We have consumed self.handle and the handle returned. + // SAFETY: We have consumed self.handle. let map = unsafe { self.dormant_map.awaken() }; map.length += 1; val_ptr } (Some(ins), val_ptr) => { drop(ins.left); - // SAFETY: We have consumed self.handle and the reference returned. + // SAFETY: We have consumed self.handle and dropped the + // remaining reference to the tree, ins.left. let map = unsafe { self.dormant_map.awaken() }; - let root = map.root.as_mut().unwrap(); + let root = map.root.as_mut().unwrap(); // same as ins.left root.push_internal_level().push(ins.kv.0, ins.kv.1, ins.right); map.length += 1; val_ptr diff --git a/library/std/src/sys/windows/compat.rs b/library/std/src/sys/windows/compat.rs index c55df04200313..ded97bb7eaaed 100644 --- a/library/std/src/sys/windows/compat.rs +++ b/library/std/src/sys/windows/compat.rs @@ -102,21 +102,23 @@ macro_rules! compat_fn { } #[allow(dead_code)] + #[inline(always)] pub fn option() -> Option { - unsafe { PTR } + unsafe { + if cfg!(miri) { + // Miri does not run `init`, so we just call `get_f` each time. + get_f() + } else { + PTR + } + } } #[allow(dead_code)] pub unsafe fn call($($argname: $argtype),*) -> $rettype { - if let Some(ptr) = PTR { + if let Some(ptr) = option() { return ptr($($argname),*); } - if cfg!(miri) { - // Miri does not run `init`, so we just call `get_f` each time. - if let Some(ptr) = get_f() { - return ptr($($argname),*); - } - } $fallback_body } } diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index b7789493df647..394db2d0cda6b 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -886,9 +886,9 @@ fn fmt_type<'cx>( primitive_link(f, PrimitiveType::Slice, &format!("[{name}]"), cx) } _ => { - primitive_link(f, PrimitiveType::Slice, "[", cx)?; + write!(f, "[")?; fmt::Display::fmt(&t.print(cx), f)?; - primitive_link(f, PrimitiveType::Slice, "]", cx) + write!(f, "]") } }, clean::Array(ref t, ref n) => { @@ -926,31 +926,6 @@ fn fmt_type<'cx>( let m = mutability.print_with_space(); let amp = if f.alternate() { "&".to_string() } else { "&".to_string() }; match **ty { - clean::Slice(ref bt) => { - // `BorrowedRef{ ... Slice(T) }` is `&[T]` - match **bt { - clean::Generic(name) => primitive_link( - f, - PrimitiveType::Slice, - &format!("{amp}{lt}{m}[{name}]"), - cx, - ), - _ => { - primitive_link( - f, - PrimitiveType::Slice, - &format!("{}{}{}[", amp, lt, m), - cx, - )?; - if f.alternate() { - write!(f, "{:#}", bt.print(cx))?; - } else { - write!(f, "{}", bt.print(cx))?; - } - primitive_link(f, PrimitiveType::Slice, "]", cx) - } - } - } clean::DynTrait(ref bounds, ref trait_lt) if bounds.len() > 1 || trait_lt.is_some() => { diff --git a/src/test/rustdoc/slice-links.link_box_u32.html b/src/test/rustdoc/slice-links.link_box_u32.html index 42fd721a4acf1..7bec7582df7c9 100644 --- a/src/test/rustdoc/slice-links.link_box_u32.html +++ b/src/test/rustdoc/slice-links.link_box_u32.html @@ -1 +1 @@ -pub fn gamma() -> MyBox<[u32]> \ No newline at end of file +pub fn gamma() -> MyBox<[u32]> \ No newline at end of file diff --git a/src/test/rustdoc/slice-links.link_slice_generic.html b/src/test/rustdoc/slice-links.link_slice_generic.html index fe79ca7a82da3..1d0f2bf75a233 100644 --- a/src/test/rustdoc/slice-links.link_slice_generic.html +++ b/src/test/rustdoc/slice-links.link_slice_generic.html @@ -1 +1 @@ -pub fn beta<T>() -> &'static [T] \ No newline at end of file +pub fn beta<T>() -> &'static [T] \ No newline at end of file diff --git a/src/test/rustdoc/slice-links.link_slice_u32.html b/src/test/rustdoc/slice-links.link_slice_u32.html index c7e430b0607f7..c86d383042615 100644 --- a/src/test/rustdoc/slice-links.link_slice_u32.html +++ b/src/test/rustdoc/slice-links.link_slice_u32.html @@ -1 +1 @@ -pub fn alpha() -> &'static [u32] \ No newline at end of file +pub fn alpha() -> &'static [u32] \ No newline at end of file diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics.rs b/src/test/ui-fulldeps/internal-lints/diagnostics.rs new file mode 100644 index 0000000000000..817d8531da905 --- /dev/null +++ b/src/test/ui-fulldeps/internal-lints/diagnostics.rs @@ -0,0 +1,73 @@ +// compile-flags: -Z unstable-options + +#![crate_type = "lib"] +#![feature(rustc_private)] +#![deny(rustc::untranslatable_diagnostic)] +#![deny(rustc::diagnostic_outside_of_impl)] + +extern crate rustc_errors; +extern crate rustc_macros; +extern crate rustc_session; +extern crate rustc_span; + +use rustc_errors::{AddSubdiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, fluent}; +use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic}; +use rustc_session::{parse::ParseSess, SessionDiagnostic}; +use rustc_span::Span; + +#[derive(SessionDiagnostic)] +#[error(slug = "parser-expect-path")] +struct DeriveSessionDiagnostic { + #[primary_span] + span: Span, +} + +#[derive(SessionSubdiagnostic)] +#[note(slug = "note")] +struct Note { + #[primary_span] + span: Span, +} + +pub struct UntranslatableInSessionDiagnostic; + +impl<'a> SessionDiagnostic<'a, ErrorGuaranteed> for UntranslatableInSessionDiagnostic { + fn into_diagnostic(self, sess: &'a ParseSess) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + sess.struct_err("untranslatable diagnostic") + //~^ ERROR diagnostics should be created using translatable messages + } +} + +pub struct TranslatableInSessionDiagnostic; + +impl<'a> SessionDiagnostic<'a, ErrorGuaranteed> for TranslatableInSessionDiagnostic { + fn into_diagnostic(self, sess: &'a ParseSess) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + sess.struct_err(fluent::parser::expect_path) + } +} + +pub struct UntranslatableInAddSubdiagnostic; + +impl AddSubdiagnostic for UntranslatableInAddSubdiagnostic { + fn add_to_diagnostic(self, diag: &mut Diagnostic) { + diag.note("untranslatable diagnostic"); + //~^ ERROR diagnostics should be created using translatable messages + } +} + +pub struct TranslatableInAddSubdiagnostic; + +impl AddSubdiagnostic for TranslatableInAddSubdiagnostic { + fn add_to_diagnostic(self, diag: &mut Diagnostic) { + diag.note(fluent::typeck::note); + } +} + +pub fn make_diagnostics<'a>(sess: &'a ParseSess) { + let _diag = sess.struct_err(fluent::parser::expect_path); + //~^ ERROR diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls + + let _diag = sess.struct_err("untranslatable diagnostic"); + //~^ ERROR diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls + //~^^ ERROR diagnostics should be created using translatable messages +} diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics.stderr b/src/test/ui-fulldeps/internal-lints/diagnostics.stderr new file mode 100644 index 0000000000000..bae78ffdc021b --- /dev/null +++ b/src/test/ui-fulldeps/internal-lints/diagnostics.stderr @@ -0,0 +1,44 @@ +error: diagnostics should be created using translatable messages + --> $DIR/diagnostics.rs:36:14 + | +LL | sess.struct_err("untranslatable diagnostic") + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/diagnostics.rs:5:9 + | +LL | #![deny(rustc::untranslatable_diagnostic)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: diagnostics should be created using translatable messages + --> $DIR/diagnostics.rs:53:14 + | +LL | diag.note("untranslatable diagnostic"); + | ^^^^ + +error: diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls + --> $DIR/diagnostics.rs:67:22 + | +LL | let _diag = sess.struct_err(fluent::parser::expect_path); + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/diagnostics.rs:6:9 + | +LL | #![deny(rustc::diagnostic_outside_of_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls + --> $DIR/diagnostics.rs:70:22 + | +LL | let _diag = sess.struct_err("untranslatable diagnostic"); + | ^^^^^^^^^^ + +error: diagnostics should be created using translatable messages + --> $DIR/diagnostics.rs:70:22 + | +LL | let _diag = sess.struct_err("untranslatable diagnostic"); + | ^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.rs b/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.rs new file mode 100644 index 0000000000000..99f99ffcd3597 --- /dev/null +++ b/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.rs @@ -0,0 +1,15 @@ +// compile-flags: -Z unstable-options + +#![feature(rustc_attrs)] + +#[rustc_lint_diagnostics] +//~^ ERROR attribute should be applied to a function +struct Foo; + +impl Foo { + #[rustc_lint_diagnostics(a)] + //~^ ERROR malformed `rustc_lint_diagnostics` + fn bar() {} +} + +fn main() {} diff --git a/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.stderr b/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.stderr new file mode 100644 index 0000000000000..46c206f3bf9fb --- /dev/null +++ b/src/test/ui-fulldeps/internal-lints/diagnostics_incorrect.stderr @@ -0,0 +1,17 @@ +error: malformed `rustc_lint_diagnostics` attribute input + --> $DIR/diagnostics_incorrect.rs:10:5 + | +LL | #[rustc_lint_diagnostics(a)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_lint_diagnostics]` + +error: attribute should be applied to a function + --> $DIR/diagnostics_incorrect.rs:5:1 + | +LL | #[rustc_lint_diagnostics] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | struct Foo; + | ----------- not a function + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/macros/assert-trailing-junk.rs b/src/test/ui/macros/assert-trailing-junk.rs index cd7faf9bf8bfb..da725e19e2ada 100644 --- a/src/test/ui/macros/assert-trailing-junk.rs +++ b/src/test/ui/macros/assert-trailing-junk.rs @@ -1,3 +1,6 @@ +// revisions: with-generic-asset without-generic-asset +// [with-generic-asset] compile-flags: --cfg feature="generic_assert" + // Ensure assert macro does not ignore trailing garbage. // // See https://github.com/rust-lang/rust/issues/60024 for details. diff --git a/src/test/ui/macros/assert-trailing-junk.stderr b/src/test/ui/macros/assert-trailing-junk.with-generic-asset.stderr similarity index 86% rename from src/test/ui/macros/assert-trailing-junk.stderr rename to src/test/ui/macros/assert-trailing-junk.with-generic-asset.stderr index eb001429c5522..09dd16a0b0d89 100644 --- a/src/test/ui/macros/assert-trailing-junk.stderr +++ b/src/test/ui/macros/assert-trailing-junk.with-generic-asset.stderr @@ -1,17 +1,17 @@ error: expected one of `,`, `.`, `?`, or an operator, found `some` - --> $DIR/assert-trailing-junk.rs:6:18 + --> $DIR/assert-trailing-junk.rs:9:18 | LL | assert!(true some extra junk, "whatever"); | ^^^^ expected one of `,`, `.`, `?`, or an operator error: expected one of `,`, `.`, `?`, or an operator, found `some` - --> $DIR/assert-trailing-junk.rs:9:18 + --> $DIR/assert-trailing-junk.rs:12:18 | LL | assert!(true some extra junk); | ^^^^ expected one of `,`, `.`, `?`, or an operator error: no rules expected the token `blah` - --> $DIR/assert-trailing-junk.rs:12:30 + --> $DIR/assert-trailing-junk.rs:15:30 | LL | assert!(true, "whatever" blah); | -^^^^ no rules expected this token in macro call @@ -19,7 +19,7 @@ LL | assert!(true, "whatever" blah); | help: missing comma here error: unexpected string literal - --> $DIR/assert-trailing-junk.rs:15:18 + --> $DIR/assert-trailing-junk.rs:18:18 | LL | assert!(true "whatever" blah); | -^^^^^^^^^^ @@ -27,7 +27,7 @@ LL | assert!(true "whatever" blah); | help: try adding a comma error: no rules expected the token `blah` - --> $DIR/assert-trailing-junk.rs:15:29 + --> $DIR/assert-trailing-junk.rs:18:29 | LL | assert!(true "whatever" blah); | -^^^^ no rules expected this token in macro call @@ -35,7 +35,7 @@ LL | assert!(true "whatever" blah); | help: missing comma here error: macro requires an expression as an argument - --> $DIR/assert-trailing-junk.rs:19:5 + --> $DIR/assert-trailing-junk.rs:22:5 | LL | assert!(true;); | ^^^^^^^^^^^^-^ @@ -43,7 +43,7 @@ LL | assert!(true;); | help: try removing semicolon error: unexpected string literal - --> $DIR/assert-trailing-junk.rs:22:27 + --> $DIR/assert-trailing-junk.rs:25:27 | LL | assert!(false || true "error message"); | -^^^^^^^^^^^^^^^ diff --git a/src/test/ui/macros/assert-trailing-junk.without-generic-asset.stderr b/src/test/ui/macros/assert-trailing-junk.without-generic-asset.stderr new file mode 100644 index 0000000000000..09dd16a0b0d89 --- /dev/null +++ b/src/test/ui/macros/assert-trailing-junk.without-generic-asset.stderr @@ -0,0 +1,54 @@ +error: expected one of `,`, `.`, `?`, or an operator, found `some` + --> $DIR/assert-trailing-junk.rs:9:18 + | +LL | assert!(true some extra junk, "whatever"); + | ^^^^ expected one of `,`, `.`, `?`, or an operator + +error: expected one of `,`, `.`, `?`, or an operator, found `some` + --> $DIR/assert-trailing-junk.rs:12:18 + | +LL | assert!(true some extra junk); + | ^^^^ expected one of `,`, `.`, `?`, or an operator + +error: no rules expected the token `blah` + --> $DIR/assert-trailing-junk.rs:15:30 + | +LL | assert!(true, "whatever" blah); + | -^^^^ no rules expected this token in macro call + | | + | help: missing comma here + +error: unexpected string literal + --> $DIR/assert-trailing-junk.rs:18:18 + | +LL | assert!(true "whatever" blah); + | -^^^^^^^^^^ + | | + | help: try adding a comma + +error: no rules expected the token `blah` + --> $DIR/assert-trailing-junk.rs:18:29 + | +LL | assert!(true "whatever" blah); + | -^^^^ no rules expected this token in macro call + | | + | help: missing comma here + +error: macro requires an expression as an argument + --> $DIR/assert-trailing-junk.rs:22:5 + | +LL | assert!(true;); + | ^^^^^^^^^^^^-^ + | | + | help: try removing semicolon + +error: unexpected string literal + --> $DIR/assert-trailing-junk.rs:25:27 + | +LL | assert!(false || true "error message"); + | -^^^^^^^^^^^^^^^ + | | + | help: try adding a comma + +error: aborting due to 7 previous errors + diff --git a/src/test/ui/macros/assert.rs b/src/test/ui/macros/assert.rs index 71b0dbb19e262..a314db907b8a2 100644 --- a/src/test/ui/macros/assert.rs +++ b/src/test/ui/macros/assert.rs @@ -1,3 +1,6 @@ +// revisions: with-generic-asset without-generic-asset +// [with-generic-asset] compile-flags: --cfg feature="generic_assert" + fn main() { assert!(); //~ ERROR requires a boolean expression assert!(struct); //~ ERROR expected expression diff --git a/src/test/ui/macros/assert.stderr b/src/test/ui/macros/assert.with-generic-asset.stderr similarity index 87% rename from src/test/ui/macros/assert.stderr rename to src/test/ui/macros/assert.with-generic-asset.stderr index 57e5c77a56692..51d8f28a35c39 100644 --- a/src/test/ui/macros/assert.stderr +++ b/src/test/ui/macros/assert.with-generic-asset.stderr @@ -1,17 +1,17 @@ error: macro requires a boolean expression as an argument - --> $DIR/assert.rs:2:5 + --> $DIR/assert.rs:5:5 | LL | assert!(); | ^^^^^^^^^ boolean expression required error: expected expression, found keyword `struct` - --> $DIR/assert.rs:3:13 + --> $DIR/assert.rs:6:13 | LL | assert!(struct); | ^^^^^^ expected expression error: macro requires a boolean expression as an argument - --> $DIR/assert.rs:4:5 + --> $DIR/assert.rs:7:5 | LL | debug_assert!(); | ^^^^^^^^^^^^^^^ boolean expression required @@ -19,7 +19,7 @@ LL | debug_assert!(); = note: this error originates in the macro `debug_assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected expression, found keyword `struct` - --> $DIR/assert.rs:5:19 + --> $DIR/assert.rs:8:19 | LL | debug_assert!(struct); | ^^^^^^ expected expression diff --git a/src/test/ui/macros/assert.without-generic-asset.stderr b/src/test/ui/macros/assert.without-generic-asset.stderr new file mode 100644 index 0000000000000..51d8f28a35c39 --- /dev/null +++ b/src/test/ui/macros/assert.without-generic-asset.stderr @@ -0,0 +1,28 @@ +error: macro requires a boolean expression as an argument + --> $DIR/assert.rs:5:5 + | +LL | assert!(); + | ^^^^^^^^^ boolean expression required + +error: expected expression, found keyword `struct` + --> $DIR/assert.rs:6:13 + | +LL | assert!(struct); + | ^^^^^^ expected expression + +error: macro requires a boolean expression as an argument + --> $DIR/assert.rs:7:5 + | +LL | debug_assert!(); + | ^^^^^^^^^^^^^^^ boolean expression required + | + = note: this error originates in the macro `debug_assert` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found keyword `struct` + --> $DIR/assert.rs:8:19 + | +LL | debug_assert!(struct); + | ^^^^^^ expected expression + +error: aborting due to 4 previous errors + diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/all-expr-kinds.rs b/src/test/ui/rfc-2011-nicer-assert-messages/all-expr-kinds.rs new file mode 100644 index 0000000000000..14533ec2481d5 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/all-expr-kinds.rs @@ -0,0 +1,142 @@ +// edition:2021 +// ignore-tidy-linelength +// run-pass + +#![allow(path_statements, unused_allocation)] +#![feature(box_syntax, core_intrinsics, generic_assert, generic_assert_internals)] + +macro_rules! test { + ( + let mut $elem_ident:ident = $elem_expr:expr; + [ $($assert:tt)* ] => $msg:literal + ) => { + { + #[allow(unused_assignments, unused_mut, unused_variables)] + let rslt = std::panic::catch_unwind(|| { + let mut $elem_ident = $elem_expr; + assert!($($assert)*); + }); + let err = rslt.unwrap_err(); + if let Some(elem) = err.downcast_ref::() { + assert_eq!(elem, &$msg); + } + else if let Some(elem) = err.downcast_ref::<&str>() { + assert_eq!(elem, &$msg); + } + else { + panic!("assert!( ... ) should return a string"); + } + } + } +} + +macro_rules! tests { + ( + let mut $elem_ident:ident = $elem_expr:expr; + + $( + [ $($elem_assert:tt)* ] => $elem_msg:literal + )+ + ) => { + $( + test!( + let mut $elem_ident = $elem_expr; + [ $($elem_assert)* ] => $elem_msg + ); + )+ + } +} + +const FOO: Foo = Foo { bar: 1 }; + +#[derive(Clone, Copy, Debug, PartialEq)] +struct Foo { + bar: i32 +} + +fn main() { + // ***** Allowed ***** + + tests!( + let mut elem = 1i32; + + // binary + [ elem + 1 == 3 ] => "Assertion failed: elem + 1 == 3\nWith captures:\n elem = 1\n" + ); + + // ***** Disallowed ***** + + tests!( + let mut elem = 1i32; + + // assign + [ { let local = elem; local } == 3 ] => "Assertion failed: { let local = elem; local } == 3" + + // assign op + [ { elem += 1; elem } == 3 ] => "Assertion failed: { elem += 1; elem } == 3" + + // async + [ { let _ = async { elem }; elem } == 3 ] => "Assertion failed: { let _ = async { elem }; elem } == 3" + + // await + + // block + [ { elem } == 3 ] => "Assertion failed: { elem } == 3" + + // box + [ box elem == box 3 ] => "Assertion failed: box elem == box 3" + + // break + [ loop { break elem; } == 3 ] => "Assertion failed: loop { break elem; } == 3" + + // closure + [(|| elem)() == 3 ] => "Assertion failed: (|| elem)() == 3" + + // const block + + // continue + + // err + + // field + [ FOO.bar == 3 ] => "Assertion failed: FOO.bar == 3" + + // for loop + [ { for _ in 0..elem { elem; } elem } == 3 ] => "Assertion failed: { for _ in 0..elem { elem; } elem } == 3" + + // if + [ if true { elem } else { elem } == 3 ] => "Assertion failed: if true { elem } else { elem } == 3" + + // inline asm + + // let + [ if let true = true { elem } else { elem } == 3 ] => "Assertion failed: if let true = true { elem } else { elem } == 3" + + // lit + + // loop + [ loop { elem; break elem; } == 3 ] => "Assertion failed: loop { elem; break elem; } == 3" + + // mac call + + // match + [ match elem { _ => elem } == 3 ] => "Assertion failed: match elem { _ => elem, } == 3" + + // ret + [ (|| { return elem; })() == 3 ] => "Assertion failed: (|| { return elem; })() == 3" + + // try + [ (|| { Some(Some(elem)?) })() == Some(3) ] => "Assertion failed: (|| { Some(Some(elem)?) })() == Some(3)" + + // try block + + // underscore + + // while + [ { while false { elem; break; } elem } == 3 ] => "Assertion failed: { while false { elem; break; } elem } == 3" + + // yeet + + // yield + ); +} diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/all-not-available-cases.rs b/src/test/ui/rfc-2011-nicer-assert-messages/all-not-available-cases.rs new file mode 100644 index 0000000000000..ecbba8c443ac7 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/all-not-available-cases.rs @@ -0,0 +1,42 @@ +// aux-build:common.rs +// ignore-tidy-linelength +// run-pass + +#![feature(core_intrinsics, generic_assert, generic_assert_internals)] + +extern crate common; + +#[derive(Clone, Copy, PartialEq)] +struct CopyNoDebug(i32); + +#[derive(Debug, PartialEq)] +struct NoCopyDebug(i32); + +#[derive(PartialEq)] +struct NoCopyNoDebug(i32); + +fn main() { + // Has Copy but does not have Debug + common::test!( + let mut copy_no_debug = CopyNoDebug(1); + [ copy_no_debug == CopyNoDebug(3) ] => "Assertion failed: copy_no_debug == CopyNoDebug(3)\nWith captures:\n copy_no_debug = N/A\n" + ); + + // Does not have Copy but has Debug + common::test!( + let mut no_copy_debug = NoCopyDebug(1); + [ no_copy_debug == NoCopyDebug(3) ] => "Assertion failed: no_copy_debug == NoCopyDebug(3)\nWith captures:\n no_copy_debug = N/A\n" + ); + + // Does not have Copy and does not have Debug + common::test!( + let mut no_copy_no_debug = NoCopyNoDebug(1); + [ no_copy_no_debug == NoCopyNoDebug(3) ] => "Assertion failed: no_copy_no_debug == NoCopyNoDebug(3)\nWith captures:\n no_copy_no_debug = N/A\n" + ); + + // Unevaluated (Expression short-circuited) + common::test!( + let mut elem = true; + [ false && elem ] => "Assertion failed: false && elem\nWith captures:\n elem = N/A\n" + ); +} diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs b/src/test/ui/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs new file mode 100644 index 0000000000000..6a1435f792bf4 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs @@ -0,0 +1,13 @@ +// compile-flags: --test +// run-pass + +#![feature(core_intrinsics, generic_assert, generic_assert_internals)] + +#[should_panic(expected = "Custom user message")] +#[test] +fn test() { + assert!(1 == 3, "Custom user message"); +} + +fn main() { +} diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs b/src/test/ui/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs new file mode 100644 index 0000000000000..d4c1df6b94a81 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs @@ -0,0 +1,13 @@ +// aux-build:common.rs +// run-pass + +#![feature(core_intrinsics, generic_assert, generic_assert_internals)] + +extern crate common; + +fn main() { + common::test!( + let mut _nothing = (); + [ 1 == 3 ] => "Assertion failed: 1 == 3" + ); +} diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/auxiliary/common.rs b/src/test/ui/rfc-2011-nicer-assert-messages/auxiliary/common.rs new file mode 100644 index 0000000000000..903ed507c2e51 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/auxiliary/common.rs @@ -0,0 +1,25 @@ +#[macro_export] +macro_rules! test { + ( + let mut $elem_ident:ident = $elem_expr:expr; + [ $($assert:tt)* ] => $msg:literal + ) => { + { + #[allow(unused_assignments, unused_mut, unused_variables)] + let rslt = std::panic::catch_unwind(|| { + let mut $elem_ident = $elem_expr; + assert!($($assert)*); + }); + let err = rslt.unwrap_err(); + if let Some(elem) = err.downcast_ref::() { + assert_eq!(elem, &$msg); + } + else if let Some(elem) = err.downcast_ref::<&str>() { + assert_eq!(elem, &$msg); + } + else { + panic!("assert!( ... ) should return a string"); + } + } + } +} diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/codegen.rs b/src/test/ui/rfc-2011-nicer-assert-messages/codegen.rs new file mode 100644 index 0000000000000..1db9d33c72aee --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/codegen.rs @@ -0,0 +1,9 @@ +// check-pass +// compile-flags: -Z unpretty=expanded + +#![feature(core_intrinsics, generic_assert, generic_assert_internals)] + +fn main() { + let elem = 1i32; + assert!(elem == 1); +} diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/codegen.stdout b/src/test/ui/rfc-2011-nicer-assert-messages/codegen.stdout new file mode 100644 index 0000000000000..a590eb3223254 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/codegen.stdout @@ -0,0 +1,29 @@ +#![feature(prelude_import)] +#![no_std] +// check-pass +// compile-flags: -Z unpretty=expanded + +#![feature(core_intrinsics, generic_assert, generic_assert_internals)] +#[prelude_import] +use ::std::prelude::rust_2015::*; +#[macro_use] +extern crate std; + +fn main() { + let elem = 1i32; + { + #[allow(unused_imports)] + use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable}; + let mut __capture0 = ::core::asserting::Capture::new(); + let __local_bind0 = &elem; + if !(*{ + (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); + __local_bind0 + } == 1) { + { + ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem == 1\nWith captures:\n elem = ", + "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + } + } + }; +} diff --git a/src/test/ui/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs b/src/test/ui/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs index f70ca87e304a9..01860adaac250 100644 --- a/src/test/ui/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs +++ b/src/test/ui/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs @@ -1,8 +1,7 @@ // compile-flags: --test +// ignore-tidy-linelength // run-pass -// `generic_assert` is completely unimplemented and doesn't generate any logic, thus the -// reason why this test currently passes #![feature(core_intrinsics, generic_assert, generic_assert_internals)] use std::fmt::{Debug, Formatter}; @@ -16,10 +15,11 @@ impl Debug for CopyDebug { } } +#[should_panic(expected = "Assertion failed: copy_debug == CopyDebug(3)\nWith captures:\n copy_debug = With great power comes great electricity bills\n")] #[test] fn test() { - let _copy_debug = CopyDebug(1); - assert!(_copy_debug == CopyDebug(3)); + let copy_debug = CopyDebug(1); + assert!(copy_debug == CopyDebug(3)); } fn main() {