extern crate proc_macro;
use proc_macro::{
TokenStream,
};
use proc_macro2::{
Span,
};
use std::{
collections::HashMap,
fs::File,
io::prelude::*,
path::Path,
};
use rustc_serialize::json;
use quote::{
quote,
};
use syn::{
parse_macro_input,
Block,
Expr,
spanned::Spanned,
};
const MYNEWT_DECL_JSON: &str = r#"{
"sensor::set_poll_rate_ms" : [ ["devname", "&Strn"], ["poll_rate", "u32"] ],
"sensor::mgr_find_next_bydevname" : [ ["devname", "&Strn"], ["prev_cursor", "*mut sensor"] ],
"sensor::register_listener" : [ ["sensor", "*mut sensor"], ["listener", "sensor_listener"] ],
"sensor::new_sensor_listener" : [ ["sensor_key", "&'static Strn"], ["sensor_type", "sensor_type_t"], ["listener_func", "SensorValueFunc"] ],
"on_my_label_show" : [ ["state", "&State"], ["env", "&Env"] ],
"on_my_button_press" : [ ["ctx", "&mut EventCtx<State>"], ["state", "&mut State"], ["env", "&Env"] ]
}"#;
const INFER_FILE: &str = "infer.json";
pub fn infer_type_internal(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as syn::Item);
match input {
syn::Item::Fn(item_fn) => infer_function_types(item_fn),
syn::Item::Struct(item_struct) => infer_struct_types(item_struct),
_ => {
assert!(false, "infer_type may be used for functions and structs only");
TokenStream::new()
},
}
}
fn infer_function_types(input: syn::ItemFn) -> TokenStream {
let sig = &input.sig;
let fname = sig.ident.to_string();
unsafe { CURRENT_FUNC = Some(Box::new(fname.clone())); }
let mut known_paras = get_decl(&fname).clone();
let mut all_para: ParaMap = HashMap::new();
for input in &sig.inputs {
match input {
syn::FnArg::Typed(arg_captured) => {
let pat = &arg_captured.pat;
let para = quote!{ #pat }.to_string();
let mut para_type = "_".to_string();
if known_paras.len() > 0 {
para_type = known_paras[0][1].to_string();
known_paras = known_paras[1..].to_vec();
}
all_para.insert(Box::new(para), Box::new(para_type));
s(pat.span());
}
_ => { assert!(false, "Unknown input"); }
}
}
let state = get_decl("State");
for field in state {
let field_name = field[0].to_string();
let field_type = field[1].to_string();
if field_type != "_" { continue; } all_para.insert(Box::new("state.".to_string() + &field_name), Box::new("_".to_string()));
}
let block = input.block;
infer_from_block(&mut all_para, &block);
let mut new_inputs = sig.inputs.clone();
let mut all_para_types: ParaTypeList = Vec::new();
for input in &mut new_inputs {
match input {
syn::FnArg::Typed(arg_captured) => {
let pat = &arg_captured.pat;
let para = quote!{ #pat }.to_string();
let type_str = match all_para.get(¶) {
Some(type_str) => type_str,
None => "_"
};
if type_str != "_" {
let tokens = type_str.parse().unwrap();
arg_captured.ty = Box::new(parse_macro_input!(tokens as syn::Type));
}
let para_type: ParaType = vec![Box::new(para), Box::new(type_str.to_string())];
all_para_types.push(para_type);
}
_ => { assert!(false, "Unknown input"); }
}
}
let mut state: ParaTypeList = Vec::new();
for (field_name, field_type) in &all_para {
if !field_name.starts_with("state.") { continue; }
if field_type.to_string() == "_" { continue; }
let state_item: ParaType = vec![
Box::new(field_name.get(6..).unwrap().to_string()), Box::new(field_type.to_string())
];
state.push(state_item);
}
let mut new_func_map = load_decls();
new_func_map.insert(Box::new(fname.clone()), all_para_types);
if state.len() > 0 {
new_func_map.insert(Box::new("State".to_string()), state);
}
save_decls(&new_func_map);
let mut new_sig = syn::Signature {
inputs: new_inputs,
..sig.clone()
};
let return_type = get_return_type(&fname); if return_type != "_" {
let arrow_return_type =
if return_type == "" { "".to_string() } else { "-> ".to_string() + &return_type }; let tokens = arrow_return_type.parse().unwrap();
new_sig.output = parse_macro_input!(tokens as syn::ReturnType);
}
let output = syn::ItemFn {
sig: new_sig,
block: block,
..input
};
let expanded = quote! {
#output
};
expanded.into()
}
fn infer_struct_types(input: syn::ItemStruct) -> TokenStream {
let struct_name = input.ident.to_string();
unsafe { CURRENT_FUNC = Some(Box::new(struct_name.clone())); }
if let syn::Fields::Named(fields) = &input.fields {
let mut new_fields = fields.named.clone();
for field in &mut new_fields {
if let Some(ident) = &field.ident { let type_str = get_inferred_type(&struct_name, &ident.to_string());
if type_str != "_" {
let tokens = type_str.parse().unwrap();
field.ty = parse_macro_input!(tokens as syn::Type);
}
}
}
let new_fields_named = syn::FieldsNamed {
named: new_fields,
..fields.clone()
};
let output = syn::ItemStruct {
fields: syn::Fields::Named(new_fields_named),
..input
};
let expanded = quote! {
#output
};
return expanded.into()
}
TokenStream::new() }
fn get_inferred_type(function_name: &str, para_name: &str) -> String {
let state = get_decl(function_name);
for field in state {
let field_name = field[0].to_string(); let field_type = field[1].to_string(); if field_name == para_name { return field_type; }
}
"_".to_string() }
fn infer_from_call(all_para: &mut ParaMap, call: &syn::ExprCall) {
s(call.span());
let func = &call.func;
let fname = quote!{ #func }.to_string().replace(" ", "");
let decl_list = get_decl(&fname);
if decl_list.len() == 0 { return }; m(&fname, call.span());
let args = &call.args;
s(args.span());
for pos in 0 .. args.len() {
let arg = &args[pos];
let arg_str = quote!{ #arg }.to_string().replace(" ", "");
let decl_type = &decl_list[pos][1];
all_para.insert(Box::new(arg_str.clone()), Box::new(decl_type.to_string()));
println!("{}: {} has inferred type {}", get_current_function(), arg_str, decl_type);
s(arg.span());
#[cfg(procmacro2_semver_exempt)] {
let decl_name = &decl_list[pos][0];
println!("#i {} | {} | {} | {} | {}", get_current_function(), arg_str, fname, decl_name, decl_type);
}
}
s(args.span());
s(call.span());
}
fn infer_from_macro(all_para: &mut ParaMap, macro_expr: &syn::ExprMacro) {
s(macro_expr.span());
let mac = ¯o_expr.mac;
let path = &mac.path;
let path_str = quote!{ #path }.to_string();
let tts = &mac.tokens;
let tts_str = quote!{ #tts }.to_string();
if path_str != "coap" { return }
m(&path_str, macro_expr.span());
let tts_split: Vec<&str> = tts_str.splitn(2, "{").collect();
if tts_split.len() < 2 { return }
let tts_str = tts_split[1];
let tts_split: Vec<&str> = tts_str.splitn(2, "}").collect();
if tts_split.len() < 2 { return }
let tts_str = tts_split[0];
let tts_split: Vec<&str> = tts_str.split(",").collect();
for field in tts_split {
let field = field.trim();
if field == "" { continue } if field.contains(":") { continue } let decl_type = "&SensorValue";
println!("{}: {} has inferred type {}", get_current_function(), field, decl_type);
#[cfg(procmacro2_semver_exempt)] println!("#i {} | {} | {} | {} | {}", get_current_function(), field, path_str, "singleton", decl_type);
all_para.insert(Box::new(field.to_string()), Box::new(decl_type.to_string()));
}
s(macro_expr.span());
}
fn infer_from_block(all_para: &mut ParaMap, block: &Block) {
s(block.span());
for stmt in &block.stmts {
match stmt {
syn::Stmt::Local(local) => {
s(stmt.span());
if let Some((_eq, expr)) = &local.init {
infer_from_expr(all_para, &expr);
}
}
syn::Stmt::Expr(expr) => {
s(stmt.span());
infer_from_expr(all_para, &expr);
}
syn::Stmt::Semi(expr, _semi) => {
s(stmt.span());
infer_from_expr(all_para, &expr);
}
syn::Stmt::Item(_item) => {}
};
}
s(block.span());
}
fn infer_from_assign(all_para: &mut ParaMap, assign: &syn::ExprAssign) {
let syn::ExprAssign{ left, right, .. } = assign;
let var_name = quote!{ #left }.to_string().replace(" ", ""); let value = quote!{ #right }.to_string();
if value.parse::<i32>().is_ok() {
all_para.insert(Box::new(var_name.clone()), Box::new("i32".to_string()));
} else if value.parse::<f32>().is_ok() {
all_para.insert(Box::new(var_name.clone()), Box::new("f32".to_string()));
} else if value.as_str().chars().nth(0) == Some('"') {
all_para.insert(Box::new(var_name.clone()), Box::new("&str".to_string()));
}
}
fn infer_from_expr(all_para: &mut ParaMap, expr: &Expr) {
s(expr.span());
match expr {
Expr::Call(expr) => {
infer_from_call(all_para, &expr);
}
Expr::Binary(expr) => {
infer_from_expr(all_para, &expr.left);
infer_from_expr(all_para, &expr.right);
}
Expr::Unary(expr) => {
infer_from_expr(all_para, &expr.expr);
}
Expr::Let(expr) => {
infer_from_expr(all_para, &expr.expr);
}
Expr::Assign(expr) => {
infer_from_assign(all_para, &expr);
}
Expr::If(expr) => {
infer_from_expr(all_para, &expr.cond);
infer_from_block(all_para, &expr.then_branch);
if let Some((_else, expr)) = &expr.else_branch {
infer_from_expr(all_para, &expr);
}
}
Expr::While(expr) => {
infer_from_expr(all_para, &expr.cond);
infer_from_block(all_para, &expr.body);
}
Expr::ForLoop(expr) => {
infer_from_expr(all_para, &expr.expr);
infer_from_block(all_para, &expr.body);
}
Expr::Loop(expr) => {
infer_from_block(all_para, &expr.body);
}
Expr::Paren(expr) => {
infer_from_expr(all_para, &expr.expr);
}
Expr::Group(expr) => {
infer_from_expr(all_para, &expr.expr);
}
Expr::Try(expr) => {
infer_from_expr(all_para, &expr.expr);
}
Expr::Macro(expr) => {
infer_from_macro(all_para, &expr);
}
_ => {}
};
s(expr.span());
}
lazy_static::lazy_static! {
static ref MYNEWT_DECL: FuncTypeMap = json::decode(&MYNEWT_DECL_JSON.to_string()).unwrap();
static ref SOURCE_DECL: FuncTypeMap = load_decls();
static ref EMPTY_PARA_TYPE_LIST: ParaTypeList = Vec::new();
}
fn get_decl(fname: &str) -> &ParaTypeList {
if fname.starts_with("on_") {
if fname.ends_with("_show") {
return MYNEWT_DECL.get(&"on_my_label_show".to_string()).unwrap();
}
if fname.ends_with("_press") {
return MYNEWT_DECL.get(&"on_my_button_press".to_string()).unwrap();
}
}
if let Some(para_type_list) = MYNEWT_DECL.get(&fname.to_string()) { return ¶_type_list }
if let Some(para_type_list) = SOURCE_DECL.get(&fname.to_string()) { return ¶_type_list }
&EMPTY_PARA_TYPE_LIST
}
fn get_return_type(fname: &str) -> String {
if fname.starts_with("on_") {
if fname.ends_with("_show") {
return "ArgValue".to_string();
}
if fname.ends_with("_press") {
return "".to_string();
}
}
match fname {
"ui_builder" => "impl Widget<State>".to_string(),
_ => "_".to_string()
}
}
fn load_decls() -> FuncTypeMap {
let path = Path::new(INFER_FILE);
let _display = path.display();
let mut file = match File::open(&path) {
Err(_why) => return HashMap::new(),
Ok(file) => file
};
let mut s = String::new();
match file.read_to_string(&mut s) {
Err(_why) => return HashMap::new(),
Ok(_) => {}
};
let all_funcs: FuncTypeMap = json::decode(&s).unwrap();
return all_funcs;
}
fn save_decls(all_funcs: &FuncTypeMap) {
let encoded = json::encode(&all_funcs).unwrap();
let path = Path::new(INFER_FILE);
let display = path.display();
let mut file = match File::create(&path) {
Err(why) => panic!("couldn't create {}: {}", display, why.to_string()),
Ok(file) => file,
};
match file.write_all(encoded.as_bytes()) {
Err(why) => panic!("couldn't write to {}: {}", display, why.to_string()),
Ok(_) => println!("{}: updated {}", get_current_function(), display),
};
}
#[cfg(procmacro2_semver_exempt)] fn s(span: Span) {
let file = span.source_file();
let start = span.start();
let end = span.end();
println!("#s {} | {} | {} | {} | {}", file.path().to_str().unwrap(), start.line, start.column, end.line, end.column);
}
#[cfg(not(procmacro2_semver_exempt))]
fn s(_span: Span) {}
#[cfg(procmacro2_semver_exempt)] fn m(fname: &str, span: Span) {
let file = span.source_file();
let start = span.start();
let end = span.end();
println!("#m {} | {} | {} | {} | {} | {}", fname, file.path().to_str().unwrap(), start.line, start.column, end.line, end.column);
}
#[cfg(not(procmacro2_semver_exempt))]
fn m(_fname: &str, _span: Span) {}
fn get_current_function() -> Box<String> {
let s = unsafe { format!("{:?}", CURRENT_FUNC) };
let s2: Vec<&str> = s.splitn(3, "\"").collect();
return Box::new(s2[1].to_string());
}
static mut CURRENT_FUNC: Option<Box<String>> = None;
type ParaMap = HashMap<Box<String>, Box<String>>;
type ParaType = Vec<Box<String>>;
type ParaTypeList = Vec<ParaType>;
type FuncTypeMap = HashMap<Box<String>, ParaTypeList>;