rustc_middle/ty/
error.rs

1use std::borrow::Cow;
2use std::fs::File;
3use std::hash::{DefaultHasher, Hash, Hasher};
4use std::io::{Read, Write};
5use std::path::PathBuf;
6
7use rustc_errors::pluralize;
8use rustc_hir as hir;
9use rustc_hir::def::{CtorOf, DefKind};
10use rustc_macros::extension;
11pub use rustc_type_ir::error::ExpectedFound;
12
13use crate::ty::print::{FmtPrinter, Print, with_forced_trimmed_paths};
14use crate::ty::{self, Lift, Ty, TyCtxt};
15
16pub type TypeError<'tcx> = rustc_type_ir::error::TypeError<TyCtxt<'tcx>>;
17
18/// Explains the source of a type err in a short, human readable way.
19/// This is meant to be placed in parentheses after some larger message.
20/// You should also invoke `note_and_explain_type_err()` afterwards
21/// to present additional details, particularly when it comes to lifetime-
22/// related errors.
23#[extension(pub trait TypeErrorToStringExt<'tcx>)]
24impl<'tcx> TypeError<'tcx> {
25    fn to_string(self, tcx: TyCtxt<'tcx>) -> Cow<'static, str> {
26        fn report_maybe_different(expected: &str, found: &str) -> String {
27            // A naive approach to making sure that we're not reporting silly errors such as:
28            // (expected closure, found closure).
29            if expected == found {
30                format!("expected {expected}, found a different {found}")
31            } else {
32                format!("expected {expected}, found {found}")
33            }
34        }
35
36        match self {
37            TypeError::CyclicTy(_) => "cyclic type of infinite size".into(),
38            TypeError::CyclicConst(_) => "encountered a self-referencing constant".into(),
39            TypeError::Mismatch => "types differ".into(),
40            TypeError::PolarityMismatch(values) => {
41                format!("expected {} polarity, found {} polarity", values.expected, values.found)
42                    .into()
43            }
44            TypeError::SafetyMismatch(values) => {
45                format!("expected {} fn, found {} fn", values.expected, values.found).into()
46            }
47            TypeError::AbiMismatch(values) => {
48                format!("expected {} fn, found {} fn", values.expected, values.found).into()
49            }
50            TypeError::ArgumentMutability(_) | TypeError::Mutability => {
51                "types differ in mutability".into()
52            }
53            TypeError::TupleSize(values) => format!(
54                "expected a tuple with {} element{}, found one with {} element{}",
55                values.expected,
56                pluralize!(values.expected),
57                values.found,
58                pluralize!(values.found)
59            )
60            .into(),
61            TypeError::ArraySize(values) => format!(
62                "expected an array with a size of {}, found one with a size of {}",
63                values.expected, values.found,
64            )
65            .into(),
66            TypeError::ArgCount => "incorrect number of function parameters".into(),
67            TypeError::RegionsDoesNotOutlive(..) => "lifetime mismatch".into(),
68            // Actually naming the region here is a bit confusing because context is lacking
69            TypeError::RegionsInsufficientlyPolymorphic(..) => {
70                "one type is more general than the other".into()
71            }
72            TypeError::RegionsPlaceholderMismatch => {
73                "one type is more general than the other".into()
74            }
75            TypeError::ArgumentSorts(values, _) | TypeError::Sorts(values) => {
76                let expected = values.expected.sort_string(tcx);
77                let found = values.found.sort_string(tcx);
78                report_maybe_different(&expected, &found).into()
79            }
80            TypeError::Traits(values) => {
81                let (mut expected, mut found) = with_forced_trimmed_paths!((
82                    tcx.def_path_str(values.expected),
83                    tcx.def_path_str(values.found),
84                ));
85                if expected == found {
86                    expected = tcx.def_path_str(values.expected);
87                    found = tcx.def_path_str(values.found);
88                }
89                report_maybe_different(&format!("trait `{expected}`"), &format!("trait `{found}`"))
90                    .into()
91            }
92            TypeError::VariadicMismatch(ref values) => format!(
93                "expected {} fn, found {} function",
94                if values.expected { "variadic" } else { "non-variadic" },
95                if values.found { "variadic" } else { "non-variadic" }
96            )
97            .into(),
98            TypeError::ProjectionMismatched(ref values) => format!(
99                "expected `{}`, found `{}`",
100                tcx.def_path_str(values.expected),
101                tcx.def_path_str(values.found)
102            )
103            .into(),
104            TypeError::ExistentialMismatch(ref values) => report_maybe_different(
105                &format!("trait `{}`", values.expected),
106                &format!("trait `{}`", values.found),
107            )
108            .into(),
109            TypeError::ConstMismatch(ref values) => {
110                format!("expected `{}`, found `{}`", values.expected, values.found).into()
111            }
112            TypeError::ForceInlineCast => {
113                "cannot coerce functions which must be inlined to function pointers".into()
114            }
115            TypeError::IntrinsicCast => "cannot coerce intrinsics to function pointers".into(),
116            TypeError::TargetFeatureCast(_) => {
117                "cannot coerce functions with `#[target_feature]` to safe function pointers".into()
118            }
119        }
120    }
121}
122
123impl<'tcx> Ty<'tcx> {
124    pub fn sort_string(self, tcx: TyCtxt<'tcx>) -> Cow<'static, str> {
125        match *self.kind() {
126            ty::Foreign(def_id) => format!("extern type `{}`", tcx.def_path_str(def_id)).into(),
127            ty::FnDef(def_id, ..) => match tcx.def_kind(def_id) {
128                DefKind::Ctor(CtorOf::Struct, _) => "struct constructor".into(),
129                DefKind::Ctor(CtorOf::Variant, _) => "enum constructor".into(),
130                _ => "fn item".into(),
131            },
132            ty::FnPtr(..) => "fn pointer".into(),
133            ty::Dynamic(inner, ..) if let Some(principal) = inner.principal() => {
134                format!("`dyn {}`", tcx.def_path_str(principal.def_id())).into()
135            }
136            ty::Dynamic(..) => "trait object".into(),
137            ty::Closure(..) => "closure".into(),
138            ty::Coroutine(def_id, ..) => {
139                format!("{:#}", tcx.coroutine_kind(def_id).unwrap()).into()
140            }
141            ty::CoroutineWitness(..) => "coroutine witness".into(),
142            ty::Infer(ty::TyVar(_)) => "inferred type".into(),
143            ty::Infer(ty::IntVar(_)) => "integer".into(),
144            ty::Infer(ty::FloatVar(_)) => "floating-point number".into(),
145            ty::Placeholder(..) => "placeholder type".into(),
146            ty::Bound(..) => "bound type".into(),
147            ty::Infer(ty::FreshTy(_)) => "fresh type".into(),
148            ty::Infer(ty::FreshIntTy(_)) => "fresh integral type".into(),
149            ty::Infer(ty::FreshFloatTy(_)) => "fresh floating-point type".into(),
150            ty::Alias(ty::Projection | ty::Inherent, _) => "associated type".into(),
151            ty::Param(p) => format!("type parameter `{p}`").into(),
152            ty::Alias(ty::Opaque, ..) => {
153                if tcx.ty_is_opaque_future(self) {
154                    "future".into()
155                } else {
156                    "opaque type".into()
157                }
158            }
159            ty::Error(_) => "type error".into(),
160            _ => {
161                let width = tcx.sess.diagnostic_width();
162                let length_limit = std::cmp::max(width / 4, 40);
163                format!(
164                    "`{}`",
165                    tcx.string_with_limit(self, length_limit, hir::def::Namespace::TypeNS)
166                )
167                .into()
168            }
169        }
170    }
171
172    pub fn prefix_string(self, tcx: TyCtxt<'_>) -> Cow<'static, str> {
173        match *self.kind() {
174            ty::Infer(_)
175            | ty::Error(_)
176            | ty::Bool
177            | ty::Char
178            | ty::Int(_)
179            | ty::Uint(_)
180            | ty::Float(_)
181            | ty::Str
182            | ty::Never => "type".into(),
183            ty::Tuple(tys) if tys.is_empty() => "unit type".into(),
184            ty::Adt(def, _) => def.descr().into(),
185            ty::Foreign(_) => "extern type".into(),
186            ty::Array(..) => "array".into(),
187            ty::Pat(..) => "pattern type".into(),
188            ty::Slice(_) => "slice".into(),
189            ty::RawPtr(_, _) => "raw pointer".into(),
190            ty::Ref(.., mutbl) => match mutbl {
191                hir::Mutability::Mut => "mutable reference",
192                _ => "reference",
193            }
194            .into(),
195            ty::FnDef(def_id, ..) => match tcx.def_kind(def_id) {
196                DefKind::Ctor(CtorOf::Struct, _) => "struct constructor".into(),
197                DefKind::Ctor(CtorOf::Variant, _) => "enum constructor".into(),
198                _ => "fn item".into(),
199            },
200            ty::FnPtr(..) => "fn pointer".into(),
201            ty::UnsafeBinder(_) => "unsafe binder".into(),
202            ty::Dynamic(..) => "trait object".into(),
203            ty::Closure(..) | ty::CoroutineClosure(..) => "closure".into(),
204            ty::Coroutine(def_id, ..) => {
205                format!("{:#}", tcx.coroutine_kind(def_id).unwrap()).into()
206            }
207            ty::CoroutineWitness(..) => "coroutine witness".into(),
208            ty::Tuple(..) => "tuple".into(),
209            ty::Placeholder(..) => "higher-ranked type".into(),
210            ty::Bound(..) => "bound type variable".into(),
211            ty::Alias(ty::Projection | ty::Inherent, _) => "associated type".into(),
212            ty::Alias(ty::Free, _) => "type alias".into(),
213            ty::Param(_) => "type parameter".into(),
214            ty::Alias(ty::Opaque, ..) => "opaque type".into(),
215        }
216    }
217}
218
219impl<'tcx> TyCtxt<'tcx> {
220    pub fn string_with_limit<T>(self, t: T, length_limit: usize, ns: hir::def::Namespace) -> String
221    where
222        T: Copy + for<'a, 'b> Lift<TyCtxt<'b>, Lifted: Print<'b, FmtPrinter<'a, 'b>>>,
223    {
224        let mut type_limit = 50;
225        let regular = FmtPrinter::print_string(self, ns, |p| {
226            self.lift(t).expect("could not lift for printing").print(p)
227        })
228        .expect("could not write to `String`");
229        if regular.len() <= length_limit {
230            return regular;
231        }
232        let mut short;
233        loop {
234            // Look for the longest properly trimmed path that still fits in length_limit.
235            short = with_forced_trimmed_paths!({
236                let mut p = FmtPrinter::new_with_limit(self, ns, rustc_session::Limit(type_limit));
237                self.lift(t)
238                    .expect("could not lift for printing")
239                    .print(&mut p)
240                    .expect("could not print type");
241                p.into_buffer()
242            });
243            if short.len() <= length_limit || type_limit == 0 {
244                break;
245            }
246            type_limit -= 1;
247        }
248        short
249    }
250
251    /// When calling this after a `Diag` is constructed, the preferred way of doing so is
252    /// `tcx.short_string(ty, diag.long_ty_path())`. The diagnostic itself is the one that keeps
253    /// the existence of a "long type" anywhere in the diagnostic, so the note telling the user
254    /// where we wrote the file to is only printed once. The path will use the type namespace.
255    pub fn short_string<T>(self, t: T, path: &mut Option<PathBuf>) -> String
256    where
257        T: Copy + Hash + for<'a, 'b> Lift<TyCtxt<'b>, Lifted: Print<'b, FmtPrinter<'a, 'b>>>,
258    {
259        self.short_string_namespace(t, path, hir::def::Namespace::TypeNS)
260    }
261
262    /// When calling this after a `Diag` is constructed, the preferred way of doing so is
263    /// `tcx.short_string(ty, diag.long_ty_path())`. The diagnostic itself is the one that keeps
264    /// the existence of a "long type" anywhere in the diagnostic, so the note telling the user
265    /// where we wrote the file to is only printed once.
266    pub fn short_string_namespace<T>(
267        self,
268        t: T,
269        path: &mut Option<PathBuf>,
270        namespace: hir::def::Namespace,
271    ) -> String
272    where
273        T: Copy + Hash + for<'a, 'b> Lift<TyCtxt<'b>, Lifted: Print<'b, FmtPrinter<'a, 'b>>>,
274    {
275        let regular = FmtPrinter::print_string(self, namespace, |p| {
276            self.lift(t).expect("could not lift for printing").print(p)
277        })
278        .expect("could not write to `String`");
279
280        if !self.sess.opts.unstable_opts.write_long_types_to_disk || self.sess.opts.verbose {
281            return regular;
282        }
283
284        let width = self.sess.diagnostic_width();
285        let length_limit = width / 2;
286        if regular.len() <= width * 2 / 3 {
287            return regular;
288        }
289        let short = self.string_with_limit(t, length_limit, namespace);
290        if regular == short {
291            return regular;
292        }
293        // Ensure we create an unique file for the type passed in when we create a file.
294        let mut s = DefaultHasher::new();
295        t.hash(&mut s);
296        let hash = s.finish();
297        *path = Some(path.take().unwrap_or_else(|| {
298            self.output_filenames(()).temp_path_for_diagnostic(&format!("long-type-{hash}.txt"))
299        }));
300        let Ok(mut file) =
301            File::options().create(true).read(true).append(true).open(&path.as_ref().unwrap())
302        else {
303            return regular;
304        };
305
306        // Do not write the same type to the file multiple times.
307        let mut contents = String::new();
308        let _ = file.read_to_string(&mut contents);
309        if let Some(_) = contents.lines().find(|line| line == &regular) {
310            return short;
311        }
312
313        match write!(file, "{regular}\n") {
314            Ok(_) => short,
315            Err(_) => regular,
316        }
317    }
318}