diff --git a/codegen/src/decorators/error.rs b/codegen/src/decorators/error.rs index 7eb99c4c..d7810673 100644 --- a/codegen/src/decorators/error.rs +++ b/codegen/src/decorators/error.rs @@ -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 +{ + 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 } diff --git a/codegen/src/decorators/route.rs b/codegen/src/decorators/route.rs index 193d4ec9..29b438f5 100644 --- a/codegen/src/decorators/route.rs +++ b/codegen/src/decorators/route.rs @@ -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>, ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, - annotated: &Annotatable, - push: &mut FnMut(Annotatable)) { + annotated: Annotatable) + -> Vec +{ + 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>, // 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>, // 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>, 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 { + 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 { 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) } ) } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 7defc3cc..4575adfe 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -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); } diff --git a/codegen/src/lints/mod.rs b/codegen/src/lints/mod.rs new file mode 100644 index 00000000..ddc501a1 --- /dev/null +++ b/codegen/src/lints/mod.rs @@ -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, + // The names of all of the routes that were declared. + info_structs: HashMap, + // 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, +} + +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> = 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 = 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() + } + } + } +} diff --git a/codegen/src/lints/utils.rs b/codegen/src/lints/utils.rs new file mode 100644 index 00000000..0773903b --- /dev/null +++ b/codegen/src/lints/utils.rs @@ -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, + } + + 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 { + 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 +} diff --git a/codegen/src/utils/mod.rs b/codegen/src/utils/mod.rs index ebb6f78d..41664c53 100644 --- a/codegen/src/utils/mod.rs +++ b/codegen/src/utils/mod.rs @@ -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, span: Span) -> Spanned { spanned(span.lo, span.hi, t) } -#[inline] pub fn sep_by_tok(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec where T: ToTokens { @@ -43,7 +43,6 @@ pub fn sep_by_tok(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec(ecx: &ExtCtxt, opt: &Option) -> P { match *opt { Some(ref item) => quote_expr!(ecx, Some($item)), @@ -51,10 +50,24 @@ pub fn option_as_expr(ecx: &ExtCtxt, opt: &Option) -> P { } } -#[inline] -pub fn emit_item(push: &mut FnMut(Annotatable), item: P) { - debug!("Emitting item: {}", item_to_string(&item)); - push(Annotatable::Item(item)); +pub fn emit_item(items: &mut Vec, item: P) { + debug!("Emitting item:\n{}", item_to_string(&item)); + items.push(Annotatable::Item(item)); +} + +pub fn attach_and_emit(out: &mut Vec, 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 { diff --git a/codegen/tests/compile-fail/lints.rs b/codegen/tests/compile-fail/lints.rs new file mode 100644 index 00000000..a8e6b85e --- /dev/null +++ b/codegen/tests/compile-fail/lints.rs @@ -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) { } + //~^ ERROR not currently being managed + + #[get("/state/extern")] + fn managed(_c: ::State) { } + //~^ WARN is not mounted + + #[get("/state/extern")] + fn unmanaged_unmounted(_c: ::State) { } + //~^ WARN is not mounted +} + +#[get("/state/bad")] +fn unmanaged(_b: State) { } +//~^ ERROR not currently being managed + +#[get("/state/ok")] +fn managed(_a: State) { } + +#[get("/state/bad")] +fn managed_two(_b: State) { } + +#[get("/state/ok")] +fn unmounted_doesnt_error(_a: State) { } +//~^ WARN is not mounted + +#[get("/ignored")] +#[allow(unmanaged_state)] +fn ignored(_b: State) { } + +#[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); +} diff --git a/examples/config/src/lib.rs b/examples/config/src/lib.rs index c61740e3..c2225f44 100644 --- a/examples/config/src/lib.rs +++ b/examples/config/src/lib.rs @@ -4,6 +4,7 @@ extern crate rocket; #[get("/")] +#[allow(unmounted_route)] pub fn hello() -> &'static str { "Hello, world!" }