mirror of
https://github.com/OMGeeky/tarpc.git
synced 2026-01-06 03:22:47 +01:00
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:
10
src/plugins/Cargo.toml
Normal file
10
src/plugins/Cargo.toml
Normal 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
185
src/plugins/src/lib.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user