// Copyright 2012 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Pulls type information out of the AST and attaches it to the document use astsrv; use doc::ItemUtils; use doc; use extract::to_str; use extract; use fold::Fold; use fold; use core::vec; use std::map::HashMap; use std::par; use syntax::ast; use syntax::print::pprust; use syntax::ast_map; pub fn mk_pass() -> Pass { { name: ~"tystr", f: run } } fn run( srv: astsrv::Srv, +doc: doc::Doc ) -> doc::Doc { let fold = Fold { fold_fn: fold_fn, fold_const: fold_const, fold_enum: fold_enum, fold_trait: fold_trait, fold_impl: fold_impl, fold_type: fold_type, fold_struct: fold_struct, .. fold::default_any_fold(srv) }; (fold.fold_doc)(&fold, doc) } fn fold_fn( fold: &fold::Fold, +doc: doc::FnDoc ) -> doc::FnDoc { let srv = fold.ctxt; { sig: get_fn_sig(srv, doc.id()), .. doc } } fn get_fn_sig(srv: astsrv::Srv, fn_id: doc::AstId) -> Option<~str> { do astsrv::exec(srv) |ctxt| { match ctxt.ast_map.get(fn_id) { ast_map::node_item(@{ ident: ident, node: ast::item_fn(decl, _, tys, _), _ }, _) | ast_map::node_foreign_item(@{ ident: ident, node: ast::foreign_item_fn(decl, _, tys), _ }, _, _) => { Some(pprust::fun_to_str(decl, ident, tys, extract::interner())) } _ => fail ~"get_fn_sig: fn_id not bound to a fn item" } } } #[test] fn should_add_fn_sig() { let doc = test::mk_doc(~"fn a() -> int { }"); assert doc.cratemod().fns()[0].sig == Some(~"fn a() -> int"); } #[test] fn should_add_foreign_fn_sig() { let doc = test::mk_doc(~"extern mod a { fn a() -> int; }"); assert doc.cratemod().nmods()[0].fns[0].sig == Some(~"fn a() -> int"); } fn fold_const( fold: &fold::Fold, +doc: doc::ConstDoc ) -> doc::ConstDoc { let srv = fold.ctxt; { sig: Some(do astsrv::exec(srv) |ctxt| { match ctxt.ast_map.get(doc.id()) { ast_map::node_item(@{ node: ast::item_const(ty, _), _ }, _) => { pprust::ty_to_str(ty, extract::interner()) } _ => fail ~"fold_const: id not bound to a const item" } }), .. doc } } #[test] fn should_add_const_types() { let doc = test::mk_doc(~"const a: bool = true;"); assert doc.cratemod().consts()[0].sig == Some(~"bool"); } fn fold_enum( fold: &fold::Fold, +doc: doc::EnumDoc ) -> doc::EnumDoc { let doc_id = doc.id(); let srv = fold.ctxt; { variants: do par::map(doc.variants) |variant| { let variant = *variant; let sig = do astsrv::exec(srv) |ctxt| { match ctxt.ast_map.get(doc_id) { ast_map::node_item(@{ node: ast::item_enum(enum_definition, _), _ }, _) => { let ast_variant = do vec::find(enum_definition.variants) |v| { to_str(v.node.name) == variant.name }.get(); pprust::variant_to_str(ast_variant, extract::interner()) } _ => fail ~"enum variant not bound to an enum item" } }; { sig: Some(sig), .. variant } }, .. doc } } #[test] fn should_add_variant_sigs() { let doc = test::mk_doc(~"enum a { b(int) }"); assert doc.cratemod().enums()[0].variants[0].sig == Some(~"b(int)"); } fn fold_trait( fold: &fold::Fold, +doc: doc::TraitDoc ) -> doc::TraitDoc { { methods: merge_methods(fold.ctxt, doc.id(), doc.methods), .. doc } } fn merge_methods( srv: astsrv::Srv, item_id: doc::AstId, docs: ~[doc::MethodDoc] ) -> ~[doc::MethodDoc] { do par::map(docs) |doc| { { sig: get_method_sig(srv, item_id, doc.name), .. *doc } } } fn get_method_sig( srv: astsrv::Srv, item_id: doc::AstId, method_name: ~str ) -> Option<~str> { do astsrv::exec(srv) |ctxt| { match ctxt.ast_map.get(item_id) { ast_map::node_item(@{ node: ast::item_trait(_, _, methods), _ }, _) => { match vec::find(methods, |method| { match *method { ast::required(ty_m) => to_str(ty_m.ident) == method_name, ast::provided(m) => to_str(m.ident) == method_name, } }) { Some(method) => { match method { ast::required(ty_m) => { Some(pprust::fun_to_str( ty_m.decl, ty_m.ident, ty_m.tps, extract::interner() )) } ast::provided(m) => { Some(pprust::fun_to_str( m.decl, m.ident, m.tps, extract::interner() )) } } } _ => fail ~"method not found" } } ast_map::node_item(@{ node: ast::item_impl(_, _, _, methods), _ }, _) => { match vec::find(methods, |method| { to_str(method.ident) == method_name }) { Some(method) => { Some(pprust::fun_to_str( method.decl, method.ident, method.tps, extract::interner() )) } None => fail ~"method not found" } } _ => fail ~"get_method_sig: item ID not bound to trait or impl" } } } #[test] fn should_add_trait_method_sigs() { let doc = test::mk_doc(~"trait i { fn a() -> int; }"); assert doc.cratemod().traits()[0].methods[0].sig == Some(~"fn a() -> int"); } fn fold_impl( fold: &fold::Fold, +doc: doc::ImplDoc ) -> doc::ImplDoc { let srv = fold.ctxt; let (trait_types, self_ty) = do astsrv::exec(srv) |ctxt| { match ctxt.ast_map.get(doc.id()) { ast_map::node_item(@{ node: ast::item_impl(_, opt_trait_type, self_ty, _), _ }, _) => { let trait_types = opt_trait_type.map_default(~[], |p| { ~[pprust::path_to_str(p.path, extract::interner())] }); (trait_types, Some(pprust::ty_to_str(self_ty, extract::interner()))) } _ => fail ~"expected impl" } }; { trait_types: trait_types, self_ty: self_ty, methods: merge_methods(fold.ctxt, doc.id(), doc.methods), .. doc } } #[test] fn should_add_impl_trait_types() { let doc = test::mk_doc(~"impl int: j { fn a() { } }"); assert doc.cratemod().impls()[0].trait_types[0] == ~"j"; } #[test] fn should_not_add_impl_trait_types_if_none() { let doc = test::mk_doc(~"impl int { fn a() { } }"); assert vec::len(doc.cratemod().impls()[0].trait_types) == 0; } #[test] fn should_add_impl_self_ty() { let doc = test::mk_doc(~"impl int { fn a() { } }"); assert doc.cratemod().impls()[0].self_ty == Some(~"int"); } #[test] fn should_add_impl_method_sigs() { let doc = test::mk_doc(~"impl int { fn a() -> int { fail } }"); assert doc.cratemod().impls()[0].methods[0].sig == Some(~"fn a() -> int"); } fn fold_type( fold: &fold::Fold, +doc: doc::TyDoc ) -> doc::TyDoc { let srv = fold.ctxt; { sig: do astsrv::exec(srv) |ctxt| { match ctxt.ast_map.get(doc.id()) { ast_map::node_item(@{ ident: ident, node: ast::item_ty(ty, params), _ }, _) => { Some(fmt!( "type %s%s = %s", to_str(ident), pprust::typarams_to_str(params, extract::interner()), pprust::ty_to_str(ty, extract::interner()) )) } _ => fail ~"expected type" } }, .. doc } } #[test] fn should_add_type_signatures() { let doc = test::mk_doc(~"type t = int;"); assert doc.cratemod().types()[0].sig == Some(~"type t = int"); } fn fold_struct( fold: &fold::Fold, +doc: doc::StructDoc ) -> doc::StructDoc { let srv = fold.ctxt; { sig: do astsrv::exec(srv) |ctxt| { match ctxt.ast_map.get(doc.id()) { ast_map::node_item(item, _) => { let item = strip_struct_extra_stuff(item); Some(pprust::item_to_str(item, extract::interner())) } _ => fail ~"not an item" } }, .. doc } } /// Removes various things from the struct item definition that /// shouldn't be displayed in the struct signature. Probably there /// should be a simple pprust::struct_to_str function that does /// what I actually want fn strip_struct_extra_stuff(item: @ast::item) -> @ast::item { let node = match item.node { ast::item_struct(def, tys) => { let def = @{ dtor: None, // Remove the drop { } block .. *def }; ast::item_struct(def, tys) } _ => fail ~"not a struct" }; @{ attrs: ~[], // Remove the attributes node: node, .. *item } } #[test] fn should_add_struct_defs() { let doc = test::mk_doc(~"struct S { field: () }"); assert doc.cratemod().structs()[0].sig.get().contains("struct S {"); } #[test] fn should_not_serialize_struct_drop_blocks() { // All we care about are the fields let doc = test::mk_doc(~"struct S { field: (), drop { } }"); assert !doc.cratemod().structs()[0].sig.get().contains("drop"); } #[test] fn should_not_serialize_struct_attrs() { // All we care about are the fields let doc = test::mk_doc(~"#[wut] struct S { field: () }"); assert !doc.cratemod().structs()[0].sig.get().contains("wut"); } #[cfg(test)] mod test { #[legacy_exports]; use astsrv; use doc; use extract; fn mk_doc(source: ~str) -> doc::Doc { do astsrv::from_str(source) |srv| { let doc = extract::from_srv(srv, ~""); run(srv, doc) } } }