]> git.lizzy.rs Git - rust.git/blobdiff - src/librustdoc/html/render.rs
Auto merge of #31715 - mitaa:rdoc-index-crate, r=alexcrichton
[rust.git] / src / librustdoc / html / render.rs
index dfbe08a0e423d2d204914dbb69cc5185d4a2224b..692d230446cda7b356b028021237f610c28bb141 100644 (file)
@@ -52,7 +52,7 @@
 
 use externalfiles::ExternalHtml;
 
-use serialize::json::{self, ToJson};
+use serialize::json::{ToJson, Json, as_json};
 use syntax::{abi, ast};
 use syntax::feature_gate::UnstableFeatures;
 use rustc::middle::cstore::LOCAL_CRATE;
@@ -290,22 +290,40 @@ struct IndexItem {
     path: String,
     desc: String,
     parent: Option<DefId>,
+    parent_idx: Option<usize>,
     search_type: Option<IndexItemFunctionType>,
 }
 
+impl ToJson for IndexItem {
+    fn to_json(&self) -> Json {
+        assert_eq!(self.parent.is_some(), self.parent_idx.is_some());
+
+        let mut data = Vec::with_capacity(6);
+        data.push((self.ty as usize).to_json());
+        data.push(self.name.to_json());
+        data.push(self.path.to_json());
+        data.push(self.desc.to_json());
+        data.push(self.parent_idx.to_json());
+        data.push(self.search_type.to_json());
+
+        Json::Array(data)
+    }
+}
+
 /// A type used for the search index.
 struct Type {
     name: Option<String>,
 }
 
-impl fmt::Display for Type {
-    /// Formats type as {name: $name}.
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        // Wrapping struct fmt should never call us when self.name is None,
-        // but just to be safe we write `null` in that case.
+impl ToJson for Type {
+    fn to_json(&self) -> Json {
         match self.name {
-            Some(ref n) => write!(f, "{{\"name\":\"{}\"}}", n),
-            None => write!(f, "null")
+            Some(ref name) => {
+                let mut data = BTreeMap::new();
+                data.insert("name".to_owned(), name.to_json());
+                Json::Object(data)
+            },
+            None => Json::Null
         }
     }
 }
@@ -316,26 +334,17 @@ struct IndexItemFunctionType {
     output: Option<Type>
 }
 
-impl fmt::Display for IndexItemFunctionType {
-    /// Formats a full fn type as a JSON {inputs: [Type], outputs: Type/null}.
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+impl ToJson for IndexItemFunctionType {
+    fn to_json(&self) -> Json {
         // If we couldn't figure out a type, just write `null`.
-        if self.inputs.iter().any(|ref i| i.name.is_none()) ||
-           (self.output.is_some() && self.output.as_ref().unwrap().name.is_none()) {
-            return write!(f, "null")
+        if self.inputs.iter().chain(self.output.iter()).any(|ref i| i.name.is_none()) {
+            Json::Null
+        } else {
+            let mut data = BTreeMap::new();
+            data.insert("inputs".to_owned(), self.inputs.to_json());
+            data.insert("output".to_owned(), self.output.to_json());
+            Json::Object(data)
         }
-
-        let inputs: Vec<String> = self.inputs.iter().map(|ref t| {
-            format!("{}", t)
-        }).collect();
-        try!(write!(f, "{{\"inputs\":[{}],\"output\":", inputs.join(",")));
-
-        match self.output {
-            Some(ref t) => try!(write!(f, "{}", t)),
-            None => try!(write!(f, "null"))
-        };
-
-        Ok(try!(write!(f, "}}")))
     }
 }
 
@@ -534,101 +543,79 @@ pub fn run(mut krate: clean::Crate,
     cx.krate(krate)
 }
 
+/// Build the search index from the collected metadata
 fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
-    // Build the search index from the collected metadata
     let mut nodeid_to_pathid = HashMap::new();
-    let mut pathid_to_nodeid = Vec::new();
-    {
-        let Cache { ref mut search_index,
-                    ref orphan_methods,
-                    ref mut paths, .. } = *cache;
-
-        // Attach all orphan methods to the type's definition if the type
-        // has since been learned.
-        for &(did, ref item) in orphan_methods {
-            match paths.get(&did) {
-                Some(&(ref fqp, _)) => {
-                    // Needed to determine `self` type.
-                    let parent_basename = Some(fqp[fqp.len() - 1].clone());
-                    search_index.push(IndexItem {
-                        ty: shortty(item),
-                        name: item.name.clone().unwrap(),
-                        path: fqp[..fqp.len() - 1].join("::"),
-                        desc: shorter(item.doc_value()),
-                        parent: Some(did),
-                        search_type: get_index_search_type(&item, parent_basename),
-                    });
-                },
-                None => {}
-            }
-        }
+    let mut crate_items = Vec::with_capacity(cache.search_index.len());
+    let mut crate_paths = Vec::<Json>::new();
+
+    let Cache { ref mut search_index,
+                ref orphan_methods,
+                ref mut paths, .. } = *cache;
+
+    // Attach all orphan methods to the type's definition if the type
+    // has since been learned.
+    for &(did, ref item) in orphan_methods {
+        match paths.get(&did) {
+            Some(&(ref fqp, _)) => {
+                // Needed to determine `self` type.
+                let parent_basename = Some(fqp[fqp.len() - 1].clone());
+                search_index.push(IndexItem {
+                    ty: shortty(item),
+                    name: item.name.clone().unwrap(),
+                    path: fqp[..fqp.len() - 1].join("::"),
+                    desc: Escape(&shorter(item.doc_value())).to_string(),
+                    parent: Some(did),
+                    parent_idx: None,
+                    search_type: get_index_search_type(&item, parent_basename),
+                });
+            },
+            None => {}
+        }
+    }
+
+    // Reduce `NodeId` in paths into smaller sequential numbers,
+    // and prune the paths that do not appear in the index.
+    let mut lastpath = String::new();
+    let mut lastpathid = 0usize;
+
+    for item in search_index {
+        item.parent_idx = item.parent.map(|nodeid| {
+            if nodeid_to_pathid.contains_key(&nodeid) {
+                *nodeid_to_pathid.get(&nodeid).unwrap()
+            } else {
+                let pathid = lastpathid;
+                nodeid_to_pathid.insert(nodeid, pathid);
+                lastpathid += 1;
 
-        // Reduce `NodeId` in paths into smaller sequential numbers,
-        // and prune the paths that do not appear in the index.
-        for item in search_index.iter() {
-            match item.parent {
-                Some(nodeid) => {
-                    if !nodeid_to_pathid.contains_key(&nodeid) {
-                        let pathid = pathid_to_nodeid.len();
-                        nodeid_to_pathid.insert(nodeid, pathid);
-                        pathid_to_nodeid.push(nodeid);
-                    }
-                }
-                None => {}
+                let &(ref fqp, short) = paths.get(&nodeid).unwrap();
+                crate_paths.push(((short as usize), fqp.last().unwrap().clone()).to_json());
+                pathid
             }
-        }
-        assert_eq!(nodeid_to_pathid.len(), pathid_to_nodeid.len());
-    }
-
-    // Collect the index into a string
-    let mut w = io::Cursor::new(Vec::new());
-    write!(&mut w, r#"searchIndex['{}'] = {{"items":["#, krate.name).unwrap();
+        });
 
-    let mut lastpath = "".to_string();
-    for (i, item) in cache.search_index.iter().enumerate() {
-        // Omit the path if it is same to that of the prior item.
-        let path;
+        // Omit the parent path if it is same to that of the prior item.
         if lastpath == item.path {
-            path = "";
+            item.path.clear();
         } else {
-            lastpath = item.path.to_string();
-            path = &item.path;
-        };
-
-        if i > 0 {
-            write!(&mut w, ",").unwrap();
+            lastpath = item.path.clone();
         }
-        write!(&mut w, r#"[{},"{}","{}",{}"#,
-               item.ty as usize, item.name, path,
-               item.desc.to_json().to_string()).unwrap();
-        match item.parent {
-            Some(nodeid) => {
-                let pathid = *nodeid_to_pathid.get(&nodeid).unwrap();
-                write!(&mut w, ",{}", pathid).unwrap();
-            }
-            None => write!(&mut w, ",null").unwrap()
-        }
-        match item.search_type {
-            Some(ref t) => write!(&mut w, ",{}", t).unwrap(),
-            None => write!(&mut w, ",null").unwrap()
-        }
-        write!(&mut w, "]").unwrap();
+        crate_items.push(item.to_json());
     }
 
-    write!(&mut w, r#"],"paths":["#).unwrap();
-
-    for (i, &did) in pathid_to_nodeid.iter().enumerate() {
-        let &(ref fqp, short) = cache.paths.get(&did).unwrap();
-        if i > 0 {
-            write!(&mut w, ",").unwrap();
-        }
-        write!(&mut w, r#"[{},"{}"]"#,
-               short as usize, *fqp.last().unwrap()).unwrap();
-    }
+    let crate_doc = krate.module.as_ref().map(|module| {
+        Escape(&shorter(module.doc_value())).to_string()
+    }).unwrap_or(String::new());
 
-    write!(&mut w, "]}};").unwrap();
+    let mut crate_data = BTreeMap::new();
+    crate_data.insert("doc".to_owned(), Json::String(crate_doc));
+    crate_data.insert("items".to_owned(), Json::Array(crate_items));
+    crate_data.insert("paths".to_owned(), Json::Array(crate_paths));
 
-    String::from_utf8(w.into_inner()).unwrap()
+    // Collect the index into a string
+    format!("searchIndex[{}] = {};",
+            as_json(&krate.name),
+            Json::Object(crate_data))
 }
 
 fn write_shared(cx: &Context,
@@ -693,7 +680,7 @@ fn collect(path: &Path, krate: &str,
                 if !line.starts_with(key) {
                     continue
                 }
-                if line.starts_with(&format!("{}['{}']", key, krate)) {
+                if line.starts_with(&format!(r#"{}["{}"]"#, key, krate)) {
                     continue
                 }
                 ret.push(line.to_string());
@@ -1065,8 +1052,9 @@ fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
                             ty: shortty(&item),
                             name: s.to_string(),
                             path: path.join("::").to_string(),
-                            desc: shorter(item.doc_value()),
+                            desc: Escape(&shorter(item.doc_value())).to_string(),
                             parent: parent,
+                            parent_idx: None,
                             search_type: get_index_search_type(&item, parent_basename),
                         });
                     }
@@ -1387,7 +1375,7 @@ fn render(w: File, cx: &Context, it: &clean::Item,
                         let js_dst = this.dst.join("sidebar-items.js");
                         let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst));
                         try_err!(write!(&mut js_out, "initSidebarItems({});",
-                                    json::as_json(&items)), &js_dst);
+                                    as_json(&items)), &js_dst);
                     }
 
                     for item in m.items {
@@ -1501,11 +1489,17 @@ fn href(&self, cx: &Context) -> Option<String> {
                           true, |component| {
                 path.push(component.to_string());
             });
-            Some(format!("{root}src/{krate}/{path}.html#{href}",
-                         root = self.cx.root_path,
-                         krate = self.cx.layout.krate,
-                         path = path.join("/"),
-                         href = href))
+            // If the span points into an external macro the
+            // source-file will be bogus, i.e `<foo macros>`
+            if Path::new(&self.item.source.filename).is_file() {
+                Some(format!("{root}src/{krate}/{path}.html#{href}",
+                             root = self.cx.root_path,
+                             krate = self.cx.layout.krate,
+                             path = path.join("/"),
+                             href = href))
+            } else {
+                None
+            }
 
         // If this item is not part of the local crate, then things get a little
         // trickier. We don't actually know the span of the external item, but
@@ -1653,8 +1647,8 @@ fn shorter<'a>(s: Option<&'a str>) -> String {
 
 #[inline]
 fn plain_summary_line(s: Option<&str>) -> String {
-    let line = shorter(s).replace("\n", " ");
-    markdown::plain_summary_line(&line[..])
+    let md = markdown::plain_summary_line(s.unwrap_or(""));
+    shorter(Some(&md)).replace("\n", " ")
 }
 
 fn document(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Result {
@@ -1781,6 +1775,7 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: usize, idx2: usize) -> Ordering
                 } else {
                     String::new()
                 };
+                let doc_value = myitem.doc_value().unwrap_or("");
                 try!(write!(w, "
                     <tr class='{stab} module-item'>
                         <td><a class='{class}' href='{href}'
@@ -1792,7 +1787,7 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: usize, idx2: usize) -> Ordering
                 ",
                 name = *myitem.name.as_ref().unwrap(),
                 stab_docs = stab_docs,
-                docs = Markdown(&shorter(myitem.doc_value())),
+                docs = shorter(Some(&Markdown(doc_value).to_string())),
                 class = shortty(myitem),
                 stab = myitem.stability_class(),
                 href = item_path(myitem),
@@ -1913,6 +1908,7 @@ fn item_function(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
            generics = f.generics,
            where_clause = WhereClause(&f.generics),
            decl = f.decl));
+    try!(render_stability_since_raw(w, it.stable_since(), None));
     document(w, cx, it)
 }
 
@@ -1992,7 +1988,7 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
     // Trait documentation
     try!(document(w, cx, it));
 
-    fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item)
+    fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item, t: &clean::Item)
                   -> fmt::Result {
         let name = m.name.as_ref().unwrap();
         let id = derive_id(format!("{}.{}", shortty(m), name));
@@ -2000,7 +1996,9 @@ fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item)
                        id = id,
                        stab = m.stability_class()));
         try!(render_assoc_item(w, m, AssocItemLink::Anchor));
-        try!(write!(w, "</code></h3>"));
+        try!(write!(w, "</code>"));
+        try!(render_stability_since(w, m, t));
+        try!(write!(w, "</h3>"));
         try!(document(w, cx, m));
         Ok(())
     }
@@ -2011,7 +2009,7 @@ fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item)
             <div class='methods'>
         "));
         for t in &types {
-            try!(trait_item(w, cx, *t));
+            try!(trait_item(w, cx, *t, it));
         }
         try!(write!(w, "</div>"));
     }
@@ -2022,7 +2020,7 @@ fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item)
             <div class='methods'>
         "));
         for t in &consts {
-            try!(trait_item(w, cx, *t));
+            try!(trait_item(w, cx, *t, it));
         }
         try!(write!(w, "</div>"));
     }
@@ -2034,7 +2032,7 @@ fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item)
             <div class='methods'>
         "));
         for m in &required {
-            try!(trait_item(w, cx, *m));
+            try!(trait_item(w, cx, *m, it));
         }
         try!(write!(w, "</div>"));
     }
@@ -2044,13 +2042,13 @@ fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item)
             <div class='methods'>
         "));
         for m in &provided {
-            try!(trait_item(w, cx, *m));
+            try!(trait_item(w, cx, *m, it));
         }
         try!(write!(w, "</div>"));
     }
 
     // If there are methods directly on this trait object, render them here.
-    try!(render_assoc_items(w, cx, it.def_id, AssocItemRender::All));
+    try!(render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All));
 
     let cache = cache();
     try!(write!(w, "
@@ -2106,6 +2104,29 @@ fn assoc_type(w: &mut fmt::Formatter, it: &clean::Item,
     Ok(())
 }
 
+fn render_stability_since_raw<'a>(w: &mut fmt::Formatter,
+                                  ver: Option<&'a str>,
+                                  containing_ver: Option<&'a str>) -> fmt::Result {
+    if containing_ver != ver {
+        match ver {
+            Some(v) =>
+                if v.len() > 0 {
+                        try!(write!(w, "<span class=\"since\">{}</span>",
+                                    v))
+                },
+            None => {}
+        }
+    }
+
+    Ok(())
+}
+
+fn render_stability_since(w: &mut fmt::Formatter,
+                          item: &clean::Item,
+                          containing_item: &clean::Item) -> fmt::Result {
+    render_stability_since_raw(w, item.stable_since(), containing_item.stable_since())
+}
+
 fn render_assoc_item(w: &mut fmt::Formatter, meth: &clean::Item,
                      link: AssocItemLink) -> fmt::Result {
     fn method(w: &mut fmt::Formatter,
@@ -2178,6 +2199,7 @@ fn item_struct(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
                        "",
                        true));
     try!(write!(w, "</pre>"));
+    try!(render_stability_since_raw(w, it.stable_since(), None));
 
     try!(document(w, cx, it));
     let mut fields = s.fields.iter().filter(|f| {
@@ -2202,7 +2224,7 @@ fn item_struct(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
             try!(write!(w, "</table>"));
         }
     }
-    render_assoc_items(w, cx, it.def_id, AssocItemRender::All)
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
 }
 
 fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
@@ -2257,10 +2279,11 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
         try!(write!(w, "}}"));
     }
     try!(write!(w, "</pre>"));
+    try!(render_stability_since_raw(w, it.stable_since(), None));
 
     try!(document(w, cx, it));
     if !e.variants.is_empty() {
-        try!(write!(w, "<h2 class='variants'>Variants</h2>\n<table>"));
+        try!(write!(w, "<h2 class='variants'>Variants</h2>\n<table class='variants_table'>"));
         for variant in &e.variants {
             try!(write!(w, "<tr><td id='variant.{name}'><code>{name}</code></td><td>",
                           name = variant.name.as_ref().unwrap()));
@@ -2296,12 +2319,14 @@ fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
                 }
                 _ => ()
             }
+            try!(write!(w, "</td><td>"));
+            try!(render_stability_since(w, variant, it));
             try!(write!(w, "</td></tr>"));
         }
         try!(write!(w, "</table>"));
 
     }
-    try!(render_assoc_items(w, cx, it.def_id, AssocItemRender::All));
+    try!(render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All));
     Ok(())
 }
 
@@ -2397,6 +2422,7 @@ enum AssocItemRender<'a> {
 
 fn render_assoc_items(w: &mut fmt::Formatter,
                       cx: &Context,
+                      containing_item: &clean::Item,
                       it: DefId,
                       what: AssocItemRender) -> fmt::Result {
     let c = cache();
@@ -2420,7 +2446,8 @@ fn render_assoc_items(w: &mut fmt::Formatter,
             }
         };
         for i in &non_trait {
-            try!(render_impl(w, cx, i, AssocItemLink::Anchor, render_header));
+            try!(render_impl(w, cx, i, AssocItemLink::Anchor, render_header,
+                             containing_item.stable_since()));
         }
     }
     if let AssocItemRender::DerefFor { .. } = what {
@@ -2436,7 +2463,7 @@ fn render_assoc_items(w: &mut fmt::Formatter,
             }
         });
         if let Some(impl_) = deref_impl {
-            try!(render_deref_methods(w, cx, impl_));
+            try!(render_deref_methods(w, cx, impl_, containing_item));
         }
         try!(write!(w, "<h2 id='implementations'>Trait \
                           Implementations</h2>"));
@@ -2445,7 +2472,8 @@ fn render_assoc_items(w: &mut fmt::Formatter,
         });
         for i in &manual {
             let did = i.trait_did().unwrap();
-            try!(render_impl(w, cx, i, AssocItemLink::GotoSource(did), true));
+            try!(render_impl(w, cx, i, AssocItemLink::GotoSource(did), true,
+                             containing_item.stable_since()));
         }
         if !derived.is_empty() {
             try!(write!(w, "<h3 id='derived_implementations'>\
@@ -2453,14 +2481,16 @@ fn render_assoc_items(w: &mut fmt::Formatter,
             </h3>"));
             for i in &derived {
                 let did = i.trait_did().unwrap();
-                try!(render_impl(w, cx, i, AssocItemLink::GotoSource(did), true));
+                try!(render_impl(w, cx, i, AssocItemLink::GotoSource(did), true,
+                                 containing_item.stable_since()));
             }
         }
     }
     Ok(())
 }
 
-fn render_deref_methods(w: &mut fmt::Formatter, cx: &Context, impl_: &Impl) -> fmt::Result {
+fn render_deref_methods(w: &mut fmt::Formatter, cx: &Context, impl_: &Impl,
+                        container_item: &clean::Item) -> fmt::Result {
     let deref_type = impl_.impl_.trait_.as_ref().unwrap();
     let target = impl_.impl_.items.iter().filter_map(|item| {
         match item.inner {
@@ -2470,12 +2500,12 @@ fn render_deref_methods(w: &mut fmt::Formatter, cx: &Context, impl_: &Impl) -> f
     }).next().expect("Expected associated type binding");
     let what = AssocItemRender::DerefFor { trait_: deref_type, type_: target };
     match *target {
-        clean::ResolvedPath { did, .. } => render_assoc_items(w, cx, did, what),
+        clean::ResolvedPath { did, .. } => render_assoc_items(w, cx, container_item, did, what),
         _ => {
             if let Some(prim) = target.primitive_type() {
                 if let Some(c) = cache().primitive_locations.get(&prim) {
                     let did = DefId { krate: *c, index: prim.to_def_index() };
-                    try!(render_assoc_items(w, cx, did, what));
+                    try!(render_assoc_items(w, cx, container_item, did, what));
                 }
             }
             Ok(())
@@ -2487,47 +2517,53 @@ fn render_deref_methods(w: &mut fmt::Formatter, cx: &Context, impl_: &Impl) -> f
 // otherwise. If render_header is false, we will avoid rendering static
 // methods, since they are not accessible for the type implementing `Deref`
 fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLink,
-               render_header: bool) -> fmt::Result {
+               render_header: bool, outer_version: Option<&str>) -> fmt::Result {
     if render_header {
-        try!(write!(w, "<h3 class='impl'><code>{}</code></h3>", i.impl_));
+        try!(write!(w, "<h3 class='impl'><code>{}</code>", i.impl_));
+        let since = i.stability.as_ref().map(|s| &s.since[..]);
+        try!(render_stability_since_raw(w, since, outer_version));
+        try!(write!(w, "</h3>"));
         if let Some(ref dox) = i.dox {
             try!(write!(w, "<div class='docblock'>{}</div>", Markdown(dox)));
         }
     }
 
     fn doctraititem(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item,
-                    link: AssocItemLink, render_static: bool) -> fmt::Result {
+                    link: AssocItemLink, render_static: bool,
+                    outer_version: Option<&str>) -> fmt::Result {
         let name = item.name.as_ref().unwrap();
         match item.inner {
             clean::MethodItem(..) | clean::TyMethodItem(..) => {
                 // Only render when the method is not static or we allow static methods
                 if !is_static_method(item) || render_static {
                     let id = derive_id(format!("method.{}", name));
-                    try!(write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item)));
-                try!(render_assoc_item(w, item, link));
+                    try!(write!(w, "<h4 id='{}' class='{}'>", id, shortty(item)));
+                    try!(render_stability_since_raw(w, item.stable_since(), outer_version));
+                    try!(write!(w, "<code>"));
+                    try!(render_assoc_item(w, item, link));
                     try!(write!(w, "</code></h4>\n"));
                 }
             }
             clean::TypedefItem(ref tydef, _) => {
-                let id = derive_id(format!("assoc_type.{}", name));
+                let id = derive_id(format!("associatedtype.{}", name));
                 try!(write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item)));
                 try!(write!(w, "type {} = {}", name, tydef.type_));
                 try!(write!(w, "</code></h4>\n"));
             }
             clean::AssociatedConstItem(ref ty, ref default) => {
-                let id = derive_id(format!("assoc_const.{}", name));
+                let id = derive_id(format!("associatedconstant.{}", name));
                 try!(write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item)));
                 try!(assoc_const(w, item, ty, default.as_ref()));
                 try!(write!(w, "</code></h4>\n"));
             }
             clean::ConstantItem(ref c) => {
-                let id = derive_id(format!("assoc_const.{}", name));
+                let id = derive_id(format!("associatedconstant.{}", name));
                 try!(write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item)));
                 try!(assoc_const(w, item, &c.type_, Some(&c.expr)));
                 try!(write!(w, "</code></h4>\n"));
             }
             clean::AssociatedTypeItem(ref bounds, ref default) => {
-                let id = derive_id(format!("assoc_type.{}", name));
+                let id = derive_id(format!("associatedtype.{}", name));
                 try!(write!(w, "<h4 id='{}' class='{}'><code>", id, shortty(item)));
                 try!(assoc_type(w, item, bounds, default));
                 try!(write!(w, "</code></h4>\n"));
@@ -2556,7 +2592,7 @@ fn is_static_method(item: &clean::Item) -> bool {
 
     try!(write!(w, "<div class='impl-items'>"));
     for trait_item in &i.impl_.items {
-        try!(doctraititem(w, cx, trait_item, link, render_header));
+        try!(doctraititem(w, cx, trait_item, link, render_header, outer_version));
     }
 
     fn render_default_items(w: &mut fmt::Formatter,
@@ -2564,7 +2600,8 @@ fn render_default_items(w: &mut fmt::Formatter,
                             did: DefId,
                             t: &clean::Trait,
                               i: &clean::Impl,
-                              render_static: bool) -> fmt::Result {
+                              render_static: bool,
+                              outer_version: Option<&str>) -> fmt::Result {
         for trait_item in &t.items {
             let n = trait_item.name.clone();
             match i.items.iter().find(|m| { m.name == n }) {
@@ -2572,7 +2609,8 @@ fn render_default_items(w: &mut fmt::Formatter,
                 None => {}
             }
 
-            try!(doctraititem(w, cx, trait_item, AssocItemLink::GotoSource(did), render_static));
+            try!(doctraititem(w, cx, trait_item, AssocItemLink::GotoSource(did), render_static,
+                              outer_version));
         }
         Ok(())
     }
@@ -2583,7 +2621,7 @@ fn render_default_items(w: &mut fmt::Formatter,
     // for them work.
     if let Some(clean::ResolvedPath { did, .. }) = i.impl_.trait_ {
         if let Some(t) = cache().traits.get(&did) {
-            try!(render_default_items(w, cx, did, t, &i.impl_, render_header));
+            try!(render_default_items(w, cx, did, t, &i.impl_, render_header, outer_version));
 
         }
     }
@@ -2675,6 +2713,7 @@ fn item_macro(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
     try!(w.write_str(&highlight::highlight(&t.source,
                                           Some("macro"),
                                           None)));
+    try!(render_stability_since_raw(w, it.stable_since(), None));
     document(w, cx, it)
 }
 
@@ -2682,7 +2721,7 @@ fn item_primitive(w: &mut fmt::Formatter, cx: &Context,
                   it: &clean::Item,
                   _p: &clean::PrimitiveType) -> fmt::Result {
     try!(document(w, cx, it));
-    render_assoc_items(w, cx, it.def_id, AssocItemRender::All)
+    render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)
 }
 
 fn get_basic_keywords() -> &'static str {