]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_const_eval/src/const_eval/error.rs
:arrow_up: rust-analyzer
[rust.git] / compiler / rustc_const_eval / src / const_eval / error.rs
1 use std::error::Error;
2 use std::fmt;
3
4 use rustc_errors::Diagnostic;
5 use rustc_hir as hir;
6 use rustc_middle::mir::AssertKind;
7 use rustc_middle::ty::{layout::LayoutError, query::TyCtxtAt, ConstInt};
8 use rustc_span::{Span, Symbol};
9
10 use super::InterpCx;
11 use crate::interpret::{
12     struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
13 };
14
15 /// The CTFE machine has some custom error kinds.
16 #[derive(Clone, Debug)]
17 pub enum ConstEvalErrKind {
18     NeedsRfc(String),
19     ConstAccessesStatic,
20     ModifiedGlobal,
21     AssertFailure(AssertKind<ConstInt>),
22     Panic { msg: Symbol, line: u32, col: u32, file: Symbol },
23     Abort(String),
24 }
25
26 impl MachineStopType for ConstEvalErrKind {
27     fn is_hard_err(&self) -> bool {
28         matches!(self, Self::Panic { .. })
29     }
30 }
31
32 // The errors become `MachineStop` with plain strings when being raised.
33 // `ConstEvalErr` (in `librustc_middle/mir/interpret/error.rs`) knows to
34 // handle these.
35 impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
36     fn into(self) -> InterpErrorInfo<'tcx> {
37         err_machine_stop!(self).into()
38     }
39 }
40
41 impl fmt::Display for ConstEvalErrKind {
42     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43         use self::ConstEvalErrKind::*;
44         match *self {
45             NeedsRfc(ref msg) => {
46                 write!(f, "\"{}\" needs an rfc before being allowed inside constants", msg)
47             }
48             ConstAccessesStatic => write!(f, "constant accesses static"),
49             ModifiedGlobal => {
50                 write!(f, "modifying a static's initial value from another static's initializer")
51             }
52             AssertFailure(ref msg) => write!(f, "{:?}", msg),
53             Panic { msg, line, col, file } => {
54                 write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col)
55             }
56             Abort(ref msg) => write!(f, "{}", msg),
57         }
58     }
59 }
60
61 impl Error for ConstEvalErrKind {}
62
63 /// When const-evaluation errors, this type is constructed with the resulting information,
64 /// and then used to emit the error as a lint or hard error.
65 #[derive(Debug)]
66 pub struct ConstEvalErr<'tcx> {
67     pub span: Span,
68     pub error: InterpError<'tcx>,
69     pub stacktrace: Vec<FrameInfo<'tcx>>,
70 }
71
72 impl<'tcx> ConstEvalErr<'tcx> {
73     /// Turn an interpreter error into something to report to the user.
74     /// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace.
75     /// Should be called only if the error is actually going to to be reported!
76     pub fn new<'mir, M: Machine<'mir, 'tcx>>(
77         ecx: &InterpCx<'mir, 'tcx, M>,
78         error: InterpErrorInfo<'tcx>,
79         span: Option<Span>,
80     ) -> ConstEvalErr<'tcx>
81     where
82         'tcx: 'mir,
83     {
84         error.print_backtrace();
85         let mut stacktrace = ecx.generate_stacktrace();
86         // Filter out `requires_caller_location` frames.
87         stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
88         // If `span` is missing, use topmost remaining frame, or else the "root" span from `ecx.tcx`.
89         let span = span.or_else(|| stacktrace.first().map(|f| f.span)).unwrap_or(ecx.tcx.span);
90         ConstEvalErr { error: error.into_kind(), stacktrace, span }
91     }
92
93     pub fn struct_error(
94         &self,
95         tcx: TyCtxtAt<'tcx>,
96         message: &str,
97         decorate: impl FnOnce(&mut Diagnostic),
98     ) -> ErrorHandled {
99         self.struct_generic(tcx, message, decorate, None)
100     }
101
102     pub fn report_as_error(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled {
103         self.struct_error(tcx, message, |_| {})
104     }
105
106     pub fn report_as_lint(
107         &self,
108         tcx: TyCtxtAt<'tcx>,
109         message: &str,
110         lint_root: hir::HirId,
111         span: Option<Span>,
112     ) -> ErrorHandled {
113         self.struct_generic(
114             tcx,
115             message,
116             |lint: &mut Diagnostic| {
117                 // Apply the span.
118                 if let Some(span) = span {
119                     let primary_spans = lint.span.primary_spans().to_vec();
120                     // point at the actual error as the primary span
121                     lint.replace_span_with(span);
122                     // point to the `const` statement as a secondary span
123                     // they don't have any label
124                     for sp in primary_spans {
125                         if sp != span {
126                             lint.span_label(sp, "");
127                         }
128                     }
129                 }
130             },
131             Some(lint_root),
132         )
133     }
134
135     /// Create a diagnostic for this const eval error.
136     ///
137     /// Sets the message passed in via `message` and adds span labels with detailed error
138     /// information before handing control back to `decorate` to do any final annotations,
139     /// after which the diagnostic is emitted.
140     ///
141     /// If `lint_root.is_some()` report it as a lint, else report it as a hard error.
142     /// (Except that for some errors, we ignore all that -- see `must_error` below.)
143     #[instrument(skip(self, tcx, decorate, lint_root), level = "debug")]
144     fn struct_generic(
145         &self,
146         tcx: TyCtxtAt<'tcx>,
147         message: &str,
148         decorate: impl FnOnce(&mut Diagnostic),
149         lint_root: Option<hir::HirId>,
150     ) -> ErrorHandled {
151         let finish = |err: &mut Diagnostic, span_msg: Option<String>| {
152             trace!("reporting const eval failure at {:?}", self.span);
153             if let Some(span_msg) = span_msg {
154                 err.span_label(self.span, span_msg);
155             }
156             // Add spans for the stacktrace. Don't print a single-line backtrace though.
157             if self.stacktrace.len() > 1 {
158                 // Helper closure to print duplicated lines.
159                 let mut flush_last_line = |last_frame, times| {
160                     if let Some((line, span)) = last_frame {
161                         err.span_label(span, &line);
162                         // Don't print [... additional calls ...] if the number of lines is small
163                         if times < 3 {
164                             for _ in 0..times {
165                                 err.span_label(span, &line);
166                             }
167                         } else {
168                             err.span_label(
169                                 span,
170                                 format!("[... {} additional calls {} ...]", times, &line),
171                             );
172                         }
173                     }
174                 };
175
176                 let mut last_frame = None;
177                 let mut times = 0;
178                 for frame_info in &self.stacktrace {
179                     let frame = (frame_info.to_string(), frame_info.span);
180                     if last_frame.as_ref() == Some(&frame) {
181                         times += 1;
182                     } else {
183                         flush_last_line(last_frame, times);
184                         last_frame = Some(frame);
185                         times = 0;
186                     }
187                 }
188                 flush_last_line(last_frame, times);
189             }
190             // Let the caller attach any additional information it wants.
191             decorate(err);
192         };
193
194         debug!("self.error: {:?}", self.error);
195         // Special handling for certain errors
196         match &self.error {
197             // Don't emit a new diagnostic for these errors
198             err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
199                 return ErrorHandled::TooGeneric;
200             }
201             err_inval!(AlreadyReported(error_reported)) => {
202                 return ErrorHandled::Reported(*error_reported);
203             }
204             err_inval!(Layout(LayoutError::SizeOverflow(_))) => {
205                 // We must *always* hard error on these, even if the caller wants just a lint.
206                 // The `message` makes little sense here, this is a more serious error than the
207                 // caller thinks anyway.
208                 // See <https://github.com/rust-lang/rust/pull/63152>.
209                 let mut err = struct_error(tcx, &self.error.to_string());
210                 finish(&mut err, None);
211                 return ErrorHandled::Reported(err.emit());
212             }
213             _ => {}
214         };
215
216         let err_msg = self.error.to_string();
217
218         // Regular case - emit a lint.
219         if let Some(lint_root) = lint_root {
220             // Report as lint.
221             let hir_id =
222                 self.stacktrace.iter().rev().find_map(|frame| frame.lint_root).unwrap_or(lint_root);
223             tcx.struct_span_lint_hir(
224                 rustc_session::lint::builtin::CONST_ERR,
225                 hir_id,
226                 tcx.span,
227                 |lint| {
228                     let mut lint = lint.build(message);
229                     finish(&mut lint, Some(err_msg));
230                     lint.emit();
231                 },
232             );
233             ErrorHandled::Linted
234         } else {
235             // Report as hard error.
236             let mut err = struct_error(tcx, message);
237             finish(&mut err, Some(err_msg));
238             ErrorHandled::Reported(err.emit())
239         }
240     }
241 }