Skip to main content

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