aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/display.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty/src/display.rs')
-rw-r--r--crates/hir_ty/src/display.rs631
1 files changed, 631 insertions, 0 deletions
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
new file mode 100644
index 000000000..19770e609
--- /dev/null
+++ b/crates/hir_ty/src/display.rs
@@ -0,0 +1,631 @@
1//! FIXME: write short doc here
2
3use std::fmt;
4
5use crate::{
6 db::HirDatabase, utils::generics, ApplicationTy, CallableDefId, FnSig, GenericPredicate,
7 Obligation, OpaqueTyId, ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
8};
9use hir_def::{
10 find_path, generics::TypeParamProvenance, item_scope::ItemInNs, AdtId, AssocContainerId,
11 Lookup, ModuleId,
12};
13use hir_expand::name::Name;
14
15pub struct HirFormatter<'a> {
16 pub db: &'a dyn HirDatabase,
17 fmt: &'a mut dyn fmt::Write,
18 buf: String,
19 curr_size: usize,
20 pub(crate) max_size: Option<usize>,
21 omit_verbose_types: bool,
22 display_target: DisplayTarget,
23}
24
25pub trait HirDisplay {
26 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError>;
27
28 /// Returns a `Display`able type that is human-readable.
29 /// Use this for showing types to the user (e.g. diagnostics)
30 fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self>
31 where
32 Self: Sized,
33 {
34 HirDisplayWrapper {
35 db,
36 t: self,
37 max_size: None,
38 omit_verbose_types: false,
39 display_target: DisplayTarget::Diagnostics,
40 }
41 }
42
43 /// Returns a `Display`able type that is human-readable and tries to be succinct.
44 /// Use this for showing types to the user where space is constrained (e.g. doc popups)
45 fn display_truncated<'a>(
46 &'a self,
47 db: &'a dyn HirDatabase,
48 max_size: Option<usize>,
49 ) -> HirDisplayWrapper<'a, Self>
50 where
51 Self: Sized,
52 {
53 HirDisplayWrapper {
54 db,
55 t: self,
56 max_size,
57 omit_verbose_types: true,
58 display_target: DisplayTarget::Diagnostics,
59 }
60 }
61
62 /// Returns a String representation of `self` that can be inserted into the given module.
63 /// Use this when generating code (e.g. assists)
64 fn display_source_code<'a>(
65 &'a self,
66 db: &'a dyn HirDatabase,
67 module_id: ModuleId,
68 ) -> Result<String, DisplaySourceCodeError> {
69 let mut result = String::new();
70 match self.hir_fmt(&mut HirFormatter {
71 db,
72 fmt: &mut result,
73 buf: String::with_capacity(20),
74 curr_size: 0,
75 max_size: None,
76 omit_verbose_types: false,
77 display_target: DisplayTarget::SourceCode { module_id },
78 }) {
79 Ok(()) => {}
80 Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
81 Err(HirDisplayError::DisplaySourceCodeError(e)) => return Err(e),
82 };
83 Ok(result)
84 }
85}
86
87impl<'a> HirFormatter<'a> {
88 pub fn write_joined<T: HirDisplay>(
89 &mut self,
90 iter: impl IntoIterator<Item = T>,
91 sep: &str,
92 ) -> Result<(), HirDisplayError> {
93 let mut first = true;
94 for e in iter {
95 if !first {
96 write!(self, "{}", sep)?;
97 }
98 first = false;
99 e.hir_fmt(self)?;
100 }
101 Ok(())
102 }
103
104 /// This allows using the `write!` macro directly with a `HirFormatter`.
105 pub fn write_fmt(&mut self, args: fmt::Arguments) -> Result<(), HirDisplayError> {
106 // We write to a buffer first to track output size
107 self.buf.clear();
108 fmt::write(&mut self.buf, args)?;
109 self.curr_size += self.buf.len();
110
111 // Then we write to the internal formatter from the buffer
112 self.fmt.write_str(&self.buf).map_err(HirDisplayError::from)
113 }
114
115 pub fn should_truncate(&self) -> bool {
116 if let Some(max_size) = self.max_size {
117 self.curr_size >= max_size
118 } else {
119 false
120 }
121 }
122
123 pub fn omit_verbose_types(&self) -> bool {
124 self.omit_verbose_types
125 }
126}
127
128#[derive(Clone, Copy)]
129enum DisplayTarget {
130 /// Display types for inlays, doc popups, autocompletion, etc...
131 /// Showing `{unknown}` or not qualifying paths is fine here.
132 /// There's no reason for this to fail.
133 Diagnostics,
134 /// Display types for inserting them in source files.
135 /// The generated code should compile, so paths need to be qualified.
136 SourceCode { module_id: ModuleId },
137}
138
139impl DisplayTarget {
140 fn is_source_code(&self) -> bool {
141 matches!(self, Self::SourceCode {..})
142 }
143}
144
145#[derive(Debug)]
146pub enum DisplaySourceCodeError {
147 PathNotFound,
148}
149
150pub enum HirDisplayError {
151 /// Errors that can occur when generating source code
152 DisplaySourceCodeError(DisplaySourceCodeError),
153 /// `FmtError` is required to be compatible with std::fmt::Display
154 FmtError,
155}
156impl From<fmt::Error> for HirDisplayError {
157 fn from(_: fmt::Error) -> Self {
158 Self::FmtError
159 }
160}
161
162pub struct HirDisplayWrapper<'a, T> {
163 db: &'a dyn HirDatabase,
164 t: &'a T,
165 max_size: Option<usize>,
166 omit_verbose_types: bool,
167 display_target: DisplayTarget,
168}
169
170impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
171where
172 T: HirDisplay,
173{
174 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175 match self.t.hir_fmt(&mut HirFormatter {
176 db: self.db,
177 fmt: f,
178 buf: String::with_capacity(20),
179 curr_size: 0,
180 max_size: self.max_size,
181 omit_verbose_types: self.omit_verbose_types,
182 display_target: self.display_target,
183 }) {
184 Ok(()) => Ok(()),
185 Err(HirDisplayError::FmtError) => Err(fmt::Error),
186 Err(HirDisplayError::DisplaySourceCodeError(_)) => {
187 // This should never happen
188 panic!("HirDisplay failed when calling Display::fmt!")
189 }
190 }
191 }
192}
193
194const TYPE_HINT_TRUNCATION: &str = "…";
195
196impl HirDisplay for &Ty {
197 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
198 HirDisplay::hir_fmt(*self, f)
199 }
200}
201
202impl HirDisplay for ApplicationTy {
203 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
204 if f.should_truncate() {
205 return write!(f, "{}", TYPE_HINT_TRUNCATION);
206 }
207
208 match self.ctor {
209 TypeCtor::Bool => write!(f, "bool")?,
210 TypeCtor::Char => write!(f, "char")?,
211 TypeCtor::Int(t) => write!(f, "{}", t)?,
212 TypeCtor::Float(t) => write!(f, "{}", t)?,
213 TypeCtor::Str => write!(f, "str")?,
214 TypeCtor::Slice => {
215 let t = self.parameters.as_single();
216 write!(f, "[{}]", t.display(f.db))?;
217 }
218 TypeCtor::Array => {
219 let t = self.parameters.as_single();
220 write!(f, "[{}; _]", t.display(f.db))?;
221 }
222 TypeCtor::RawPtr(m) => {
223 let t = self.parameters.as_single();
224 write!(f, "*{}{}", m.as_keyword_for_ptr(), t.display(f.db))?;
225 }
226 TypeCtor::Ref(m) => {
227 let t = self.parameters.as_single();
228 let ty_display = if f.omit_verbose_types() {
229 t.display_truncated(f.db, f.max_size)
230 } else {
231 t.display(f.db)
232 };
233 write!(f, "&{}{}", m.as_keyword_for_ref(), ty_display)?;
234 }
235 TypeCtor::Never => write!(f, "!")?,
236 TypeCtor::Tuple { .. } => {
237 let ts = &self.parameters;
238 if ts.len() == 1 {
239 write!(f, "({},)", ts[0].display(f.db))?;
240 } else {
241 write!(f, "(")?;
242 f.write_joined(&*ts.0, ", ")?;
243 write!(f, ")")?;
244 }
245 }
246 TypeCtor::FnPtr { is_varargs, .. } => {
247 let sig = FnSig::from_fn_ptr_substs(&self.parameters, is_varargs);
248 write!(f, "fn(")?;
249 f.write_joined(sig.params(), ", ")?;
250 if is_varargs {
251 if sig.params().is_empty() {
252 write!(f, "...")?;
253 } else {
254 write!(f, ", ...")?;
255 }
256 }
257 write!(f, ")")?;
258 let ret = sig.ret();
259 if *ret != Ty::unit() {
260 let ret_display = if f.omit_verbose_types() {
261 ret.display_truncated(f.db, f.max_size)
262 } else {
263 ret.display(f.db)
264 };
265 write!(f, " -> {}", ret_display)?;
266 }
267 }
268 TypeCtor::FnDef(def) => {
269 let sig = f.db.callable_item_signature(def).subst(&self.parameters);
270 match def {
271 CallableDefId::FunctionId(ff) => {
272 write!(f, "fn {}", f.db.function_data(ff).name)?
273 }
274 CallableDefId::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?,
275 CallableDefId::EnumVariantId(e) => {
276 write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)?
277 }
278 };
279 if self.parameters.len() > 0 {
280 let generics = generics(f.db.upcast(), def.into());
281 let (parent_params, self_param, type_params, _impl_trait_params) =
282 generics.provenance_split();
283 let total_len = parent_params + self_param + type_params;
284 // We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
285 if total_len > 0 {
286 write!(f, "<")?;
287 f.write_joined(&self.parameters.0[..total_len], ", ")?;
288 write!(f, ">")?;
289 }
290 }
291 write!(f, "(")?;
292 f.write_joined(sig.params(), ", ")?;
293 write!(f, ")")?;
294 let ret = sig.ret();
295 if *ret != Ty::unit() {
296 let ret_display = if f.omit_verbose_types() {
297 ret.display_truncated(f.db, f.max_size)
298 } else {
299 ret.display(f.db)
300 };
301 write!(f, " -> {}", ret_display)?;
302 }
303 }
304 TypeCtor::Adt(def_id) => {
305 match f.display_target {
306 DisplayTarget::Diagnostics => {
307 let name = match def_id {
308 AdtId::StructId(it) => f.db.struct_data(it).name.clone(),
309 AdtId::UnionId(it) => f.db.union_data(it).name.clone(),
310 AdtId::EnumId(it) => f.db.enum_data(it).name.clone(),
311 };
312 write!(f, "{}", name)?;
313 }
314 DisplayTarget::SourceCode { module_id } => {
315 if let Some(path) = find_path::find_path(
316 f.db.upcast(),
317 ItemInNs::Types(def_id.into()),
318 module_id,
319 ) {
320 write!(f, "{}", path)?;
321 } else {
322 return Err(HirDisplayError::DisplaySourceCodeError(
323 DisplaySourceCodeError::PathNotFound,
324 ));
325 }
326 }
327 }
328
329 if self.parameters.len() > 0 {
330 let parameters_to_write =
331 if f.display_target.is_source_code() || f.omit_verbose_types() {
332 match self
333 .ctor
334 .as_generic_def()
335 .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
336 .filter(|defaults| !defaults.is_empty())
337 {
338 None => self.parameters.0.as_ref(),
339 Some(default_parameters) => {
340 let mut default_from = 0;
341 for (i, parameter) in self.parameters.iter().enumerate() {
342 match (parameter, default_parameters.get(i)) {
343 (&Ty::Unknown, _) | (_, None) => {
344 default_from = i + 1;
345 }
346 (_, Some(default_parameter)) => {
347 let actual_default = default_parameter
348 .clone()
349 .subst(&self.parameters.prefix(i));
350 if parameter != &actual_default {
351 default_from = i + 1;
352 }
353 }
354 }
355 }
356 &self.parameters.0[0..default_from]
357 }
358 }
359 } else {
360 self.parameters.0.as_ref()
361 };
362 if !parameters_to_write.is_empty() {
363 write!(f, "<")?;
364 f.write_joined(parameters_to_write, ", ")?;
365 write!(f, ">")?;
366 }
367 }
368 }
369 TypeCtor::AssociatedType(type_alias) => {
370 let trait_ = match type_alias.lookup(f.db.upcast()).container {
371 AssocContainerId::TraitId(it) => it,
372 _ => panic!("not an associated type"),
373 };
374 let trait_ = f.db.trait_data(trait_);
375 let type_alias = f.db.type_alias_data(type_alias);
376 write!(f, "{}::{}", trait_.name, type_alias.name)?;
377 if self.parameters.len() > 0 {
378 write!(f, "<")?;
379 f.write_joined(&*self.parameters.0, ", ")?;
380 write!(f, ">")?;
381 }
382 }
383 TypeCtor::OpaqueType(opaque_ty_id) => {
384 let bounds = match opaque_ty_id {
385 OpaqueTyId::ReturnTypeImplTrait(func, idx) => {
386 let datas =
387 f.db.return_type_impl_traits(func).expect("impl trait id without data");
388 let data = (*datas)
389 .as_ref()
390 .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
391 data.subst(&self.parameters)
392 }
393 };
394 write!(f, "impl ")?;
395 write_bounds_like_dyn_trait(&bounds.value, f)?;
396 // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution
397 }
398 TypeCtor::Closure { .. } => {
399 let sig = self.parameters[0].callable_sig(f.db);
400 if let Some(sig) = sig {
401 if sig.params().is_empty() {
402 write!(f, "||")?;
403 } else if f.omit_verbose_types() {
404 write!(f, "|{}|", TYPE_HINT_TRUNCATION)?;
405 } else {
406 write!(f, "|")?;
407 f.write_joined(sig.params(), ", ")?;
408 write!(f, "|")?;
409 };
410
411 let ret_display = if f.omit_verbose_types() {
412 sig.ret().display_truncated(f.db, f.max_size)
413 } else {
414 sig.ret().display(f.db)
415 };
416 write!(f, " -> {}", ret_display)?;
417 } else {
418 write!(f, "{{closure}}")?;
419 }
420 }
421 }
422 Ok(())
423 }
424}
425
426impl HirDisplay for ProjectionTy {
427 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
428 if f.should_truncate() {
429 return write!(f, "{}", TYPE_HINT_TRUNCATION);
430 }
431
432 let trait_ = f.db.trait_data(self.trait_(f.db));
433 write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?;
434 if self.parameters.len() > 1 {
435 write!(f, "<")?;
436 f.write_joined(&self.parameters[1..], ", ")?;
437 write!(f, ">")?;
438 }
439 write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?;
440 Ok(())
441 }
442}
443
444impl HirDisplay for Ty {
445 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
446 if f.should_truncate() {
447 return write!(f, "{}", TYPE_HINT_TRUNCATION);
448 }
449
450 match self {
451 Ty::Apply(a_ty) => a_ty.hir_fmt(f)?,
452 Ty::Projection(p_ty) => p_ty.hir_fmt(f)?,
453 Ty::Placeholder(id) => {
454 let generics = generics(f.db.upcast(), id.parent);
455 let param_data = &generics.params.types[id.local_id];
456 match param_data.provenance {
457 TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => {
458 write!(f, "{}", param_data.name.clone().unwrap_or_else(Name::missing))?
459 }
460 TypeParamProvenance::ArgumentImplTrait => {
461 write!(f, "impl ")?;
462 let bounds = f.db.generic_predicates_for_param(*id);
463 let substs = Substs::type_params_for_generics(&generics);
464 write_bounds_like_dyn_trait(
465 &bounds.iter().map(|b| b.clone().subst(&substs)).collect::<Vec<_>>(),
466 f,
467 )?;
468 }
469 }
470 }
471 Ty::Bound(idx) => write!(f, "?{}.{}", idx.debruijn.depth(), idx.index)?,
472 Ty::Dyn(predicates) => {
473 write!(f, "dyn ")?;
474 write_bounds_like_dyn_trait(predicates, f)?;
475 }
476 Ty::Opaque(opaque_ty) => {
477 let bounds = match opaque_ty.opaque_ty_id {
478 OpaqueTyId::ReturnTypeImplTrait(func, idx) => {
479 let datas =
480 f.db.return_type_impl_traits(func).expect("impl trait id without data");
481 let data = (*datas)
482 .as_ref()
483 .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
484 data.subst(&opaque_ty.parameters)
485 }
486 };
487 write!(f, "impl ")?;
488 write_bounds_like_dyn_trait(&bounds.value, f)?;
489 }
490 Ty::Unknown => write!(f, "{{unknown}}")?,
491 Ty::Infer(..) => write!(f, "_")?,
492 }
493 Ok(())
494 }
495}
496
497fn write_bounds_like_dyn_trait(
498 predicates: &[GenericPredicate],
499 f: &mut HirFormatter,
500) -> Result<(), HirDisplayError> {
501 // Note: This code is written to produce nice results (i.e.
502 // corresponding to surface Rust) for types that can occur in
503 // actual Rust. It will have weird results if the predicates
504 // aren't as expected (i.e. self types = $0, projection
505 // predicates for a certain trait come after the Implemented
506 // predicate for that trait).
507 let mut first = true;
508 let mut angle_open = false;
509 for p in predicates.iter() {
510 match p {
511 GenericPredicate::Implemented(trait_ref) => {
512 if angle_open {
513 write!(f, ">")?;
514 }
515 if !first {
516 write!(f, " + ")?;
517 }
518 // We assume that the self type is $0 (i.e. the
519 // existential) here, which is the only thing that's
520 // possible in actual Rust, and hence don't print it
521 write!(f, "{}", f.db.trait_data(trait_ref.trait_).name)?;
522 if trait_ref.substs.len() > 1 {
523 write!(f, "<")?;
524 f.write_joined(&trait_ref.substs[1..], ", ")?;
525 // there might be assoc type bindings, so we leave the angle brackets open
526 angle_open = true;
527 }
528 }
529 GenericPredicate::Projection(projection_pred) => {
530 // in types in actual Rust, these will always come
531 // after the corresponding Implemented predicate
532 if angle_open {
533 write!(f, ", ")?;
534 } else {
535 write!(f, "<")?;
536 angle_open = true;
537 }
538 let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty);
539 write!(f, "{} = ", type_alias.name)?;
540 projection_pred.ty.hir_fmt(f)?;
541 }
542 GenericPredicate::Error => {
543 if angle_open {
544 // impl Trait<X, {error}>
545 write!(f, ", ")?;
546 } else if !first {
547 // impl Trait + {error}
548 write!(f, " + ")?;
549 }
550 p.hir_fmt(f)?;
551 }
552 }
553 first = false;
554 }
555 if angle_open {
556 write!(f, ">")?;
557 }
558 Ok(())
559}
560
561impl TraitRef {
562 fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> Result<(), HirDisplayError> {
563 if f.should_truncate() {
564 return write!(f, "{}", TYPE_HINT_TRUNCATION);
565 }
566
567 self.substs[0].hir_fmt(f)?;
568 if use_as {
569 write!(f, " as ")?;
570 } else {
571 write!(f, ": ")?;
572 }
573 write!(f, "{}", f.db.trait_data(self.trait_).name)?;
574 if self.substs.len() > 1 {
575 write!(f, "<")?;
576 f.write_joined(&self.substs[1..], ", ")?;
577 write!(f, ">")?;
578 }
579 Ok(())
580 }
581}
582
583impl HirDisplay for TraitRef {
584 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
585 self.hir_fmt_ext(f, false)
586 }
587}
588
589impl HirDisplay for &GenericPredicate {
590 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
591 HirDisplay::hir_fmt(*self, f)
592 }
593}
594
595impl HirDisplay for GenericPredicate {
596 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
597 if f.should_truncate() {
598 return write!(f, "{}", TYPE_HINT_TRUNCATION);
599 }
600
601 match self {
602 GenericPredicate::Implemented(trait_ref) => trait_ref.hir_fmt(f)?,
603 GenericPredicate::Projection(projection_pred) => {
604 write!(f, "<")?;
605 projection_pred.projection_ty.trait_ref(f.db).hir_fmt_ext(f, true)?;
606 write!(
607 f,
608 ">::{} = {}",
609 f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name,
610 projection_pred.ty.display(f.db)
611 )?;
612 }
613 GenericPredicate::Error => write!(f, "{{error}}")?,
614 }
615 Ok(())
616 }
617}
618
619impl HirDisplay for Obligation {
620 fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
621 Ok(match self {
622 Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db))?,
623 Obligation::Projection(proj) => write!(
624 f,
625 "Normalize({} => {})",
626 proj.projection_ty.display(f.db),
627 proj.ty.display(f.db)
628 )?,
629 })
630 }
631}