mirror of https://github.com/rwf2/Rocket.git
Add lints to catch unmanaged state and unmounted routes.
* The `unmanaged_state` lint emits a warning when a `State<T>` request guard is used without an accompanying `manage` call for `T`. * The `unmounted_route` lint emits a warning when a route declared via a Rocket attribute is not mounted via a call to `mount`. There is one known shortcoming of these lints at present: _any_ call to `manage` or `mount` marks state/routes as managed/mounted. This can be an issue when an application uses more than one `Rocket` instance, with different calls to `mount` and `manage` in each. The lints should perform their analyses on a per-instance basis.
This commit is contained in:
parent
5f04beaafc
commit
4eaf9ba9c5
|
@ -1,12 +1,12 @@
|
|||
use ::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX, CATCHER_ATTR};
|
||||
use parser::ErrorParams;
|
||||
use utils::*;
|
||||
use ::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX};
|
||||
|
||||
use syntax::codemap::{Span};
|
||||
use syntax::ast::{MetaItem, Ident, TyKind};
|
||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||
use syntax::tokenstream::TokenTree;
|
||||
use syntax::parse::token;
|
||||
use parser::ErrorParams;
|
||||
|
||||
const ERR_PARAM: &'static str = "_error";
|
||||
const REQ_PARAM: &'static str = "_request";
|
||||
|
@ -46,17 +46,27 @@ impl ErrorGenerateExt for ErrorParams {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
||||
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
||||
let error = ErrorParams::from(ecx, sp, meta_item, annotated);
|
||||
pub fn error_decorator(ecx: &mut ExtCtxt,
|
||||
sp: Span,
|
||||
meta_item: &MetaItem,
|
||||
annotated: Annotatable)
|
||||
-> Vec<Annotatable>
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
|
||||
// Parse the parameters from the error annotation.
|
||||
let error = ErrorParams::from(ecx, sp, meta_item, &annotated);
|
||||
|
||||
// Get all of the information we learned from the attribute + function.
|
||||
let user_fn_name = error.annotated_fn.ident();
|
||||
let catch_fn_name = user_fn_name.prepend(CATCH_FN_PREFIX);
|
||||
let code = error.code.node;
|
||||
let (err_ident, req_ident) = (Ident::from_str(ERR_PARAM), Ident::from_str(REQ_PARAM));
|
||||
let err_ident = Ident::from_str(ERR_PARAM);
|
||||
let req_ident = Ident::from_str(REQ_PARAM);
|
||||
let fn_arguments = error.generate_fn_arguments(ecx, err_ident, req_ident);
|
||||
|
||||
emit_item(push, quote_item!(ecx,
|
||||
// Push the Rocket generated catch function.
|
||||
emit_item(&mut output, quote_item!(ecx,
|
||||
fn $catch_fn_name<'_b>($err_ident: ::rocket::Error,
|
||||
$req_ident: &'_b ::rocket::Request)
|
||||
-> ::rocket::response::Result<'_b> {
|
||||
|
@ -67,8 +77,9 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
|||
}
|
||||
).expect("catch function"));
|
||||
|
||||
// Push the static catch info. This is what the errors! macro refers to.
|
||||
let struct_name = user_fn_name.prepend(CATCH_STRUCT_PREFIX);
|
||||
emit_item(push, quote_item!(ecx,
|
||||
emit_item(&mut output, quote_item!(ecx,
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static $struct_name: ::rocket::StaticCatchInfo =
|
||||
::rocket::StaticCatchInfo {
|
||||
|
@ -76,4 +87,11 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
|||
handler: $catch_fn_name
|
||||
};
|
||||
).expect("catch info struct"));
|
||||
|
||||
// Attach a `rocket_catcher` attribute to the user's function and emit it.
|
||||
let attr_name = Ident::from_str(CATCHER_ATTR);
|
||||
let catcher_attr = quote_attr!(ecx, #[$attr_name($struct_name)]);
|
||||
attach_and_emit(&mut output, catcher_attr, annotated);
|
||||
|
||||
output
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::collections::HashSet;
|
|||
use std::fmt::Display;
|
||||
|
||||
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX};
|
||||
use utils::{emit_item, span, sep_by_tok, option_as_expr, strip_ty_lifetimes};
|
||||
use utils::{SpanExt, IdentExt, ArgExt};
|
||||
use ::{ROUTE_ATTR, ROUTE_INFO_ATTR};
|
||||
use parser::{Param, RouteParams};
|
||||
use utils::*;
|
||||
|
||||
use syntax::codemap::{Span, Spanned};
|
||||
use syntax::tokenstream::TokenTree;
|
||||
|
@ -169,7 +169,7 @@ impl RouteGenerateExt for RouteParams {
|
|||
!a.named(&p.node.name)
|
||||
})
|
||||
} else {
|
||||
ecx.span_err(a.pat.span, "argument names must be identifiers");
|
||||
ecx.span_err(a.pat.span, "route argument names must be identifiers");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
@ -180,6 +180,7 @@ impl RouteGenerateExt for RouteParams {
|
|||
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
|
||||
let ty = strip_ty_lifetimes(arg.ty.clone());
|
||||
fn_param_statements.push(quote_stmt!(ecx,
|
||||
#[allow(non_snake_case)]
|
||||
let $ident: $ty = match
|
||||
::rocket::request::FromRequest::from_request(_req) {
|
||||
::rocket::outcome::Outcome::Success(v) => v,
|
||||
|
@ -220,10 +221,13 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
|||
ecx: &mut ExtCtxt,
|
||||
sp: Span,
|
||||
meta_item: &MetaItem,
|
||||
annotated: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)) {
|
||||
annotated: Annotatable)
|
||||
-> Vec<Annotatable>
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
|
||||
// Parse the route and generate the code to create the form and param vars.
|
||||
let route = RouteParams::from(ecx, sp, known_method, meta_item, annotated);
|
||||
let route = RouteParams::from(ecx, sp, known_method, meta_item, &annotated);
|
||||
debug!("Route params: {:?}", route);
|
||||
|
||||
let param_statements = route.generate_param_statements(ecx);
|
||||
|
@ -234,7 +238,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
|||
// Generate and emit the wrapping function with the Rocket handler signature.
|
||||
let user_fn_name = route.annotated_fn.ident();
|
||||
let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX);
|
||||
emit_item(push, quote_item!(ecx,
|
||||
emit_item(&mut output, quote_item!(ecx,
|
||||
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
|
||||
-> ::rocket::handler::Outcome<'_b> {
|
||||
$param_statements
|
||||
|
@ -249,7 +253,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
|||
// function as its handler. A proper Rocket route will be created from this.
|
||||
let struct_name = user_fn_name.prepend(ROUTE_STRUCT_PREFIX);
|
||||
let (path, method, content_type, rank) = route.explode(ecx);
|
||||
emit_item(push, quote_item!(ecx,
|
||||
let static_route_info_item = quote_item!(ecx,
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static $struct_name: ::rocket::StaticRouteInfo =
|
||||
::rocket::StaticRouteInfo {
|
||||
|
@ -259,24 +263,35 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
|||
format: $content_type,
|
||||
rank: $rank,
|
||||
};
|
||||
).unwrap());
|
||||
).expect("static route info");
|
||||
|
||||
// Attach a `rocket_route_info` attribute to the route info and emit it.
|
||||
let attr_name = Ident::from_str(ROUTE_INFO_ATTR);
|
||||
let info_attr = quote_attr!(ecx, #[$attr_name]);
|
||||
attach_and_emit(&mut output, info_attr, Annotatable::Item(static_route_info_item));
|
||||
|
||||
// Attach a `rocket_route` attribute to the user's function and emit it.
|
||||
let attr_name = Ident::from_str(ROUTE_ATTR);
|
||||
let route_attr = quote_attr!(ecx, #[$attr_name($struct_name)]);
|
||||
attach_and_emit(&mut output, route_attr, annotated);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn route_decorator(ecx: &mut ExtCtxt,
|
||||
sp: Span,
|
||||
meta_item: &MetaItem,
|
||||
annotated: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)) {
|
||||
generic_route_decorator(None, ecx, sp, meta_item, annotated, push);
|
||||
pub fn route_decorator(
|
||||
ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: Annotatable
|
||||
) -> Vec<Annotatable> {
|
||||
generic_route_decorator(None, ecx, sp, meta_item, annotated)
|
||||
}
|
||||
|
||||
macro_rules! method_decorator {
|
||||
($name:ident, $method:ident) => (
|
||||
pub fn $name(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
||||
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
||||
pub fn $name(
|
||||
ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: Annotatable
|
||||
) -> Vec<Annotatable> {
|
||||
let i_sp = meta_item.span.shorten_to(stringify!($method).len());
|
||||
let method = Some(span(Method::$method, i_sp));
|
||||
generic_route_decorator(method, ecx, sp, meta_item, annotated, push);
|
||||
generic_route_decorator(method, ecx, sp, meta_item, annotated)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -97,9 +97,9 @@
|
|||
#![allow(deprecated)]
|
||||
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate rustc;
|
||||
extern crate syntax;
|
||||
extern crate syntax_ext;
|
||||
extern crate rustc;
|
||||
extern crate rustc_plugin;
|
||||
extern crate rocket;
|
||||
|
||||
|
@ -107,6 +107,7 @@ extern crate rocket;
|
|||
mod parser;
|
||||
mod macros;
|
||||
mod decorators;
|
||||
mod lints;
|
||||
|
||||
use std::env;
|
||||
use rustc_plugin::Registry;
|
||||
|
@ -121,10 +122,15 @@ const CATCH_STRUCT_PREFIX: &'static str = "static_rocket_catch_info_for_";
|
|||
const ROUTE_FN_PREFIX: &'static str = "rocket_route_fn_";
|
||||
const CATCH_FN_PREFIX: &'static str = "rocket_catch_fn_";
|
||||
|
||||
const ROUTE_ATTR: &'static str = "rocket_route";
|
||||
const ROUTE_INFO_ATTR: &'static str = "rocket_route_info";
|
||||
|
||||
const CATCHER_ATTR: &'static str = "rocket_catcher";
|
||||
|
||||
macro_rules! register_decorators {
|
||||
($registry:expr, $($name:expr => $func:ident),+) => (
|
||||
$($registry.register_syntax_extension(Symbol::intern($name),
|
||||
SyntaxExtension::MultiDecorator(Box::new(decorators::$func)));
|
||||
SyntaxExtension::MultiModifier(Box::new(decorators::$func)));
|
||||
)+
|
||||
)
|
||||
}
|
||||
|
@ -137,6 +143,12 @@ macro_rules! register_derives {
|
|||
)
|
||||
}
|
||||
|
||||
macro_rules! register_lints {
|
||||
($registry:expr, $($item:ident),+) => ($(
|
||||
$registry.register_late_lint_pass(Box::new(lints::$item::default()));
|
||||
)+)
|
||||
}
|
||||
|
||||
/// Compiler hook for Rust to register plugins.
|
||||
#[plugin_registrar]
|
||||
pub fn plugin_registrar(reg: &mut Registry) {
|
||||
|
@ -164,4 +176,6 @@ pub fn plugin_registrar(reg: &mut Registry) {
|
|||
// TODO: Allow this once Diesel incompatibility is fixed. Fix docs too.
|
||||
// "options" => options_decorator
|
||||
);
|
||||
|
||||
register_lints!(reg, ManagedStateLint);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
extern crate syntax_pos;
|
||||
|
||||
mod utils;
|
||||
|
||||
use self::utils::*;
|
||||
|
||||
use ::{ROUTE_ATTR, ROUTE_INFO_ATTR};
|
||||
|
||||
use std::mem::transmute;
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
use rustc::lint::Level;
|
||||
use rustc::lint::{LateContext, LintContext, LintPass, LateLintPass, LintArray};
|
||||
use rustc::hir::{Item, Expr, Crate};
|
||||
use rustc::hir::def::Def;
|
||||
use rustc::hir::def_id::DefId;
|
||||
use rustc::ty::Ty;
|
||||
use rustc::hir::intravisit::{FnKind};
|
||||
use rustc::hir::{FnDecl, Body};
|
||||
use rustc::hir::Ty_::TyPath;
|
||||
use self::syntax_pos::Span;
|
||||
|
||||
use syntax::symbol::Symbol as Name;
|
||||
use syntax::ast::NodeId;
|
||||
|
||||
const STATE_TYPE: &'static [&'static str] = &["rocket", "request", "state", "State"];
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ManagedStateLint {
|
||||
// All of the types that were requested as managed state.
|
||||
// (fn_name, fn_span, info_struct_def_id, req_type, req_param_span)
|
||||
requested: Vec<(Name, Span, DefId, Ty<'static>, Span)>,
|
||||
// The DefId of all of the route infos for the mounted routes.
|
||||
mounted: HashSet<DefId>,
|
||||
// The names of all of the routes that were declared.
|
||||
info_structs: HashMap<Name, DefId>,
|
||||
// The name, span, and info DefId for all declared route functions.
|
||||
declared: Vec<(Name, Span, DefId)>,
|
||||
// The expressions that were passed into a `.manage` call.
|
||||
managed: Vec<(Ty<'static>, Span)>,
|
||||
// Span for rocket::ignite() or rocket::custom().
|
||||
start_call: Vec<Span>,
|
||||
}
|
||||
|
||||
declare_lint!(UNMOUNTED_ROUTE, Warn, "Warn on routes that are unmounted.");
|
||||
declare_lint!(UNMANAGED_STATE, Warn, "Warn on declared use on unmanaged state.");
|
||||
|
||||
impl<'tcx> LintPass for ManagedStateLint {
|
||||
fn get_lints(&self) -> LintArray {
|
||||
lint_array!(UNMANAGED_STATE, UNMOUNTED_ROUTE)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ManagedStateLint {
|
||||
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
|
||||
if let Some(args) = rocket_method_call("manage", cx, expr) {
|
||||
let expr = &args[0];
|
||||
if let Some(ty) = cx.tables.expr_ty_opt(expr) {
|
||||
let casted = unsafe { transmute(ty) };
|
||||
self.managed.push((casted, expr.span));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(args) = rocket_method_call("mount", cx, expr) {
|
||||
for def_id in extract_mount_fn_def_ids(cx, &args[1]) {
|
||||
self.mounted.insert(def_id);
|
||||
}
|
||||
}
|
||||
|
||||
if is_rocket_start_call(cx, expr) {
|
||||
self.start_call.push(expr.span);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
|
||||
// Return early if this is not a route info structure.
|
||||
if !item.attrs.iter().any(|attr| attr.check_name(ROUTE_INFO_ATTR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(def_id) = cx.tcx.map.opt_local_def_id(item.id) {
|
||||
self.info_structs.insert(item.name, def_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_fn(&mut self,
|
||||
cx: &LateContext<'a, 'tcx>,
|
||||
kind: FnKind<'tcx>,
|
||||
decl: &'tcx FnDecl,
|
||||
_: &'tcx Body,
|
||||
fn_sp: Span,
|
||||
fn_id: NodeId)
|
||||
{
|
||||
// Get the name of the function, if any.
|
||||
let fn_name = match kind {
|
||||
FnKind::ItemFn(name, ..) => name,
|
||||
_ => return
|
||||
};
|
||||
|
||||
// Figure out if this is a route function by trying to find the
|
||||
// `ROUTE_ATTR` attribute and extracing the info struct's name from it.
|
||||
let attr_value = kind.attrs().iter().filter_map(|attr| {
|
||||
if !attr.check_name(ROUTE_ATTR) {
|
||||
None
|
||||
} else {
|
||||
attr.value.meta_item_list().and_then(|list| list[0].name())
|
||||
}
|
||||
}).next();
|
||||
|
||||
// Try to get the DEF_ID using the info struct's name. Return early if
|
||||
// anything goes awry.
|
||||
let def_id = match attr_value {
|
||||
Some(val) if self.info_structs.contains_key(&val) => {
|
||||
self.info_structs.get(&val).unwrap()
|
||||
}
|
||||
_ => return
|
||||
};
|
||||
|
||||
// Add this to the list of declared routes to check for mounting later
|
||||
// unless unmounted routes were explicitly allowed for this function.
|
||||
if cx.current_level(UNMOUNTED_ROUTE) != Level::Allow {
|
||||
self.declared.push((fn_name, fn_sp, def_id.clone()));
|
||||
}
|
||||
|
||||
// If unmanaged state was explicitly allowed for this function, don't
|
||||
// record any additional information. Just return now.
|
||||
if cx.current_level(UNMANAGED_STATE) == Level::Allow {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all of the `State` types into `tys`.
|
||||
let mut tys: Vec<Ty<'static>> = vec![];
|
||||
if let Some(sig) = cx.tables.liberated_fn_sigs.get(&fn_id) {
|
||||
for input_ty in sig.inputs() {
|
||||
let def_id = match input_ty.ty_to_def_id() {
|
||||
Some(id) => id,
|
||||
None => continue
|
||||
};
|
||||
|
||||
if !match_def_path(cx.tcx, def_id, STATE_TYPE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(inner_type) = input_ty.walk_shallow().next() {
|
||||
let casted = unsafe { transmute(inner_type) };
|
||||
tys.push(casted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all of the spans for the `State` parameters.
|
||||
let mut spans = vec![];
|
||||
for input in decl.inputs.iter() {
|
||||
let id = input.id;
|
||||
if let TyPath(ref qpath) = input.node {
|
||||
if let Def::Struct(defid) = cx.tables.qpath_def(qpath, id) {
|
||||
if match_def_path(cx.tcx, defid, STATE_TYPE) {
|
||||
spans.push(input.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check: we should have as many spans as types.
|
||||
if tys.len() != spans.len() {
|
||||
panic!("Internal lint error: mismatched type/spans: {}/{}",
|
||||
tys.len(), spans.len());
|
||||
}
|
||||
|
||||
// Insert the information we've collected.
|
||||
for (ty, span) in tys.into_iter().zip(spans.into_iter()) {
|
||||
self.requested.push((fn_name, fn_sp, def_id.clone(), ty, span));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_crate_post(&mut self, cx: &LateContext<'a, 'tcx>, _: &'tcx Crate) {
|
||||
// Record the start function span if we found one, and only one.
|
||||
// TODO: Try to find the _right_ one using some heuristics.
|
||||
let start_call_sp = match self.start_call.len() == 1 {
|
||||
true => Some(self.start_call[0]),
|
||||
false => None
|
||||
};
|
||||
|
||||
// Emit a warning for all unmounted, declared routes.
|
||||
for &(route_name, fn_sp, info_def_id) in self.declared.iter() {
|
||||
if !self.mounted.contains(&info_def_id) {
|
||||
let msg = format!("the '{}' route is not mounted", route_name);
|
||||
let mut b = cx.struct_span_lint(UNMOUNTED_ROUTE, fn_sp, &msg);
|
||||
b.note("Rocket will not dispatch requests to unmounted routes");
|
||||
if let Some(start_sp) = start_call_sp {
|
||||
b.span_help(start_sp, "maybe missing a call to 'mount' here?");
|
||||
}
|
||||
|
||||
b.emit();
|
||||
}
|
||||
}
|
||||
|
||||
let managed_types: HashSet<Ty> = self.managed.iter()
|
||||
.map(|&(ty, _)| ty)
|
||||
.collect();
|
||||
|
||||
for &(_, _, info_def_id, ty, sp) in self.requested.iter() {
|
||||
// Don't warn on unmounted routes.
|
||||
if !self.mounted.contains(&info_def_id) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !managed_types.contains(&ty) {
|
||||
let m = format!("'{}' is not currently being managed by Rocket", ty);
|
||||
let mut b = cx.struct_span_lint(UNMANAGED_STATE, sp, &m);
|
||||
b.note("this 'State' request guard will always fail");
|
||||
if let Some(start_sp) = start_call_sp {
|
||||
let msg = format!("maybe missing a call to 'manage' here?");
|
||||
b.span_help(start_sp, &msg);
|
||||
}
|
||||
|
||||
b.emit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
use rustc::ty;
|
||||
use rustc::hir::def_id::DefId;
|
||||
use rustc::lint::LateContext;
|
||||
use rustc::hir::Expr_::*;
|
||||
use rustc::hir::Expr;
|
||||
|
||||
use syntax::symbol;
|
||||
|
||||
const ROCKET_TYPE: &'static [&'static str] = &["rocket", "rocket", "Rocket"];
|
||||
|
||||
const ROCKET_IGNITE_FN: &'static [&'static str] = &["rocket", "ignite"];
|
||||
const ROCKET_IGNITE_STATIC: &'static [&'static str]
|
||||
= &["rocket", "rocket", "Rocket", "ignite"];
|
||||
|
||||
const ROCKET_CUSTOM_FN: &'static [&'static str] = &["rocket", "custom"];
|
||||
const ROCKET_CUSTOM_STATIC: &'static [&'static str]
|
||||
= &["rocket", "rocket", "Rocket", "custom"];
|
||||
|
||||
const ABSOLUTE: &'static ty::item_path::RootMode = &ty::item_path::RootMode::Absolute;
|
||||
|
||||
/// Check if a `DefId`'s path matches the given absolute type path usage.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust,ignore
|
||||
/// match_def_path(cx.tcx, id, &["core", "option", "Option"])
|
||||
/// ```
|
||||
///
|
||||
/// See also the `paths` module.
|
||||
pub fn match_def_path(tcx: ty::TyCtxt, def_id: DefId, path: &[&str]) -> bool {
|
||||
struct AbsolutePathBuffer {
|
||||
names: Vec<symbol::InternedString>,
|
||||
}
|
||||
|
||||
impl ty::item_path::ItemPathBuffer for AbsolutePathBuffer {
|
||||
fn root_mode(&self) -> &ty::item_path::RootMode {
|
||||
ABSOLUTE
|
||||
}
|
||||
|
||||
fn push(&mut self, text: &str) {
|
||||
self.names.push(symbol::Symbol::intern(text).as_str());
|
||||
}
|
||||
}
|
||||
|
||||
let mut apb = AbsolutePathBuffer { names: vec![] };
|
||||
tcx.push_item_path(&mut apb, def_id);
|
||||
|
||||
apb.names.len() == path.len() &&
|
||||
apb.names.iter().zip(path.iter()).all(|(a, &b)| &**a == b)
|
||||
}
|
||||
|
||||
/// Check if the method call given in `expr` belongs to given type.
|
||||
pub fn is_impl_method(cx: &LateContext, expr: &Expr, path: &[&str]) -> bool {
|
||||
let method_call = ty::MethodCall::expr(expr.id);
|
||||
|
||||
let trt_id = cx.tables
|
||||
.method_map
|
||||
.get(&method_call)
|
||||
.and_then(|callee| cx.tcx.impl_of_method(callee.def_id));
|
||||
|
||||
if let Some(trt_id) = trt_id {
|
||||
match_def_path(cx.tcx, trt_id, path)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rocket_method_call<'e>(
|
||||
method: &str, cx: &LateContext, expr: &'e Expr
|
||||
) -> Option<&'e [Expr]> {
|
||||
if let ExprMethodCall(ref name, _, ref exprs) = expr.node {
|
||||
if &*name.node.as_str() == method && is_impl_method(cx, expr, ROCKET_TYPE) {
|
||||
return Some(&exprs[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn is_rocket_start_call(cx: &LateContext, expr: &Expr) -> bool {
|
||||
if let ExprCall(ref expr, ..) = expr.node {
|
||||
if let ExprPath(ref qpath) = expr.node {
|
||||
let def_id = cx.tables.qpath_def(qpath, expr.id).def_id();
|
||||
if match_def_path(cx.tcx, def_id, ROCKET_IGNITE_FN) {
|
||||
return true
|
||||
} else if match_def_path(cx.tcx, def_id, ROCKET_IGNITE_STATIC) {
|
||||
return true
|
||||
} else if match_def_path(cx.tcx, def_id, ROCKET_CUSTOM_FN) {
|
||||
return true
|
||||
} else if is_impl_method(cx, expr, ROCKET_CUSTOM_STATIC) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn extract_mount_fn_def_ids(cx: &LateContext, expr: &Expr) -> Vec<DefId> {
|
||||
let mut output = Vec::new();
|
||||
// Call to into_vec
|
||||
if let ExprCall(_, ref args) = expr.node {
|
||||
if let Some(&ExprBox(ref expr)) = args.iter().next().map(|e| &e.node) {
|
||||
// Array of routes.
|
||||
if let ExprArray(ref members) = expr.node {
|
||||
for expr in members.iter() {
|
||||
// Route::From call
|
||||
if let ExprCall(_, ref args) = expr.node {
|
||||
if args.len() < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// address of info struct
|
||||
if let ExprAddrOf(_, ref expr) = args[0].node {
|
||||
// path to info_struct
|
||||
if let ExprPath(ref qpath) = expr.node {
|
||||
let def = cx.tables.qpath_def(qpath, expr.id);
|
||||
output.push(def.def_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
|
@ -12,6 +12,7 @@ pub use self::span_ext::SpanExt;
|
|||
|
||||
use std::convert::AsRef;
|
||||
|
||||
use syntax;
|
||||
use syntax::parse::token::Token;
|
||||
use syntax::tokenstream::TokenTree;
|
||||
use syntax::ast::{Item, Expr};
|
||||
|
@ -21,14 +22,13 @@ use syntax::ext::quote::rt::ToTokens;
|
|||
use syntax::print::pprust::item_to_string;
|
||||
use syntax::ptr::P;
|
||||
use syntax::fold::Folder;
|
||||
use syntax::ast::{Lifetime, LifetimeDef, Ty};
|
||||
use syntax::ast::{Attribute, Lifetime, LifetimeDef, Ty};
|
||||
use syntax::attr::HasAttrs;
|
||||
|
||||
#[inline]
|
||||
pub fn span<T>(t: T, span: Span) -> Spanned<T> {
|
||||
spanned(span.lo, span.hi, t)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sep_by_tok<T>(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec<TokenTree>
|
||||
where T: ToTokens
|
||||
{
|
||||
|
@ -43,7 +43,6 @@ pub fn sep_by_tok<T>(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec<TokenTree
|
|||
output
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
|
||||
match *opt {
|
||||
Some(ref item) => quote_expr!(ecx, Some($item)),
|
||||
|
@ -51,10 +50,24 @@ pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn emit_item(push: &mut FnMut(Annotatable), item: P<Item>) {
|
||||
debug!("Emitting item: {}", item_to_string(&item));
|
||||
push(Annotatable::Item(item));
|
||||
pub fn emit_item(items: &mut Vec<Annotatable>, item: P<Item>) {
|
||||
debug!("Emitting item:\n{}", item_to_string(&item));
|
||||
items.push(Annotatable::Item(item));
|
||||
}
|
||||
|
||||
pub fn attach_and_emit(out: &mut Vec<Annotatable>, attr: Attribute, to: Annotatable) {
|
||||
syntax::attr::mark_used(&attr);
|
||||
syntax::attr::mark_known(&attr);
|
||||
|
||||
// Attach the attribute to the user's function and emit it.
|
||||
if let Annotatable::Item(user_item) = to {
|
||||
let item = user_item.map_attrs(|mut attrs| {
|
||||
attrs.push(attr);
|
||||
attrs
|
||||
});
|
||||
|
||||
emit_item(out, item);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! quote_enum {
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
#![feature(plugin)]
|
||||
#![plugin(rocket_codegen)]
|
||||
#![allow(dead_code)]
|
||||
#![deny(unmanaged_state)]
|
||||
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::State;
|
||||
|
||||
struct MyType;
|
||||
struct MySecondType;
|
||||
|
||||
mod external {
|
||||
#[get("/state/extern")]
|
||||
fn unmanaged(_c: ::State<i32>) { }
|
||||
//~^ ERROR not currently being managed
|
||||
|
||||
#[get("/state/extern")]
|
||||
fn managed(_c: ::State<u32>) { }
|
||||
//~^ WARN is not mounted
|
||||
|
||||
#[get("/state/extern")]
|
||||
fn unmanaged_unmounted(_c: ::State<u8>) { }
|
||||
//~^ WARN is not mounted
|
||||
}
|
||||
|
||||
#[get("/state/bad")]
|
||||
fn unmanaged(_b: State<MySecondType>) { }
|
||||
//~^ ERROR not currently being managed
|
||||
|
||||
#[get("/state/ok")]
|
||||
fn managed(_a: State<u32>) { }
|
||||
|
||||
#[get("/state/bad")]
|
||||
fn managed_two(_b: State<MyType>) { }
|
||||
|
||||
#[get("/state/ok")]
|
||||
fn unmounted_doesnt_error(_a: State<i8>) { }
|
||||
//~^ WARN is not mounted
|
||||
|
||||
#[get("/ignored")]
|
||||
#[allow(unmanaged_state)]
|
||||
fn ignored(_b: State<u16>) { }
|
||||
|
||||
#[get("/unmounted/ignored")]
|
||||
#[allow(unmounted_route)]
|
||||
fn unmounted_ignored() { }
|
||||
|
||||
fn main() {
|
||||
rocket::ignite()
|
||||
.mount("/", routes![managed, unmanaged, external::unmanaged])
|
||||
.mount("/", routes![managed_two, ignored])
|
||||
.manage(MyType)
|
||||
.manage(100u32);
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
extern crate rocket;
|
||||
|
||||
#[get("/")]
|
||||
#[allow(unmounted_route)]
|
||||
pub fn hello() -> &'static str {
|
||||
"Hello, world!"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue