diff options
| author | 2021-01-02 23:02:59 +0100 | |
|---|---|---|
| committer | 2021-01-05 16:17:14 +0100 | |
| commit | b76f7ef778dd37db08cb4dba2dc9329b15bbe984 (patch) | |
| tree | f12a3086ce2b5d5af9ed4ff6274c1fd44b3c2e1c /askama_shared | |
| parent | c2102d4d1f33fda499bccb09f77ea9cbd72f2018 (diff) | |
| download | askama-b76f7ef778dd37db08cb4dba2dc9329b15bbe984.tar.gz askama-b76f7ef778dd37db08cb4dba2dc9329b15bbe984.tar.bz2 askama-b76f7ef778dd37db08cb4dba2dc9329b15bbe984.zip | |
Removed implicit borrowing of literals, calls, and more (fixes #404)
Diffstat (limited to '')
| -rw-r--r-- | askama_shared/src/filters/json.rs | 6 | ||||
| -rw-r--r-- | askama_shared/src/filters/mod.rs | 96 | ||||
| -rw-r--r-- | askama_shared/src/filters/yaml.rs | 6 | ||||
| -rw-r--r-- | askama_shared/src/generator.rs | 13 | ||||
| -rw-r--r-- | askama_shared/src/parser.rs | 39 | 
5 files changed, 105 insertions, 55 deletions
| diff --git a/askama_shared/src/filters/json.rs b/askama_shared/src/filters/json.rs index ba7e61b..c0df707 100644 --- a/askama_shared/src/filters/json.rs +++ b/askama_shared/src/filters/json.rs @@ -8,8 +8,8 @@ use serde::Serialize;  ///  /// This will panic if `S`'s implementation of `Serialize` decides to fail,  /// or if `T` contains a map with non-string keys. -pub fn json<E: Escaper, S: Serialize>(e: E, s: &S) -> Result<MarkupDisplay<E, String>> { -    match serde_json::to_string_pretty(s) { +pub fn json<E: Escaper, S: Serialize>(e: E, s: S) -> Result<MarkupDisplay<E, String>> { +    match serde_json::to_string_pretty(&s) {          Ok(s) => Ok(MarkupDisplay::new_safe(s, e)),          Err(e) => Err(Error::from(e)),      } @@ -22,6 +22,8 @@ mod tests {      #[test]      fn test_json() { +        assert_eq!(json(Html, true).unwrap().to_string(), "true"); +        assert_eq!(json(Html, "foo").unwrap().to_string(), r#""foo""#);          assert_eq!(json(Html, &true).unwrap().to_string(), "true");          assert_eq!(json(Html, &"foo").unwrap().to_string(), r#""foo""#);          assert_eq!( diff --git a/askama_shared/src/filters/mod.rs b/askama_shared/src/filters/mod.rs index 35989ca..6838d57 100644 --- a/askama_shared/src/filters/mod.rs +++ b/askama_shared/src/filters/mod.rs @@ -141,7 +141,7 @@ pub fn filesizeformat<B: FileSize>(b: &B) -> Result<String> {  /// To encode `/` as well, see [`urlencode_strict`](./fn.urlencode_strict.html).  ///  /// [`urlencode_strict`]: ./fn.urlencode_strict.html -pub fn urlencode(s: &dyn fmt::Display) -> Result<String> { +pub fn urlencode<T: fmt::Display>(s: T) -> Result<String> {      let s = s.to_string();      Ok(utf8_percent_encode(&s, URLENCODE_SET).to_string())  } @@ -161,7 +161,7 @@ pub fn urlencode(s: &dyn fmt::Display) -> Result<String> {  /// ```  ///  /// If you want to preserve `/`, see [`urlencode`](./fn.urlencode.html). -pub fn urlencode_strict(s: &dyn fmt::Display) -> Result<String> { +pub fn urlencode_strict<T: fmt::Display>(s: T) -> Result<String> {      let s = s.to_string();      Ok(utf8_percent_encode(&s, URLENCODE_STRICT_SET).to_string())  } @@ -199,7 +199,7 @@ pub fn format() {}  ///  /// A single newline becomes an HTML line break `<br>` and a new line  /// followed by a blank line becomes a paragraph break `<p>`. -pub fn linebreaks(s: &dyn fmt::Display) -> Result<String> { +pub fn linebreaks<T: fmt::Display>(s: T) -> Result<String> {      let s = s.to_string();      let linebroken = s.replace("\n\n", "</p><p>").replace("\n", "<br/>"); @@ -207,46 +207,46 @@ pub fn linebreaks(s: &dyn fmt::Display) -> Result<String> {  }  /// Converts all newlines in a piece of plain text to HTML line breaks -pub fn linebreaksbr(s: &dyn fmt::Display) -> Result<String> { +pub fn linebreaksbr<T: fmt::Display>(s: T) -> Result<String> {      let s = s.to_string();      Ok(s.replace("\n", "<br/>"))  }  /// Converts to lowercase -pub fn lower(s: &dyn fmt::Display) -> Result<String> { +pub fn lower<T: fmt::Display>(s: T) -> Result<String> {      let s = s.to_string();      Ok(s.to_lowercase())  }  /// Alias for the `lower()` filter -pub fn lowercase(s: &dyn fmt::Display) -> Result<String> { +pub fn lowercase<T: fmt::Display>(s: T) -> Result<String> {      lower(s)  }  /// Converts to uppercase -pub fn upper(s: &dyn fmt::Display) -> Result<String> { +pub fn upper<T: fmt::Display>(s: T) -> Result<String> {      let s = s.to_string();      Ok(s.to_uppercase())  }  /// Alias for the `upper()` filter -pub fn uppercase(s: &dyn fmt::Display) -> Result<String> { +pub fn uppercase<T: fmt::Display>(s: T) -> Result<String> {      upper(s)  }  /// Strip leading and trailing whitespace -pub fn trim(s: &dyn fmt::Display) -> Result<String> { +pub fn trim<T: fmt::Display>(s: T) -> Result<String> {      let s = s.to_string();      Ok(s.trim().to_owned())  }  /// Limit string length, appends '...' if truncated -pub fn truncate(s: &dyn fmt::Display, len: &usize) -> Result<String> { +pub fn truncate<T: fmt::Display>(s: T, len: usize) -> Result<String> {      let mut s = s.to_string(); -    if s.len() <= *len { +    if s.len() <= len {          Ok(s)      } else { -        let mut real_len = *len; +        let mut real_len = len;          while !s.is_char_boundary(real_len) {              real_len += 1;          } @@ -257,7 +257,7 @@ pub fn truncate(s: &dyn fmt::Display, len: &usize) -> Result<String> {  }  /// Indent lines with `width` spaces -pub fn indent(s: &dyn fmt::Display, width: &usize) -> Result<String> { +pub fn indent<T: fmt::Display>(s: T, width: usize) -> Result<String> {      let s = s.to_string();      let mut indented = String::new(); @@ -266,7 +266,7 @@ pub fn indent(s: &dyn fmt::Display, width: &usize) -> Result<String> {          indented.push(c);          if c == '\n' && i < s.len() - 1 { -            for _ in 0..*width { +            for _ in 0..width {                  indented.push(' ');              }          } @@ -277,7 +277,7 @@ pub fn indent(s: &dyn fmt::Display, width: &usize) -> Result<String> {  #[cfg(feature = "num-traits")]  /// Casts number to f64 -pub fn into_f64<T>(number: &T) -> Result<f64> +pub fn into_f64<T>(number: T) -> Result<f64>  where      T: NumCast,  { @@ -286,7 +286,7 @@ where  #[cfg(feature = "num-traits")]  /// Casts number to isize -pub fn into_isize<T>(number: &T) -> Result<isize> +pub fn into_isize<T>(number: T) -> Result<isize>  where      T: NumCast,  { @@ -325,7 +325,7 @@ where  }  /// Capitalize a value. The first character will be uppercase, all others lowercase. -pub fn capitalize(s: &dyn fmt::Display) -> Result<String> { +pub fn capitalize<T: fmt::Display>(s: T) -> Result<String> {      let mut s = s.to_string();      match s.get_mut(0..1).map(|s| { @@ -371,7 +371,7 @@ pub fn center(src: &dyn fmt::Display, dst_len: usize) -> Result<String> {  }  /// Count the words in that string -pub fn wordcount(s: &dyn fmt::Display) -> Result<usize> { +pub fn wordcount<T: fmt::Display>(s: T) -> Result<usize> {      let s = s.to_string();      Ok(s.split_whitespace().count()) @@ -480,36 +480,36 @@ mod tests {      #[test]      fn test_truncate() { -        assert_eq!(truncate(&"hello", &2).unwrap(), "he..."); +        assert_eq!(truncate(&"hello", 2).unwrap(), "he...");          let a = String::from("您好");          assert_eq!(a.len(), 6);          assert_eq!(String::from("您").len(), 3); -        assert_eq!(truncate(&"您好", &1).unwrap(), "您..."); -        assert_eq!(truncate(&"您好", &2).unwrap(), "您..."); -        assert_eq!(truncate(&"您好", &3).unwrap(), "您..."); -        assert_eq!(truncate(&"您好", &4).unwrap(), "您好..."); -        assert_eq!(truncate(&"您好", &6).unwrap(), "您好"); -        assert_eq!(truncate(&"您好", &7).unwrap(), "您好"); +        assert_eq!(truncate(&"您好", 1).unwrap(), "您..."); +        assert_eq!(truncate(&"您好", 2).unwrap(), "您..."); +        assert_eq!(truncate(&"您好", 3).unwrap(), "您..."); +        assert_eq!(truncate(&"您好", 4).unwrap(), "您好..."); +        assert_eq!(truncate(&"您好", 6).unwrap(), "您好"); +        assert_eq!(truncate(&"您好", 7).unwrap(), "您好");          let s = String::from("🤚a🤚");          assert_eq!(s.len(), 9);          assert_eq!(String::from("🤚").len(), 4); -        assert_eq!(truncate(&"🤚a🤚", &1).unwrap(), "🤚..."); -        assert_eq!(truncate(&"🤚a🤚", &2).unwrap(), "🤚..."); -        assert_eq!(truncate(&"🤚a🤚", &3).unwrap(), "🤚..."); -        assert_eq!(truncate(&"🤚a🤚", &4).unwrap(), "🤚..."); -        assert_eq!(truncate(&"🤚a🤚", &5).unwrap(), "🤚a..."); -        assert_eq!(truncate(&"🤚a🤚", &6).unwrap(), "🤚a🤚..."); -        assert_eq!(truncate(&"🤚a🤚", &9).unwrap(), "🤚a🤚"); -        assert_eq!(truncate(&"🤚a🤚", &10).unwrap(), "🤚a🤚"); +        assert_eq!(truncate(&"🤚a🤚", 1).unwrap(), "🤚..."); +        assert_eq!(truncate(&"🤚a🤚", 2).unwrap(), "🤚..."); +        assert_eq!(truncate(&"🤚a🤚", 3).unwrap(), "🤚..."); +        assert_eq!(truncate(&"🤚a🤚", 4).unwrap(), "🤚..."); +        assert_eq!(truncate(&"🤚a🤚", 5).unwrap(), "🤚a..."); +        assert_eq!(truncate(&"🤚a🤚", 6).unwrap(), "🤚a🤚..."); +        assert_eq!(truncate(&"🤚a🤚", 9).unwrap(), "🤚a🤚"); +        assert_eq!(truncate(&"🤚a🤚", 10).unwrap(), "🤚a🤚");      }      #[test]      fn test_indent() { -        assert_eq!(indent(&"hello", &2).unwrap(), "hello"); -        assert_eq!(indent(&"hello\n", &2).unwrap(), "hello\n"); -        assert_eq!(indent(&"hello\nfoo", &2).unwrap(), "hello\n  foo"); +        assert_eq!(indent(&"hello", 2).unwrap(), "hello"); +        assert_eq!(indent(&"hello\n", 2).unwrap(), "hello\n"); +        assert_eq!(indent(&"hello\nfoo", 2).unwrap(), "hello\n  foo");          assert_eq!( -            indent(&"hello\nfoo\n bar", &4).unwrap(), +            indent(&"hello\nfoo\n bar", 4).unwrap(),              "hello\n    foo\n     bar"          );      } @@ -518,22 +518,22 @@ mod tests {      #[test]      #[allow(clippy::float_cmp)]      fn test_into_f64() { -        assert_eq!(into_f64(&1).unwrap(), 1.0_f64); -        assert_eq!(into_f64(&1.9).unwrap(), 1.9_f64); -        assert_eq!(into_f64(&-1.9).unwrap(), -1.9_f64); -        assert_eq!(into_f64(&(INFINITY as f32)).unwrap(), INFINITY); -        assert_eq!(into_f64(&(-INFINITY as f32)).unwrap(), -INFINITY); +        assert_eq!(into_f64(1).unwrap(), 1.0_f64); +        assert_eq!(into_f64(1.9).unwrap(), 1.9_f64); +        assert_eq!(into_f64(-1.9).unwrap(), -1.9_f64); +        assert_eq!(into_f64(INFINITY as f32).unwrap(), INFINITY); +        assert_eq!(into_f64(-INFINITY as f32).unwrap(), -INFINITY);      }      #[cfg(feature = "num-traits")]      #[test]      fn test_into_isize() { -        assert_eq!(into_isize(&1).unwrap(), 1_isize); -        assert_eq!(into_isize(&1.9).unwrap(), 1_isize); -        assert_eq!(into_isize(&-1.9).unwrap(), -1_isize); -        assert_eq!(into_isize(&(1.5_f64)).unwrap(), 1_isize); -        assert_eq!(into_isize(&(-1.5_f64)).unwrap(), -1_isize); -        match into_isize(&INFINITY) { +        assert_eq!(into_isize(1).unwrap(), 1_isize); +        assert_eq!(into_isize(1.9).unwrap(), 1_isize); +        assert_eq!(into_isize(-1.9).unwrap(), -1_isize); +        assert_eq!(into_isize(1.5_f64).unwrap(), 1_isize); +        assert_eq!(into_isize(-1.5_f64).unwrap(), -1_isize); +        match into_isize(INFINITY) {              Err(Fmt(fmt::Error)) => {}              _ => panic!("Should return error of type Err(Fmt(fmt::Error))"),          }; diff --git a/askama_shared/src/filters/yaml.rs b/askama_shared/src/filters/yaml.rs index fdd03f6..a6059b7 100644 --- a/askama_shared/src/filters/yaml.rs +++ b/askama_shared/src/filters/yaml.rs @@ -8,8 +8,8 @@ use serde::Serialize;  ///  /// This will panic if `S`'s implementation of `Serialize` decides to fail,  /// or if `T` contains a map with non-string keys. -pub fn yaml<E: Escaper, S: Serialize>(e: E, s: &S) -> Result<MarkupDisplay<E, String>> { -    match serde_yaml::to_string(s) { +pub fn yaml<E: Escaper, S: Serialize>(e: E, s: S) -> Result<MarkupDisplay<E, String>> { +    match serde_yaml::to_string(&s) {          Ok(s) => Ok(MarkupDisplay::new_safe(s, e)),          Err(e) => Err(Error::from(e)),      } @@ -22,6 +22,8 @@ mod tests {      #[test]      fn test_yaml() { +        assert_eq!(yaml(Html, true).unwrap().to_string(), "---\ntrue"); +        assert_eq!(yaml(Html, "foo").unwrap().to_string(), "---\nfoo");          assert_eq!(yaml(Html, &true).unwrap().to_string(), "---\ntrue");          assert_eq!(yaml(Html, &"foo").unwrap().to_string(), "---\nfoo");          assert_eq!( diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index cde0ead..801db37 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -1275,9 +1275,12 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {          for (i, arg) in args.iter().enumerate() {              if i > 0 { -                buf.write(", &"); -            } else { -                buf.write("&"); +                buf.write(", "); +            } + +            let borrow = !arg.is_copyable(); +            if borrow { +                buf.write("&(");              }              let scoped = matches!(arg, @@ -1293,6 +1296,10 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {              } else {                  self.visit_expr(buf, arg)?;              } + +            if borrow { +                buf.write(")"); +            }          }          Ok(())      } diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 43377a6..3b3d4f4 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -51,6 +51,45 @@ pub enum Expr<'a> {      RustMacro(&'a str, &'a str),  } +impl Expr<'_> { +    /// Returns `true` if enough assumptions can be made, +    /// to determine that `self` is copyable. +    pub fn is_copyable(&self) -> bool { +        self.is_copyable_within_op(false) +    } + +    fn is_copyable_within_op(&self, within_op: bool) -> bool { +        use Expr::*; +        match self { +            BoolLit(_) | NumLit(_) | StrLit(_) | CharLit(_) => true, +            Unary(.., expr) => expr.is_copyable_within_op(true), +            BinOp(_, lhs, rhs) => { +                lhs.is_copyable_within_op(true) && rhs.is_copyable_within_op(true) +            } +            // The result of a call likely doesn't need to be borrowed, +            // as in that case the call is more likely to return a +            // reference in the first place then. +            VarCall(..) | PathCall(..) | MethodCall(..) => true, +            // If the `expr` is within a `Unary` or `BinOp` then +            // an assumption can be made that the operand is copy. +            // If not, then the value is moved and adding `.clone()` +            // will solve that issue. However, if the operand is +            // implicitly borrowed, then it's likely not even possible +            // to get the template to compile. +            _ => within_op && self.is_attr_self(), +        } +    } + +    /// Returns `true` if this is an `Attr` where the `obj` is `"self"`. +    pub fn is_attr_self(&self) -> bool { +        match self { +            Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Var("self")) => true, +            Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Attr(..)) => obj.is_attr_self(), +            _ => false, +        } +    } +} +  pub type When<'a> = (      WS,      Option<MatchVariant<'a>>, | 
