diff --git a/macros/src/decorators/route.rs b/macros/src/decorators/route.rs
index 1adbf1a8..9a90224e 100644
--- a/macros/src/decorators/route.rs
+++ b/macros/src/decorators/route.rs
@@ -78,12 +78,7 @@ impl RouteGenerateExt for RouteParams {
}
let arg = arg.unwrap();
- if arg.ident().is_none() {
- ecx.span_err(arg.pat.span, "argument names must be identifiers");
- return None;
- };
-
- let name = arg.ident().unwrap().prepend(PARAM_PREFIX);
+ let name = arg.ident().expect("form param identifier").prepend(PARAM_PREFIX);
let ty = strip_ty_lifetimes(arg.ty.clone());
Some(quote_stmt!(ecx,
let $name: $ty =
@@ -152,14 +147,17 @@ impl RouteGenerateExt for RouteParams {
// A from_request parameter is one that isn't declared, form, or query.
let from_request = |a: &&Arg| {
- a.name().map_or(false, |name| {
+ if let Some(name) = a.name() {
!declared_set.contains(name)
&& self.form_param.as_ref().map_or(true, |p| {
!a.named(&p.value().name)
}) && self.query_param.as_ref().map_or(true, |p| {
!a.named(&p.node.name)
})
- })
+ } else {
+ ecx.span_err(a.pat.span, "argument names must be identifiers");
+ false
+ }
};
// Generate the code for `form_request` parameters.
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index e573e8a1..d6dca3a6 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -1,5 +1,5 @@
#![crate_type = "dylib"]
-#![feature(quote, concat_idents, plugin_registrar, rustc_private)]
+#![feature(quote, concat_idents, plugin_registrar, rustc_private, unicode)]
#![feature(custom_attribute)]
#![feature(dotdot_in_tuple_patterns)]
#![allow(unused_attributes)]
diff --git a/macros/src/parser/param.rs b/macros/src/parser/param.rs
index 59092bc1..ebc7342d 100644
--- a/macros/src/parser/param.rs
+++ b/macros/src/parser/param.rs
@@ -3,7 +3,7 @@ use syntax::ext::base::ExtCtxt;
use syntax::codemap::{Span, Spanned, BytePos};
use syntax::parse::token::str_to_ident;
-use utils::{span, SpanExt};
+use utils::{span, SpanExt, is_valid_ident};
#[derive(Debug)]
pub enum Param {
@@ -45,16 +45,23 @@ impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
type Item = Param;
fn next(&mut self) -> Option {
+ let err = |ecx: &ExtCtxt, sp: Span, msg: &str| {
+ ecx.span_err(sp, msg);
+ return None;
+ };
+
// Find the start and end indexes for the next parameter, if any.
- let (start, end) = match (self.string.find('<'), self.string.find('>')) {
- (Some(i), Some(j)) => (i, j),
+ let (start, end) = match self.string.find('<') {
+ Some(i) => match self.string.find('>') {
+ Some(j) => (i, j),
+ None => return err(self.ctxt, self.span, "malformed parameter list")
+ },
_ => return None,
};
// Ensure we found a valid parameter.
if end <= start {
- self.ctxt.span_err(self.span, "Parameter list is malformed.");
- return None;
+ return err(self.ctxt, self.span, "malformed parameter list");
}
// Calculate the parameter's ident.
@@ -74,11 +81,11 @@ impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
// Check for nonemptiness, that the characters are correct, and return.
if param.is_empty() {
- self.ctxt.span_err(param_span, "parameter names cannot be empty");
- None
- } else if param.contains(|c: char| !c.is_alphanumeric()) {
- self.ctxt.span_err(param_span, "parameter names must be alphanumeric");
- None
+ err(self.ctxt, param_span, "parameter names cannot be empty")
+ } else if !is_valid_ident(param) {
+ err(self.ctxt, param_span, "parameter names must be valid identifiers")
+ } else if param.starts_with("_") {
+ err(self.ctxt, param_span, "parameters cannot be ignored")
} else if is_many && !self.string.is_empty() {
let sp = self.span.shorten_to(self.string.len() as u32);
self.ctxt.struct_span_err(sp, "text after a trailing '..' param")
diff --git a/macros/src/utils/arg_ext.rs b/macros/src/utils/arg_ext.rs
index d3277a45..6ff0fce1 100644
--- a/macros/src/utils/arg_ext.rs
+++ b/macros/src/utils/arg_ext.rs
@@ -4,9 +4,7 @@ pub trait ArgExt {
fn ident(&self) -> Option<&Ident>;
fn name(&self) -> Option<&Name> {
- self.ident().map(|ident| {
- &ident.name
- })
+ self.ident().map(|ident| &ident.name)
}
fn named(&self, name: &Name) -> bool {
diff --git a/macros/src/utils/mod.rs b/macros/src/utils/mod.rs
index b6ba0318..b89c3bd2 100644
--- a/macros/src/utils/mod.rs
+++ b/macros/src/utils/mod.rs
@@ -10,6 +10,8 @@ pub use self::parser_ext::ParserExt;
pub use self::ident_ext::IdentExt;
pub use self::span_ext::SpanExt;
+use std::convert::AsRef;
+
use syntax::parse::token::Token;
use syntax::tokenstream::TokenTree;
use syntax::ast::{Item, Expr};
@@ -90,3 +92,34 @@ impl Folder for TyLifetimeRemover {
pub fn strip_ty_lifetimes(ty: P) -> P {
TyLifetimeRemover.fold_ty(ty)
}
+
+// Lifted from Rust's lexer, except this takes a `char`, not an `Option`.
+fn ident_start(c: char) -> bool {
+ (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' ||
+ (c > '\x7f' && c.is_xid_start())
+}
+
+// Lifted from Rust's lexer, except this takes a `char`, not an `Option`.
+fn ident_continue(c: char) -> bool {
+ (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
+ c == '_' || (c > '\x7f' && c.is_xid_continue())
+}
+
+pub fn is_valid_ident>(s: S) -> bool {
+ let string = s.as_ref();
+ if string.is_empty() {
+ return false;
+ }
+
+ for (i, c) in string.chars().enumerate() {
+ if i == 0 {
+ if !ident_start(c) {
+ return false;
+ }
+ } else if !ident_continue(c) {
+ return false;
+ }
+ }
+
+ true
+}
diff --git a/macros/tests/compile-fail/bad-ident-argument.rs b/macros/tests/compile-fail/bad-ident-argument.rs
new file mode 100644
index 00000000..8dd3a19e
--- /dev/null
+++ b/macros/tests/compile-fail/bad-ident-argument.rs
@@ -0,0 +1,9 @@
+#![feature(plugin)]
+#![plugin(rocket_macros)]
+
+extern crate rocket;
+
+#[get("/")]
+fn get(_: &str) -> &'static str { "hi" } //~ ERROR argument
+
+fn main() { }
diff --git a/macros/tests/compile-fail/ignored_params.rs b/macros/tests/compile-fail/ignored_params.rs
index 59b487c4..946106dc 100644
--- a/macros/tests/compile-fail/ignored_params.rs
+++ b/macros/tests/compile-fail/ignored_params.rs
@@ -2,6 +2,6 @@
#![plugin(rocket_macros)]
#[get("/")] //~ ERROR 'name' is declared
-fn get(_: &str) -> &'static str { "hi" } //~ ERROR isn't in the function
+fn get(other: &str) -> &'static str { "hi" } //~ ERROR isn't in the function
fn main() { }
diff --git a/macros/tests/compile-fail/malformed-param-list.rs b/macros/tests/compile-fail/malformed-param-list.rs
new file mode 100644
index 00000000..735fed22
--- /dev/null
+++ b/macros/tests/compile-fail/malformed-param-list.rs
@@ -0,0 +1,25 @@
+#![feature(plugin)]
+#![plugin(rocket_macros)]
+
+#[get("/><")] //~ ERROR malformed
+fn get() -> &'static str { "hi" }
+
+#[get("/<")] //~ ERROR malformed
+fn get(name: &str) -> &'static str { "hi" }
+
+#[get("/<<<<")] //~ ERROR identifiers
+fn get(name: &str) -> &'static str { "hi" }
+
+#[get("/")] //~ ERROR identifiers
+fn get() -> &'static str { "hi" }
+
+#[get("/<_>")] //~ ERROR ignored
+fn get() -> &'static str { "hi" }
+
+#[get("/<1>")] //~ ERROR identifiers
+fn get() -> &'static str { "hi" }
+
+#[get("/<>name><")] //~ ERROR cannot be empty
+fn get() -> &'static str { "hi" }
+
+fn main() { }