diff options
-rw-r--r-- | askama_shared/src/generator.rs | 88 |
1 files changed, 84 insertions, 4 deletions
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index b5ab31c..50ea67a 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -586,6 +586,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.write("{"); for (i, param) in params.iter().enumerate() { if let Some(MatchParameter::Name(p)) = param.1 { + let p = normalize_identifier(p); self.locals.insert_with_default(p); } else { self.locals.insert_with_default(param.0); @@ -822,12 +823,14 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.write("let "); match *var { Target::Name(name) => { + let name = normalize_identifier(name); self.locals.insert_with_default(name); buf.write(name); } Target::Tuple(ref targets) => { buf.write("("); for name in targets { + let name = normalize_identifier(name); self.locals.insert_with_default(name); buf.write(name); buf.write(","); @@ -851,6 +854,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { match *var { Target::Name(name) => { + let name = normalize_identifier(name); let meta = self.locals.get(&name).cloned(); let shadowed = matches!(&meta, Some(meta) if meta.initialized); @@ -867,9 +871,10 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { self.locals.insert(name, LocalMeta::initialized()); } Target::Tuple(ref targets) => { - let shadowed = targets - .iter() - .any(|name| matches!(self.locals.get(&name), Some(meta) if meta.initialized)); + let shadowed = targets.iter().any(|name| { + let name = normalize_identifier(name); + matches!(self.locals.get(&name), Some(meta) if meta.initialized) + }); if shadowed { // Need to flush the buffer if the variable is being shadowed, // to ensure the old variable is used. @@ -878,6 +883,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { buf.write("let ("); for name in targets { + let name = normalize_identifier(name); self.locals.insert(name, LocalMeta::initialized()); buf.write(name); buf.write(","); @@ -1442,7 +1448,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { return DisplayWrap::Unwrapped; } - buf.write(&self.locals.resolve_or_self(&s)); + buf.write(normalize_identifier(&self.locals.resolve_or_self(&s))); DisplayWrap::Unwrapped } @@ -1453,6 +1459,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { args: &[Expr], ) -> Result<DisplayWrap, CompileError> { buf.write("("); + let s = normalize_identifier(s); if !self.locals.contains(&s) && s != "self" { buf.write("self."); } @@ -1486,12 +1493,14 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { fn visit_target(&mut self, buf: &mut Buffer, target: &'a Target) { match *target { Target::Name(name) => { + let name = normalize_identifier(name); self.locals.insert_with_default(name); buf.write(name); } Target::Tuple(ref targets) => { buf.write("("); for name in targets { + let name = normalize_identifier(name); self.locals.insert_with_default(name); buf.write(name); buf.write(","); @@ -1689,6 +1698,7 @@ where impl MapChain<'_, &str, LocalMeta> { fn resolve(&self, name: &str) -> Option<String> { + let name = normalize_identifier(name); self.get(&name).map(|meta| match &meta.refs { Some(expr) => expr.clone(), None => name.to_string(), @@ -1696,6 +1706,7 @@ impl MapChain<'_, &str, LocalMeta> { } fn resolve_or_self(&self, name: &str) -> String { + let name = normalize_identifier(name); self.resolve(name) .unwrap_or_else(|| format!("self.{}", name)) } @@ -1732,3 +1743,72 @@ enum Writable<'a> { Lit(&'a str), Expr(&'a Expr<'a>), } + +// Identifiers to be replaced with raw identifiers, so as to avoid +// collisions between template syntax and Rust's syntax. In particular +// [Rust keywords](https://doc.rust-lang.org/reference/keywords.html) +// should be replaced, since they're not reserved words in Askama +// syntax but have a high probability of causing problems in the +// generated code. +// +// This list excludes the Rust keywords *self*, *Self*, and *super* +// because they are not allowed to be raw identifiers, and *loop* +// because it's used something like a keyword in the template +// language. +static USE_RAW: [(&str, &str); 47] = [ + ("as", "r#as"), + ("break", "r#break"), + ("const", "r#const"), + ("continue", "r#continue"), + ("crate", "r#crate"), + ("else", "r#else"), + ("enum", "r#enum"), + ("extern", "r#extern"), + ("false", "r#false"), + ("fn", "r#fn"), + ("for", "r#for"), + ("if", "r#if"), + ("impl", "r#impl"), + ("in", "r#in"), + ("let", "r#let"), + ("match", "r#match"), + ("mod", "r#mod"), + ("move", "r#move"), + ("mut", "r#mut"), + ("pub", "r#pub"), + ("ref", "r#ref"), + ("return", "r#return"), + ("static", "r#static"), + ("struct", "r#struct"), + ("trait", "r#trait"), + ("true", "r#true"), + ("type", "r#type"), + ("unsafe", "r#unsafe"), + ("use", "r#use"), + ("where", "r#where"), + ("while", "r#while"), + ("async", "r#async"), + ("await", "r#await"), + ("dyn", "r#dyn"), + ("abstract", "r#abstract"), + ("become", "r#become"), + ("box", "r#box"), + ("do", "r#do"), + ("final", "r#final"), + ("macro", "r#macro"), + ("override", "r#override"), + ("priv", "r#priv"), + ("typeof", "r#typeof"), + ("unsized", "r#unsized"), + ("virtual", "r#virtual"), + ("yield", "r#yield"), + ("try", "r#try"), +]; + +fn normalize_identifier(ident: &str) -> &str { + if let Some(word) = USE_RAW.iter().find(|x| x.0 == ident) { + word.1 + } else { + ident + } +} |