Extend snake_to_camel plugin to replace {} in the doc string with the original snake-cased ident. (#50)

* Extend snake_to_camel plugin to replace {} in the doc string with the origin snake-cased ident.

Also, track tokio-rs master.

This is really ad-hoc, undiscoverable, and unintuitive, but there's no way to programmatically create doc strings
in regular code, and I want to produce better doc strings for the associated types.

Given `fn foo_bar`:

Before: `/// The type of future returned by the function of the same name.`
After: ``/// The type of future returned by `{}`.``
    => `/// The type of future returned by foo_bar.`

* Fix some docs

* Use a helper fn on pipeline::Frame instead of handrolled match.

* Don't hide docs for ClientFuture.

It's exposed in the Connect impl of FutureService -- the tradeoff for not generating *another* item -- and hiding it breaks doc links.

* Formatting

* Rename snake_to_camel plugin => tarpc-plugins

* Update README

* Mangle a lot of names in macro expansion.

To lower the chance of any issues, prefix idents in service expansion with __tarpc_service.
In future_enum, prefix with __future_enum. The pattern is basically __macro_name_ident.

Any imported enum variant will conflict with a let binding or a function arg, so we basically
can't use any generic idents at all. Example:

    enum Req { request(..) }
    use self::Req::request;

    fn make_request(request: Request) { ... }

                    ^^^^^^^ conflict here

Additionally, suffix generated associated types with Fut to avoid conflicts with camelcased rpcs.
Why someone would do that, I don't know, but we shouldn't allow that wart.
This commit is contained in:
Tim
2016-09-14 01:19:24 -07:00
committed by GitHub
parent 54017839d1
commit be5f55c5f6
20 changed files with 422 additions and 381 deletions

10
src/plugins/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "tarpc-plugins"
version = "0.1.0"
authors = ["Tim Kuehn <tikue@google.com>"]
[dependencies]
itertools = "0.4"
[lib]
plugin = true

185
src/plugins/src/lib.rs Normal file
View File

@@ -0,0 +1,185 @@
#![feature(plugin_registrar, rustc_private)]
extern crate itertools;
extern crate rustc;
extern crate rustc_plugin;
extern crate syntax;
use itertools::Itertools;
use rustc_plugin::Registry;
use syntax::ast::{self, Ident, TraitRef, Ty, TyKind};
use syntax::ast::LitKind::Str;
use syntax::ast::MetaItemKind::NameValue;
use syntax::codemap::Spanned;
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::parse::{self, token, PResult};
use syntax::ptr::P;
use syntax::parse::parser::{Parser, PathStyle};
use syntax::parse::token::intern_and_get_ident;
use syntax::tokenstream::TokenTree;
use syntax::ext::quote::rt::Span;
use syntax::util::small_vector::SmallVector;
fn snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg(), tts.into());
// The `expand_expr` method is called so that any macro calls in the
// parsed expression are expanded.
let mut item = match parser.parse_trait_item() {
Ok(s) => s,
Err(mut diagnostic) => {
diagnostic.emit();
return DummyResult::any(sp);
}
};
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
diagnostic.emit();
return DummyResult::any(sp);
}
let old_ident = convert(&mut item.ident);
// As far as I know, it's not possible in macro_rules! to reference an $ident in a doc string,
// so this is the hacky workaround.
//
// This code looks intimidating, but it's just iterating through the trait item's attributes
// (NameValues), filtering out non-doc attributes, and replacing any {} in the doc string with
// the original, snake_case ident.
for meta_item in item.attrs.iter_mut().map(|attr| &mut attr.node.value) {
let updated = match meta_item.node {
NameValue(ref name, _) if name == "doc" => {
let mut updated = (**meta_item).clone();
if let NameValue(_, Spanned { node: Str(ref mut doc, _), .. }) = updated.node {
let updated_doc = doc.replace("{}", &old_ident);
*doc = intern_and_get_ident(&updated_doc);
} else {
unreachable!()
};
Some(P(updated))
}
_ => None,
};
if let Some(updated) = updated {
*meta_item = updated;
}
}
MacEager::trait_items(SmallVector::one(item))
}
fn impl_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg(), tts.into());
// The `expand_expr` method is called so that any macro calls in the
// parsed expression are expanded.
let mut item = match parser.parse_impl_item() {
Ok(s) => s,
Err(mut diagnostic) => {
diagnostic.emit();
return DummyResult::any(sp);
}
};
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
diagnostic.emit();
return DummyResult::any(sp);
}
convert(&mut item.ident);
MacEager::impl_items(SmallVector::one(item))
}
fn ty_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg(), tts.into());
// The `expand_expr` method is called so that any macro calls in the
// parsed expression are expanded.
let mut ty = match parser.parse_ty_path() {
Ok(s) => s,
Err(mut diagnostic) => {
diagnostic.emit();
return DummyResult::any(sp);
}
};
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
diagnostic.emit();
return DummyResult::any(sp);
}
// Only capitalize the final segment
if let TyKind::Path(_, ref mut path) = ty {
convert(&mut path.segments.last_mut().unwrap().identifier);
} else {
unreachable!()
}
MacEager::ty(P(Ty {
id: ast::DUMMY_NODE_ID,
node: ty,
span: sp,
}))
}
/// Converts an ident in-place to CamelCase and returns the previous ident.
fn convert(ident: &mut Ident) -> String {
let ident_str = ident.to_string();
let mut camel_ty = String::new();
{
// Find the first non-underscore and add it capitalized.
let mut chars = ident_str.chars();
// Find the first non-underscore char, uppercase it, and append it.
// Guaranteed to succeed because all idents must have at least one non-underscore char.
camel_ty.extend(chars.find(|&c| c != '_').unwrap().to_uppercase());
// When we find an underscore, we remove it and capitalize the next char. To do this,
// we need to ensure the next char is not another underscore.
let mut chars = chars.coalesce(|c1, c2| {
if c1 == '_' && c2 == '_' {
Ok(c1)
} else {
Err((c1, c2))
}
});
while let Some(c) = chars.next() {
if c != '_' {
camel_ty.push(c);
} else {
if let Some(c) = chars.next() {
camel_ty.extend(c.to_uppercase());
}
}
}
}
// The Fut suffix is hardcoded right now; this macro isn't really meant to be general-purpose.
camel_ty.push_str("Fut");
*ident = Ident::with_empty_ctxt(token::intern(&camel_ty));
ident_str
}
trait ParseTraitRef {
fn parse_trait_ref(&mut self) -> PResult<TraitRef>;
}
impl<'a> ParseTraitRef for Parser<'a> {
/// Parse a::B<String,i32>
fn parse_trait_ref(&mut self) -> PResult<TraitRef> {
Ok(TraitRef {
path: try!(self.parse_path(PathStyle::Type)),
ref_id: ast::DUMMY_NODE_ID,
})
}
}
#[plugin_registrar]
#[doc(hidden)]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_macro("snake_to_camel", snake_to_camel);
reg.register_macro("impl_snake_to_camel", impl_snake_to_camel);
reg.register_macro("ty_snake_to_camel", ty_snake_to_camel);
}