| //! Type Names for Debug Info. |
| |
| // Notes on targeting MSVC: |
| // In general, MSVC's debugger attempts to parse all arguments as C++ expressions, |
| // even if the argument is explicitly a symbol name. |
| // As such, there are many things that cause parsing issues: |
| // * `#` is treated as a special character for macros. |
| // * `{` or `<` at the beginning of a name is treated as an operator. |
| // * `>>` is always treated as a right-shift. |
| // * `[` in a name is treated like a regex bracket expression (match any char |
| // within the brackets). |
| // * `"` is treated as the start of a string. |
| |
| use rustc_data_structures::fx::FxHashSet; |
| use rustc_data_structures::stable_hasher::{Hash64, HashStable, StableHasher}; |
| use rustc_hir::def_id::DefId; |
| use rustc_hir::definitions::{DefPathData, DefPathDataName, DisambiguatedDefPathData}; |
| use rustc_hir::{CoroutineKind, CoroutineSource, Mutability}; |
| use rustc_middle::ty::layout::{IntegerExt, TyAndLayout}; |
| use rustc_middle::ty::{self, ExistentialProjection, ParamEnv, Ty, TyCtxt}; |
| use rustc_middle::ty::{GenericArgKind, GenericArgsRef}; |
| use rustc_target::abi::Integer; |
| use smallvec::SmallVec; |
| |
| use std::fmt::Write; |
| |
| use crate::debuginfo::wants_c_like_enum_debuginfo; |
| |
| /// Compute the name of the type as it should be stored in debuginfo. Does not do |
| /// any caching, i.e., calling the function twice with the same type will also do |
| /// the work twice. The `qualified` parameter only affects the first level of the |
| /// type name, further levels (i.e., type parameters) are always fully qualified. |
| pub fn compute_debuginfo_type_name<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| t: Ty<'tcx>, |
| qualified: bool, |
| ) -> String { |
| let _prof = tcx.prof.generic_activity("compute_debuginfo_type_name"); |
| |
| let mut result = String::with_capacity(64); |
| let mut visited = FxHashSet::default(); |
| push_debuginfo_type_name(tcx, t, qualified, &mut result, &mut visited); |
| result |
| } |
| |
| // Pushes the name of the type as it should be stored in debuginfo on the |
| // `output` String. See also compute_debuginfo_type_name(). |
| fn push_debuginfo_type_name<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| t: Ty<'tcx>, |
| qualified: bool, |
| output: &mut String, |
| visited: &mut FxHashSet<Ty<'tcx>>, |
| ) { |
| // When targeting MSVC, emit C++ style type names for compatibility with |
| // .natvis visualizers (and perhaps other existing native debuggers?) |
| let cpp_like_debuginfo = cpp_like_debuginfo(tcx); |
| |
| match *t.kind() { |
| ty::Bool => output.push_str("bool"), |
| ty::Char => output.push_str("char"), |
| ty::Str => { |
| if cpp_like_debuginfo { |
| output.push_str("str$") |
| } else { |
| output.push_str("str") |
| } |
| } |
| ty::Never => { |
| if cpp_like_debuginfo { |
| output.push_str("never$"); |
| } else { |
| output.push('!'); |
| } |
| } |
| ty::Int(int_ty) => output.push_str(int_ty.name_str()), |
| ty::Uint(uint_ty) => output.push_str(uint_ty.name_str()), |
| ty::Float(float_ty) => output.push_str(float_ty.name_str()), |
| ty::Foreign(def_id) => push_item_name(tcx, def_id, qualified, output), |
| ty::Adt(def, args) => { |
| // `layout_for_cpp_like_fallback` will be `Some` if we want to use the fallback encoding. |
| let layout_for_cpp_like_fallback = if cpp_like_debuginfo && def.is_enum() { |
| match tcx.layout_of(ParamEnv::reveal_all().and(t)) { |
| Ok(layout) => { |
| if !wants_c_like_enum_debuginfo(layout) { |
| Some(layout) |
| } else { |
| // This is a C-like enum so we don't want to use the fallback encoding |
| // for the name. |
| None |
| } |
| } |
| Err(e) => { |
| // Computing the layout can still fail here, e.g. if the target architecture |
| // cannot represent the type. See https://github.com/rust-lang/rust/issues/94961. |
| tcx.sess.emit_fatal(e.into_diagnostic()); |
| } |
| } |
| } else { |
| // We are not emitting cpp-like debuginfo or this isn't even an enum. |
| None |
| }; |
| |
| if let Some(ty_and_layout) = layout_for_cpp_like_fallback { |
| msvc_enum_fallback( |
| ty_and_layout, |
| &|output, visited| { |
| push_item_name(tcx, def.did(), true, output); |
| push_generic_params_internal(tcx, args, def.did(), output, visited); |
| }, |
| output, |
| visited, |
| ); |
| } else { |
| push_item_name(tcx, def.did(), qualified, output); |
| push_generic_params_internal(tcx, args, def.did(), output, visited); |
| } |
| } |
| ty::Tuple(component_types) => { |
| if cpp_like_debuginfo { |
| output.push_str("tuple$<"); |
| } else { |
| output.push('('); |
| } |
| |
| for component_type in component_types { |
| push_debuginfo_type_name(tcx, component_type, true, output, visited); |
| push_arg_separator(cpp_like_debuginfo, output); |
| } |
| if !component_types.is_empty() { |
| pop_arg_separator(output); |
| } |
| |
| if cpp_like_debuginfo { |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| } else { |
| output.push(')'); |
| } |
| } |
| ty::RawPtr(ty::TypeAndMut { ty: inner_type, mutbl }) => { |
| if cpp_like_debuginfo { |
| match mutbl { |
| Mutability::Not => output.push_str("ptr_const$<"), |
| Mutability::Mut => output.push_str("ptr_mut$<"), |
| } |
| } else { |
| output.push('*'); |
| match mutbl { |
| Mutability::Not => output.push_str("const "), |
| Mutability::Mut => output.push_str("mut "), |
| } |
| } |
| |
| push_debuginfo_type_name(tcx, inner_type, qualified, output, visited); |
| |
| if cpp_like_debuginfo { |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| } |
| } |
| ty::Ref(_, inner_type, mutbl) => { |
| if cpp_like_debuginfo { |
| match mutbl { |
| Mutability::Not => output.push_str("ref$<"), |
| Mutability::Mut => output.push_str("ref_mut$<"), |
| } |
| } else { |
| output.push('&'); |
| output.push_str(mutbl.prefix_str()); |
| } |
| |
| push_debuginfo_type_name(tcx, inner_type, qualified, output, visited); |
| |
| if cpp_like_debuginfo { |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| } |
| } |
| ty::Array(inner_type, len) => { |
| if cpp_like_debuginfo { |
| output.push_str("array$<"); |
| push_debuginfo_type_name(tcx, inner_type, true, output, visited); |
| match len.kind() { |
| ty::ConstKind::Param(param) => write!(output, ",{}>", param.name).unwrap(), |
| _ => write!( |
| output, |
| ",{}>", |
| len.eval_target_usize(tcx, ty::ParamEnv::reveal_all()) |
| ) |
| .unwrap(), |
| } |
| } else { |
| output.push('['); |
| push_debuginfo_type_name(tcx, inner_type, true, output, visited); |
| match len.kind() { |
| ty::ConstKind::Param(param) => write!(output, "; {}]", param.name).unwrap(), |
| _ => write!( |
| output, |
| "; {}]", |
| len.eval_target_usize(tcx, ty::ParamEnv::reveal_all()) |
| ) |
| .unwrap(), |
| } |
| } |
| } |
| ty::Slice(inner_type) => { |
| if cpp_like_debuginfo { |
| output.push_str("slice2$<"); |
| } else { |
| output.push('['); |
| } |
| |
| push_debuginfo_type_name(tcx, inner_type, true, output, visited); |
| |
| if cpp_like_debuginfo { |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| } else { |
| output.push(']'); |
| } |
| } |
| ty::Dynamic(ref trait_data, ..) => { |
| let auto_traits: SmallVec<[DefId; 4]> = trait_data.auto_traits().collect(); |
| |
| let has_enclosing_parens = if cpp_like_debuginfo { |
| output.push_str("dyn$<"); |
| false |
| } else { |
| if trait_data.len() > 1 && auto_traits.len() != 0 { |
| // We need enclosing parens because there is more than one trait |
| output.push_str("(dyn "); |
| true |
| } else { |
| output.push_str("dyn "); |
| false |
| } |
| }; |
| |
| if let Some(principal) = trait_data.principal() { |
| let principal = |
| tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), principal); |
| push_item_name(tcx, principal.def_id, qualified, output); |
| let principal_has_generic_params = push_generic_params_internal( |
| tcx, |
| principal.args, |
| principal.def_id, |
| output, |
| visited, |
| ); |
| |
| let projection_bounds: SmallVec<[_; 4]> = trait_data |
| .projection_bounds() |
| .map(|bound| { |
| let ExistentialProjection { def_id: item_def_id, term, .. } = |
| tcx.erase_late_bound_regions(bound); |
| // FIXME(associated_const_equality): allow for consts here |
| (item_def_id, term.ty().unwrap()) |
| }) |
| .collect(); |
| |
| if projection_bounds.len() != 0 { |
| if principal_has_generic_params { |
| // push_generic_params_internal() above added a `>` but we actually |
| // want to add more items to that list, so remove that again... |
| pop_close_angle_bracket(output); |
| // .. and add a comma to separate the regular generic args from the |
| // associated types. |
| push_arg_separator(cpp_like_debuginfo, output); |
| } else { |
| // push_generic_params_internal() did not add `<...>`, so we open |
| // angle brackets here. |
| output.push('<'); |
| } |
| |
| for (item_def_id, ty) in projection_bounds { |
| if cpp_like_debuginfo { |
| output.push_str("assoc$<"); |
| push_item_name(tcx, item_def_id, false, output); |
| push_arg_separator(cpp_like_debuginfo, output); |
| push_debuginfo_type_name(tcx, ty, true, output, visited); |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| } else { |
| push_item_name(tcx, item_def_id, false, output); |
| output.push('='); |
| push_debuginfo_type_name(tcx, ty, true, output, visited); |
| } |
| push_arg_separator(cpp_like_debuginfo, output); |
| } |
| |
| pop_arg_separator(output); |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| } |
| |
| if auto_traits.len() != 0 { |
| push_auto_trait_separator(cpp_like_debuginfo, output); |
| } |
| } |
| |
| if auto_traits.len() != 0 { |
| let mut auto_traits: SmallVec<[String; 4]> = auto_traits |
| .into_iter() |
| .map(|def_id| { |
| let mut name = String::with_capacity(20); |
| push_item_name(tcx, def_id, true, &mut name); |
| name |
| }) |
| .collect(); |
| auto_traits.sort_unstable(); |
| |
| for auto_trait in auto_traits { |
| output.push_str(&auto_trait); |
| push_auto_trait_separator(cpp_like_debuginfo, output); |
| } |
| |
| pop_auto_trait_separator(output); |
| } |
| |
| if cpp_like_debuginfo { |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| } else if has_enclosing_parens { |
| output.push(')'); |
| } |
| } |
| ty::FnDef(..) | ty::FnPtr(_) => { |
| // We've encountered a weird 'recursive type' |
| // Currently, the only way to generate such a type |
| // is by using 'impl trait': |
| // |
| // fn foo() -> impl Copy { foo } |
| // |
| // There's not really a sensible name we can generate, |
| // since we don't include 'impl trait' types (e.g. ty::Opaque) |
| // in the output |
| // |
| // Since we need to generate *something*, we just |
| // use a dummy string that should make it clear |
| // that something unusual is going on |
| if !visited.insert(t) { |
| output.push_str(if cpp_like_debuginfo { |
| "recursive_type$" |
| } else { |
| "<recursive_type>" |
| }); |
| return; |
| } |
| |
| let sig = |
| tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), t.fn_sig(tcx)); |
| |
| if cpp_like_debuginfo { |
| // Format as a C++ function pointer: return_type (*)(params...) |
| if sig.output().is_unit() { |
| output.push_str("void"); |
| } else { |
| push_debuginfo_type_name(tcx, sig.output(), true, output, visited); |
| } |
| output.push_str(" (*)("); |
| } else { |
| output.push_str(sig.unsafety.prefix_str()); |
| |
| if sig.abi != rustc_target::spec::abi::Abi::Rust { |
| output.push_str("extern \""); |
| output.push_str(sig.abi.name()); |
| output.push_str("\" "); |
| } |
| |
| output.push_str("fn("); |
| } |
| |
| if !sig.inputs().is_empty() { |
| for ¶meter_type in sig.inputs() { |
| push_debuginfo_type_name(tcx, parameter_type, true, output, visited); |
| push_arg_separator(cpp_like_debuginfo, output); |
| } |
| pop_arg_separator(output); |
| } |
| |
| if sig.c_variadic { |
| if !sig.inputs().is_empty() { |
| output.push_str(", ..."); |
| } else { |
| output.push_str("..."); |
| } |
| } |
| |
| output.push(')'); |
| |
| if !cpp_like_debuginfo && !sig.output().is_unit() { |
| output.push_str(" -> "); |
| push_debuginfo_type_name(tcx, sig.output(), true, output, visited); |
| } |
| |
| // We only keep the type in 'visited' |
| // for the duration of the body of this method. |
| // It's fine for a particular function type |
| // to show up multiple times in one overall type |
| // (e.g. MyType<fn() -> u8, fn() -> u8> |
| // |
| // We only care about avoiding recursing |
| // directly back to the type we're currently |
| // processing |
| visited.remove(&t); |
| } |
| ty::Closure(def_id, args) | ty::Coroutine(def_id, args, ..) => { |
| // Name will be "{closure_env#0}<T1, T2, ...>", "{coroutine_env#0}<T1, T2, ...>", or |
| // "{async_fn_env#0}<T1, T2, ...>", etc. |
| // In the case of cpp-like debuginfo, the name additionally gets wrapped inside of |
| // an artificial `enum2$<>` type, as defined in msvc_enum_fallback(). |
| if cpp_like_debuginfo && t.is_coroutine() { |
| let ty_and_layout = tcx.layout_of(ParamEnv::reveal_all().and(t)).unwrap(); |
| msvc_enum_fallback( |
| ty_and_layout, |
| &|output, visited| { |
| push_closure_or_coroutine_name(tcx, def_id, args, true, output, visited); |
| }, |
| output, |
| visited, |
| ); |
| } else { |
| push_closure_or_coroutine_name(tcx, def_id, args, qualified, output, visited); |
| } |
| } |
| // Type parameters from polymorphized functions. |
| ty::Param(_) => { |
| write!(output, "{t:?}").unwrap(); |
| } |
| ty::Error(_) |
| | ty::Infer(_) |
| | ty::Placeholder(..) |
| | ty::Alias(..) |
| | ty::Bound(..) |
| | ty::CoroutineWitness(..) => { |
| bug!( |
| "debuginfo: Trying to create type name for \ |
| unexpected type: {:?}", |
| t |
| ); |
| } |
| } |
| |
| /// MSVC names enums differently than other platforms so that the debugging visualization |
| // format (natvis) is able to understand enums and render the active variant correctly in the |
| // debugger. For more information, look in |
| // rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs. |
| fn msvc_enum_fallback<'tcx>( |
| ty_and_layout: TyAndLayout<'tcx>, |
| push_inner: &dyn Fn(/*output*/ &mut String, /*visited*/ &mut FxHashSet<Ty<'tcx>>), |
| output: &mut String, |
| visited: &mut FxHashSet<Ty<'tcx>>, |
| ) { |
| debug_assert!(!wants_c_like_enum_debuginfo(ty_and_layout)); |
| output.push_str("enum2$<"); |
| push_inner(output, visited); |
| push_close_angle_bracket(true, output); |
| } |
| |
| const NON_CPP_AUTO_TRAIT_SEPARATOR: &str = " + "; |
| |
| fn push_auto_trait_separator(cpp_like_debuginfo: bool, output: &mut String) { |
| if cpp_like_debuginfo { |
| push_arg_separator(cpp_like_debuginfo, output); |
| } else { |
| output.push_str(NON_CPP_AUTO_TRAIT_SEPARATOR); |
| } |
| } |
| |
| fn pop_auto_trait_separator(output: &mut String) { |
| if output.ends_with(NON_CPP_AUTO_TRAIT_SEPARATOR) { |
| output.truncate(output.len() - NON_CPP_AUTO_TRAIT_SEPARATOR.len()); |
| } else { |
| pop_arg_separator(output); |
| } |
| } |
| } |
| |
| pub enum VTableNameKind { |
| // Is the name for the const/static holding the vtable? |
| GlobalVariable, |
| // Is the name for the type of the vtable? |
| Type, |
| } |
| |
| /// Computes a name for the global variable storing a vtable (or the type of that global variable). |
| /// |
| /// The name is of the form: |
| /// |
| /// `<path::to::SomeType as path::to::SomeTrait>::{vtable}` |
| /// |
| /// or, when generating C++-like names: |
| /// |
| /// `impl$<path::to::SomeType, path::to::SomeTrait>::vtable$` |
| /// |
| /// If `kind` is `VTableNameKind::Type` then the last component is `{vtable_ty}` instead of just |
| /// `{vtable}`, so that the type and the corresponding global variable get assigned different |
| /// names. |
| pub fn compute_debuginfo_vtable_name<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| t: Ty<'tcx>, |
| trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>, |
| kind: VTableNameKind, |
| ) -> String { |
| let cpp_like_debuginfo = cpp_like_debuginfo(tcx); |
| |
| let mut vtable_name = String::with_capacity(64); |
| |
| if cpp_like_debuginfo { |
| vtable_name.push_str("impl$<"); |
| } else { |
| vtable_name.push('<'); |
| } |
| |
| let mut visited = FxHashSet::default(); |
| push_debuginfo_type_name(tcx, t, true, &mut vtable_name, &mut visited); |
| |
| if cpp_like_debuginfo { |
| vtable_name.push_str(", "); |
| } else { |
| vtable_name.push_str(" as "); |
| } |
| |
| if let Some(trait_ref) = trait_ref { |
| let trait_ref = |
| tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), trait_ref); |
| push_item_name(tcx, trait_ref.def_id, true, &mut vtable_name); |
| visited.clear(); |
| push_generic_params_internal( |
| tcx, |
| trait_ref.args, |
| trait_ref.def_id, |
| &mut vtable_name, |
| &mut visited, |
| ); |
| } else { |
| vtable_name.push('_'); |
| } |
| |
| push_close_angle_bracket(cpp_like_debuginfo, &mut vtable_name); |
| |
| let suffix = match (cpp_like_debuginfo, kind) { |
| (true, VTableNameKind::GlobalVariable) => "::vtable$", |
| (false, VTableNameKind::GlobalVariable) => "::{vtable}", |
| (true, VTableNameKind::Type) => "::vtable_type$", |
| (false, VTableNameKind::Type) => "::{vtable_type}", |
| }; |
| |
| vtable_name.reserve_exact(suffix.len()); |
| vtable_name.push_str(suffix); |
| |
| vtable_name |
| } |
| |
| pub fn push_item_name(tcx: TyCtxt<'_>, def_id: DefId, qualified: bool, output: &mut String) { |
| let def_key = tcx.def_key(def_id); |
| if qualified { |
| if let Some(parent) = def_key.parent { |
| push_item_name(tcx, DefId { krate: def_id.krate, index: parent }, true, output); |
| output.push_str("::"); |
| } |
| } |
| |
| push_unqualified_item_name(tcx, def_id, def_key.disambiguated_data, output); |
| } |
| |
| fn coroutine_kind_label(coroutine_kind: Option<CoroutineKind>) -> &'static str { |
| match coroutine_kind { |
| Some(CoroutineKind::Gen(CoroutineSource::Block)) => "gen_block", |
| Some(CoroutineKind::Gen(CoroutineSource::Closure)) => "gen_closure", |
| Some(CoroutineKind::Gen(CoroutineSource::Fn)) => "gen_fn", |
| Some(CoroutineKind::Async(CoroutineSource::Block)) => "async_block", |
| Some(CoroutineKind::Async(CoroutineSource::Closure)) => "async_closure", |
| Some(CoroutineKind::Async(CoroutineSource::Fn)) => "async_fn", |
| Some(CoroutineKind::Coroutine) => "coroutine", |
| None => "closure", |
| } |
| } |
| |
| fn push_disambiguated_special_name( |
| label: &str, |
| disambiguator: u32, |
| cpp_like_debuginfo: bool, |
| output: &mut String, |
| ) { |
| if cpp_like_debuginfo { |
| write!(output, "{label}${disambiguator}").unwrap(); |
| } else { |
| write!(output, "{{{label}#{disambiguator}}}").unwrap(); |
| } |
| } |
| |
| fn push_unqualified_item_name( |
| tcx: TyCtxt<'_>, |
| def_id: DefId, |
| disambiguated_data: DisambiguatedDefPathData, |
| output: &mut String, |
| ) { |
| match disambiguated_data.data { |
| DefPathData::CrateRoot => { |
| output.push_str(tcx.crate_name(def_id.krate).as_str()); |
| } |
| DefPathData::ClosureExpr => { |
| let label = coroutine_kind_label(tcx.coroutine_kind(def_id)); |
| |
| push_disambiguated_special_name( |
| label, |
| disambiguated_data.disambiguator, |
| cpp_like_debuginfo(tcx), |
| output, |
| ); |
| } |
| _ => match disambiguated_data.data.name() { |
| DefPathDataName::Named(name) => { |
| output.push_str(name.as_str()); |
| } |
| DefPathDataName::Anon { namespace } => { |
| push_disambiguated_special_name( |
| namespace.as_str(), |
| disambiguated_data.disambiguator, |
| cpp_like_debuginfo(tcx), |
| output, |
| ); |
| } |
| }, |
| }; |
| } |
| |
| fn push_generic_params_internal<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| args: GenericArgsRef<'tcx>, |
| def_id: DefId, |
| output: &mut String, |
| visited: &mut FxHashSet<Ty<'tcx>>, |
| ) -> bool { |
| debug_assert_eq!(args, tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), args)); |
| let mut args = args.non_erasable_generics(tcx, def_id).peekable(); |
| if args.peek().is_none() { |
| return false; |
| } |
| let cpp_like_debuginfo = cpp_like_debuginfo(tcx); |
| |
| output.push('<'); |
| |
| for type_parameter in args { |
| match type_parameter { |
| GenericArgKind::Type(type_parameter) => { |
| push_debuginfo_type_name(tcx, type_parameter, true, output, visited); |
| } |
| GenericArgKind::Const(ct) => { |
| push_const_param(tcx, ct, output); |
| } |
| other => bug!("Unexpected non-erasable generic: {:?}", other), |
| } |
| |
| push_arg_separator(cpp_like_debuginfo, output); |
| } |
| pop_arg_separator(output); |
| push_close_angle_bracket(cpp_like_debuginfo, output); |
| |
| true |
| } |
| |
| fn push_const_param<'tcx>(tcx: TyCtxt<'tcx>, ct: ty::Const<'tcx>, output: &mut String) { |
| match ct.kind() { |
| ty::ConstKind::Param(param) => { |
| write!(output, "{}", param.name) |
| } |
| _ => match ct.ty().kind() { |
| ty::Int(ity) => { |
| let bits = ct.eval_bits(tcx, ty::ParamEnv::reveal_all()); |
| let val = Integer::from_int_ty(&tcx, *ity).size().sign_extend(bits) as i128; |
| write!(output, "{val}") |
| } |
| ty::Uint(_) => { |
| let val = ct.eval_bits(tcx, ty::ParamEnv::reveal_all()); |
| write!(output, "{val}") |
| } |
| ty::Bool => { |
| let val = ct.try_eval_bool(tcx, ty::ParamEnv::reveal_all()).unwrap(); |
| write!(output, "{val}") |
| } |
| _ => { |
| // If we cannot evaluate the constant to a known type, we fall back |
| // to emitting a stable hash value of the constant. This isn't very pretty |
| // but we get a deterministic, virtually unique value for the constant. |
| // |
| // Let's only emit 64 bits of the hash value. That should be plenty for |
| // avoiding collisions and will make the emitted type names shorter. |
| let hash_short = tcx.with_stable_hashing_context(|mut hcx| { |
| let mut hasher = StableHasher::new(); |
| let ct = ct.eval(tcx, ty::ParamEnv::reveal_all(), None).unwrap(); |
| hcx.while_hashing_spans(false, |hcx| ct.hash_stable(hcx, &mut hasher)); |
| hasher.finish::<Hash64>() |
| }); |
| |
| if cpp_like_debuginfo(tcx) { |
| write!(output, "CONST${hash_short:x}") |
| } else { |
| write!(output, "{{CONST#{hash_short:x}}}") |
| } |
| } |
| }, |
| } |
| .unwrap(); |
| } |
| |
| pub fn push_generic_params<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| args: GenericArgsRef<'tcx>, |
| def_id: DefId, |
| output: &mut String, |
| ) { |
| let _prof = tcx.prof.generic_activity("compute_debuginfo_type_name"); |
| let mut visited = FxHashSet::default(); |
| push_generic_params_internal(tcx, args, def_id, output, &mut visited); |
| } |
| |
| fn push_closure_or_coroutine_name<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| def_id: DefId, |
| args: GenericArgsRef<'tcx>, |
| qualified: bool, |
| output: &mut String, |
| visited: &mut FxHashSet<Ty<'tcx>>, |
| ) { |
| // Name will be "{closure_env#0}<T1, T2, ...>", "{coroutine_env#0}<T1, T2, ...>", or |
| // "{async_fn_env#0}<T1, T2, ...>", etc. |
| let def_key = tcx.def_key(def_id); |
| let coroutine_kind = tcx.coroutine_kind(def_id); |
| |
| if qualified { |
| let parent_def_id = DefId { index: def_key.parent.unwrap(), ..def_id }; |
| push_item_name(tcx, parent_def_id, true, output); |
| output.push_str("::"); |
| } |
| |
| let mut label = String::with_capacity(20); |
| write!(&mut label, "{}_env", coroutine_kind_label(coroutine_kind)).unwrap(); |
| |
| push_disambiguated_special_name( |
| &label, |
| def_key.disambiguated_data.disambiguator, |
| cpp_like_debuginfo(tcx), |
| output, |
| ); |
| |
| // We also need to add the generic arguments of the async fn/coroutine or |
| // the enclosing function (for closures or async blocks), so that we end |
| // up with a unique name for every instantiation. |
| |
| // Find the generics of the enclosing function, as defined in the source code. |
| let enclosing_fn_def_id = tcx.typeck_root_def_id(def_id); |
| let generics = tcx.generics_of(enclosing_fn_def_id); |
| |
| // Truncate the args to the length of the above generics. This will cut off |
| // anything closure- or coroutine-specific. |
| let args = args.truncate_to(tcx, generics); |
| push_generic_params_internal(tcx, args, enclosing_fn_def_id, output, visited); |
| } |
| |
| fn push_close_angle_bracket(cpp_like_debuginfo: bool, output: &mut String) { |
| // MSVC debugger always treats `>>` as a shift, even when parsing templates, |
| // so add a space to avoid confusion. |
| if cpp_like_debuginfo && output.ends_with('>') { |
| output.push(' ') |
| }; |
| |
| output.push('>'); |
| } |
| |
| fn pop_close_angle_bracket(output: &mut String) { |
| assert!(output.ends_with('>'), "'output' does not end with '>': {output}"); |
| output.pop(); |
| if output.ends_with(' ') { |
| output.pop(); |
| } |
| } |
| |
| fn push_arg_separator(cpp_like_debuginfo: bool, output: &mut String) { |
| // Natvis does not always like having spaces between parts of the type name |
| // and this causes issues when we need to write a typename in natvis, for example |
| // as part of a cast like the `HashMap` visualizer does. |
| if cpp_like_debuginfo { |
| output.push(','); |
| } else { |
| output.push_str(", "); |
| }; |
| } |
| |
| fn pop_arg_separator(output: &mut String) { |
| if output.ends_with(' ') { |
| output.pop(); |
| } |
| |
| assert!(output.ends_with(',')); |
| |
| output.pop(); |
| } |
| |
| /// Check if we should generate C++ like names and debug information. |
| pub fn cpp_like_debuginfo(tcx: TyCtxt<'_>) -> bool { |
| tcx.sess.target.is_like_msvc |
| } |