refactor item-paths in diagnostics, symbol names

This change has a few parts. We introduce a new `item_path` module for
constructing item paths. The job of this module is basically to make
nice, user-readable paths -- but these paths are not necessarily 100%
unique. They meant to help a *human* find code, but not necessarily a
compute. These paths are used to drive `item_path_str` but also symbol
names.

Because the paths are not unique, we also modify the symbol name hash to
include the full `DefPath`, whereas before it included only those
aspects of the def-path that were not included in the "informative"
symbol name.

Eventually, I'd like to make the item-path infrastructure a bit more
declarative.  Right now it's based purely on strings. In particular, for
impls, we should supply the raw types to the `ItemPathBuffer`, so that
symbol names can be encoded using the C++ encoding scheme for better
integration with tooling.
This commit is contained in:
Niko Matsakis 2016-03-16 05:57:03 -04:00
parent cd5cf09635
commit 2291abf313
3 changed files with 364 additions and 28 deletions

View File

@ -0,0 +1,317 @@
// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use front::map::DefPathData;
use middle::cstore::LOCAL_CRATE;
use middle::def_id::DefId;
use middle::ty::{self, Ty, TyCtxt};
use syntax::ast;
impl<'tcx> TyCtxt<'tcx> {
/// Returns a string identifying this def-id. This string is
/// suitable for user output. It is relative to the current crate
/// root.
pub fn item_path_str(&self, def_id: DefId) -> String {
let mut buffer = LocalPathBuffer::new(RootMode::Local);
self.push_item_path(&mut buffer, def_id);
buffer.into_string()
}
/// Returns a string identifying this def-id. This string is
/// suitable for user output. It always begins with a crate identifier.
pub fn absolute_item_path_str(&self, def_id: DefId) -> String {
let mut buffer = LocalPathBuffer::new(RootMode::Absolute);
self.push_item_path(&mut buffer, def_id);
buffer.into_string()
}
/// Returns the "path" to a particular crate. This can proceed in
/// various ways, depending on the `root_mode` of the `buffer`.
/// (See `RootMode` enum for more details.)
pub fn push_krate_path<T>(&self, buffer: &mut T, cnum: ast::CrateNum)
where T: ItemPathBuffer
{
match *buffer.root_mode() {
RootMode::Local => {
// In local mode, when we encounter a crate other than
// LOCAL_CRATE, execution proceeds in one of two ways:
//
// 1. for a direct dependency, where user added an
// `extern crate` manually, we put the `extern
// crate` as the parent. So you wind up with
// something relative to the current crate.
// 2. for an indirect crate, where there is no extern
// crate, we just prepend the crate name.
//
// Returns `None` for the local crate.
if cnum != LOCAL_CRATE {
let opt_extern_crate = self.sess.cstore.extern_crate(cnum);
let opt_extern_crate = opt_extern_crate.and_then(|extern_crate| {
if extern_crate.direct {
Some(extern_crate.def_id)
} else {
None
}
});
if let Some(extern_crate_def_id) = opt_extern_crate {
self.push_item_path(buffer, extern_crate_def_id);
} else {
buffer.push(&self.crate_name(cnum));
}
}
}
RootMode::Absolute => {
// In absolute mode, just write the crate name
// unconditionally.
buffer.push(&self.crate_name(cnum));
}
}
}
pub fn push_item_path<T>(&self, buffer: &mut T, def_id: DefId)
where T: ItemPathBuffer
{
let key = self.def_key(def_id);
match key.disambiguated_data.data {
DefPathData::CrateRoot => {
assert!(key.parent.is_none());
self.push_krate_path(buffer, def_id.krate);
}
DefPathData::InlinedRoot(ref root_path) => {
assert!(key.parent.is_none());
self.push_item_path(buffer, root_path.def_id);
}
DefPathData::Impl => {
self.push_impl_path(buffer, def_id);
}
// Unclear if there is any value in distinguishing these.
// Probably eventually (and maybe we would even want
// finer-grained distinctions, e.g. between enum/struct).
data @ DefPathData::Misc |
data @ DefPathData::TypeNs(..) |
data @ DefPathData::ValueNs(..) |
data @ DefPathData::TypeParam(..) |
data @ DefPathData::LifetimeDef(..) |
data @ DefPathData::EnumVariant(..) |
data @ DefPathData::Field(..) |
data @ DefPathData::StructCtor |
data @ DefPathData::Initializer |
data @ DefPathData::MacroDef(..) |
data @ DefPathData::ClosureExpr |
data @ DefPathData::Binding(..) => {
let parent_def_id = self.parent_def_id(def_id).unwrap();
self.push_item_path(buffer, parent_def_id);
buffer.push(&data.as_interned_str());
}
}
}
fn push_impl_path<T>(&self,
buffer: &mut T,
impl_def_id: DefId)
where T: ItemPathBuffer
{
let parent_def_id = self.parent_def_id(impl_def_id).unwrap();
let use_types = if !impl_def_id.is_local() {
// always have full types available for extern crates
true
} else {
// for local crates, check whether type info is
// available; typeck might not have completed yet
self.impl_trait_refs.borrow().contains_key(&impl_def_id)
};
if !use_types {
return self.push_impl_path_fallback(buffer, impl_def_id);
}
// Decide whether to print the parent path for the impl.
// Logically, since impls are global, it's never needed, but
// users may find it useful. Currently, we omit the parent if
// the impl is either in the same module as the self-type or
// as the trait.
let self_ty = self.lookup_item_type(impl_def_id).ty;
let in_self_mod = match self.characteristic_def_id_of_type(self_ty) {
None => false,
Some(ty_def_id) => self.parent_def_id(ty_def_id) == Some(parent_def_id),
};
let impl_trait_ref = self.impl_trait_ref(impl_def_id);
let in_trait_mod = match impl_trait_ref {
None => false,
Some(trait_ref) => self.parent_def_id(trait_ref.def_id) == Some(parent_def_id),
};
if !in_self_mod && !in_trait_mod {
// If the impl is not co-located with either self-type or
// trait-type, then fallback to a format that identifies
// the module more clearly.
self.push_item_path(buffer, parent_def_id);
if let Some(trait_ref) = impl_trait_ref {
buffer.push(&format!("<impl {} for {}>", trait_ref, self_ty));
} else {
buffer.push(&format!("<impl {}>", self_ty));
}
return;
}
// Otherwise, try to give a good form that would be valid language
// syntax. Preferably using associated item notation.
if let Some(trait_ref) = impl_trait_ref {
// Trait impls.
buffer.push(&format!("<{} as {}>",
self_ty,
trait_ref));
return;
}
// Inherent impls. Try to print `Foo::bar` for an inherent
// impl on `Foo`, but fallback to `<Foo>::bar` if self-type is
// anything other than a simple path.
match self_ty.sty {
ty::TyStruct(adt_def, substs) |
ty::TyEnum(adt_def, substs) => {
if substs.types.is_empty() { // ignore regions
self.push_item_path(buffer, adt_def.did);
} else {
buffer.push(&format!("<{}>", self_ty));
}
}
ty::TyBool |
ty::TyChar |
ty::TyInt(_) |
ty::TyUint(_) |
ty::TyFloat(_) |
ty::TyStr => {
buffer.push(&format!("{}", self_ty));
}
_ => {
buffer.push(&format!("<{}>", self_ty));
}
}
}
fn push_impl_path_fallback<T>(&self,
buffer: &mut T,
impl_def_id: DefId)
where T: ItemPathBuffer
{
// If no type info is available, fall back to
// pretty printing some span information. This should
// only occur very early in the compiler pipeline.
let parent_def_id = self.parent_def_id(impl_def_id).unwrap();
self.push_item_path(buffer, parent_def_id);
let node_id = self.map.as_local_node_id(impl_def_id).unwrap();
let item = self.map.expect_item(node_id);
let span_str = self.sess.codemap().span_to_string(item.span);
buffer.push(&format!("<impl at {}>", span_str));
}
/// As a heuristic, when we see an impl, if we see that the
/// 'self-type' is a type defined in the same module as the impl,
/// we can omit including the path to the impl itself. This
/// function tries to find a "characteristic def-id" for a
/// type. It's just a heuristic so it makes some questionable
/// decisions and we may want to adjust it later.
fn characteristic_def_id_of_type(&self, ty: Ty<'tcx>) -> Option<DefId> {
match ty.sty {
ty::TyStruct(adt_def, _) |
ty::TyEnum(adt_def, _) =>
Some(adt_def.did),
ty::TyTrait(ref data) =>
Some(data.principal_def_id()),
ty::TyBox(subty) =>
self.characteristic_def_id_of_type(subty),
ty::TyRawPtr(mt) |
ty::TyRef(_, mt) =>
self.characteristic_def_id_of_type(mt.ty),
ty::TyTuple(ref tys) =>
tys.iter()
.filter_map(|ty| self.characteristic_def_id_of_type(ty))
.next(),
_ =>
None
}
}
/// Returns the def-id of `def_id`'s parent in the def tree. If
/// this returns `None`, then `def_id` represents a crate root or
/// inlined root.
fn parent_def_id(&self, def_id: DefId) -> Option<DefId> {
let key = self.def_key(def_id);
key.parent.map(|index| DefId { krate: def_id.krate, index: index })
}
}
/// Unifying Trait for different kinds of item paths we might
/// construct. The basic interface is that components get pushed: the
/// instance can also customize how we handle the root of a crate.
pub trait ItemPathBuffer {
fn root_mode(&self) -> &RootMode;
fn push(&mut self, text: &str);
}
#[derive(Debug)]
pub enum RootMode {
/// Try to make a path relative to the local crate. In
/// particular, local paths have no prefix, and if the path comes
/// from an extern crate, start with the path to the `extern
/// crate` declaration.
Local,
/// Always prepend the crate name to the path, forming an absolute
/// path from within a given set of crates.
Absolute,
}
#[derive(Debug)]
struct LocalPathBuffer {
root_mode: RootMode,
str: String,
}
impl LocalPathBuffer {
fn new(root_mode: RootMode) -> LocalPathBuffer {
LocalPathBuffer {
root_mode: root_mode,
str: String::new()
}
}
fn into_string(self) -> String {
self.str
}
}
impl ItemPathBuffer for LocalPathBuffer {
fn root_mode(&self) -> &RootMode {
&self.root_mode
}
fn push(&mut self, text: &str) {
if !self.str.is_empty() {
self.str.push_str("::");
}
self.str.push_str(text);
}
}

View File

@ -86,6 +86,7 @@ pub mod cast;
pub mod error;
pub mod fast_reject;
pub mod fold;
pub mod item_path;
pub mod _match;
pub mod maps;
pub mod outlives;
@ -2218,8 +2219,12 @@ impl<'tcx> TyCtxt<'tcx> {
self.def_map.borrow().get(&tr.ref_id).expect("no def-map entry for trait").def_id()
}
pub fn item_path_str(&self, id: DefId) -> String {
self.with_path(id, |path| ast_map::path_to_string(path))
pub fn def_key(&self, id: DefId) -> ast_map::DefKey {
if id.is_local() {
self.map.def_key(id)
} else {
self.sess.cstore.def_key(id)
}
}
/// Returns the `DefPath` of an item. Note that if `id` is not

View File

@ -103,10 +103,10 @@ use util::sha2::{Digest, Sha256};
use rustc::middle::cstore;
use rustc::middle::def_id::DefId;
use rustc::middle::ty::{self, TypeFoldable};
use rustc::middle::ty::item_path::{ItemPathBuffer, RootMode};
use rustc::front::map::definitions::DefPath;
use std::fmt::Write;
use syntax::ast;
use syntax::parse::token::{self, InternedString};
use serialize::hex::ToHex;
@ -135,29 +135,23 @@ pub fn def_path_to_string<'tcx>(tcx: &ty::TyCtxt<'tcx>, def_path: &DefPath) -> S
fn get_symbol_hash<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
def_path: &DefPath,
originating_crate: ast::CrateNum,
parameters: &[ty::Ty<'tcx>])
-> String {
debug!("get_symbol_hash(def_path={:?}, parameters={:?})",
def_path, parameters);
let tcx = ccx.tcx();
let mut hash_state = ccx.symbol_hasher().borrow_mut();
hash_state.reset();
if originating_crate == cstore::LOCAL_CRATE {
hash_state.input_str(&tcx.sess.crate_disambiguator.borrow()[..]);
} else {
hash_state.input_str(&tcx.sess.cstore.crate_disambiguator(originating_crate));
}
for component in def_path {
let disambiguator_bytes = [(component.disambiguator >> 0) as u8,
(component.disambiguator >> 8) as u8,
(component.disambiguator >> 16) as u8,
(component.disambiguator >> 24) as u8];
hash_state.input(&disambiguator_bytes);
}
// the main symbol name is not necessarily unique; hash in the
// compiler's internal def-path, guaranteeing each symbol has a
// truly unique path
hash_state.input_str(&def_path_to_string(tcx, def_path));
// also include any type parameters (for generic items)
for t in parameters {
assert!(!t.has_erasable_regions());
assert!(!t.needs_subst());
@ -180,6 +174,9 @@ fn exported_name_with_opt_suffix<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
-> String {
let &Instance { def: mut def_id, params: parameters } = instance;
debug!("exported_name_with_opt_suffix(def_id={:?}, parameters={:?}, suffix={:?})",
def_id, parameters, suffix);
if let Some(node_id) = ccx.tcx().map.as_local_node_id(def_id) {
if let Some(&src_def_id) = ccx.external_srcs().borrow().get(&node_id) {
def_id = src_def_id;
@ -187,21 +184,34 @@ fn exported_name_with_opt_suffix<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
}
let def_path = ccx.tcx().def_path(def_id);
let hash = get_symbol_hash(ccx, &def_path, def_id.krate, parameters.as_slice());
assert_eq!(def_path.krate, def_id.krate);
let hash = get_symbol_hash(ccx, &def_path, parameters.as_slice());
let mut path = Vec::with_capacity(16);
if def_id.is_local() {
path.push(ccx.tcx().crate_name.clone());
}
path.extend(def_path.into_iter().map(|e| e.data.as_interned_str()));
let mut buffer = SymbolPathBuffer {
names: Vec::with_capacity(def_path.data.len())
};
ccx.tcx().push_item_path(&mut buffer, def_id);
if let Some(suffix) = suffix {
path.push(token::intern_and_get_ident(suffix));
buffer.push(suffix);
}
mangle(path.into_iter(), Some(&hash[..]))
mangle(buffer.names.into_iter(), Some(&hash[..]))
}
struct SymbolPathBuffer {
names: Vec<InternedString>,
}
impl ItemPathBuffer for SymbolPathBuffer {
fn root_mode(&self) -> &RootMode {
const ABSOLUTE: &'static RootMode = &RootMode::Absolute;
ABSOLUTE
}
fn push(&mut self, text: &str) {
self.names.push(token::intern(text).as_str());
}
}
pub fn exported_name<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
@ -225,7 +235,11 @@ pub fn internal_name_from_type_and_suffix<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>
-> String {
let path = [token::intern(&t.to_string()).as_str(),
gensym_name(suffix).as_str()];
let hash = get_symbol_hash(ccx, &Vec::new(), cstore::LOCAL_CRATE, &[t]);
let def_path = DefPath {
data: vec![],
krate: cstore::LOCAL_CRATE,
};
let hash = get_symbol_hash(ccx, &def_path, &[t]);
mangle(path.iter().cloned(), Some(&hash[..]))
}