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#[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 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 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!("`{}`", tcx.string_with_limit(self, length_limit)).into()
164 }
165 }
166 }
167
168 pub fn prefix_string(self, tcx: TyCtxt<'_>) -> Cow<'static, str> {
169 match *self.kind() {
170 ty::Infer(_)
171 | ty::Error(_)
172 | ty::Bool
173 | ty::Char
174 | ty::Int(_)
175 | ty::Uint(_)
176 | ty::Float(_)
177 | ty::Str
178 | ty::Never => "type".into(),
179 ty::Tuple(tys) if tys.is_empty() => "unit type".into(),
180 ty::Adt(def, _) => def.descr().into(),
181 ty::Foreign(_) => "extern type".into(),
182 ty::Array(..) => "array".into(),
183 ty::Pat(..) => "pattern type".into(),
184 ty::Slice(_) => "slice".into(),
185 ty::RawPtr(_, _) => "raw pointer".into(),
186 ty::Ref(.., mutbl) => match mutbl {
187 hir::Mutability::Mut => "mutable reference",
188 _ => "reference",
189 }
190 .into(),
191 ty::FnDef(def_id, ..) => match tcx.def_kind(def_id) {
192 DefKind::Ctor(CtorOf::Struct, _) => "struct constructor".into(),
193 DefKind::Ctor(CtorOf::Variant, _) => "enum constructor".into(),
194 _ => "fn item".into(),
195 },
196 ty::FnPtr(..) => "fn pointer".into(),
197 ty::UnsafeBinder(_) => "unsafe binder".into(),
198 ty::Dynamic(..) => "trait object".into(),
199 ty::Closure(..) | ty::CoroutineClosure(..) => "closure".into(),
200 ty::Coroutine(def_id, ..) => {
201 format!("{:#}", tcx.coroutine_kind(def_id).unwrap()).into()
202 }
203 ty::CoroutineWitness(..) => "coroutine witness".into(),
204 ty::Tuple(..) => "tuple".into(),
205 ty::Placeholder(..) => "higher-ranked type".into(),
206 ty::Bound(..) => "bound type variable".into(),
207 ty::Alias(ty::Projection | ty::Inherent, _) => "associated type".into(),
208 ty::Alias(ty::Weak, _) => "type alias".into(),
209 ty::Param(_) => "type parameter".into(),
210 ty::Alias(ty::Opaque, ..) => "opaque type".into(),
211 }
212 }
213}
214
215impl<'tcx> TyCtxt<'tcx> {
216 pub fn string_with_limit<'a, T>(self, p: T, length_limit: usize) -> String
217 where
218 T: Print<'tcx, FmtPrinter<'a, 'tcx>> + Lift<TyCtxt<'tcx>> + Copy,
219 <T as Lift<TyCtxt<'tcx>>>::Lifted: Print<'tcx, FmtPrinter<'a, 'tcx>>,
220 {
221 let mut type_limit = 50;
222 let regular = FmtPrinter::print_string(self, hir::def::Namespace::TypeNS, |cx| {
223 self.lift(p).expect("could not lift for printing").print(cx)
224 })
225 .expect("could not write to `String`");
226 if regular.len() <= length_limit {
227 return regular;
228 }
229 let mut short;
230 loop {
231 short = with_forced_trimmed_paths!({
233 let mut cx = FmtPrinter::new_with_limit(
234 self,
235 hir::def::Namespace::TypeNS,
236 rustc_session::Limit(type_limit),
237 );
238 self.lift(p)
239 .expect("could not lift for printing")
240 .print(&mut cx)
241 .expect("could not print type");
242 cx.into_buffer()
243 });
244 if short.len() <= length_limit || type_limit == 0 {
245 break;
246 }
247 type_limit -= 1;
248 }
249 short
250 }
251
252 pub fn short_string<'a, T>(self, p: T, path: &mut Option<PathBuf>) -> String
257 where
258 T: Print<'tcx, FmtPrinter<'a, 'tcx>> + Lift<TyCtxt<'tcx>> + Copy + Hash,
259 <T as Lift<TyCtxt<'tcx>>>::Lifted: Print<'tcx, FmtPrinter<'a, 'tcx>>,
260 {
261 let regular = FmtPrinter::print_string(self, hir::def::Namespace::TypeNS, |cx| {
262 self.lift(p).expect("could not lift for printing").print(cx)
263 })
264 .expect("could not write to `String`");
265
266 if !self.sess.opts.unstable_opts.write_long_types_to_disk || self.sess.opts.verbose {
267 return regular;
268 }
269
270 let width = self.sess.diagnostic_width();
271 let length_limit = width / 2;
272 if regular.len() <= width * 2 / 3 {
273 return regular;
274 }
275 let short = self.string_with_limit(p, length_limit);
276 if regular == short {
277 return regular;
278 }
279 let mut s = DefaultHasher::new();
281 p.hash(&mut s);
282 let hash = s.finish();
283 *path = Some(path.take().unwrap_or_else(|| {
284 self.output_filenames(()).temp_path_ext(&format!("long-type-{hash}.txt"), None)
285 }));
286 let Ok(mut file) =
287 File::options().create(true).read(true).append(true).open(&path.as_ref().unwrap())
288 else {
289 return regular;
290 };
291
292 let mut contents = String::new();
294 let _ = file.read_to_string(&mut contents);
295 if let Some(_) = contents.lines().find(|line| line == ®ular) {
296 return short;
297 }
298
299 match write!(file, "{regular}\n") {
300 Ok(_) => short,
301 Err(_) => regular,
302 }
303 }
304}