Repository: sunng87/handlebars-rust (rust)

Issue:
Block parameters can't be used as context for partials
This test case fails:

```rust
#[test]
fn test_partial_with_blocks() {
    let hbs = Handlebars::new();

    let data = json!({
        "a": [
            {"b": 1},
            {"b": 2},
        ],
    });

    let template = "{{#*inline \"test\"}}{{b}};{{/inline}}{{#each a as |z|}}{{> test z}}{{/each}}";
    assert_eq!(
        hbs.render_template(template, &data).unwrap(),
        "1;2;"
    );
}
```

I've verified on http://tryhandlebarsjs.com/ that this is the right output.


Discussion:
This is impacting me right now on 2.0.0-beta.2. I'd be happy to pick up the work, but I wanted to first log it as an issue. If you have any intuition as to what the issue may be, feel free to point me there.
Thanks! If it works on handlebars.js, it's something we expected to support. 

The context issue is usually quite complex. I'm here to offer help if you need.
The issue is how I visit context value here: 

https://github.com/sunng87/handlebars-rust/blob/master/src/partial.rs#L22

I'm using the local variable name to concat with current path root, for the json path. And here `z` is a local alias so it won't work. This may require a re-implementation of block context through handlebars-rust. Do you have any progress on this? Otherwise I will take on this and start the re-implementation.
No progress, @sunng87. I usually only find a few hours on the weekend, so progress would likely be slow. You have more context here, so if you're interested in taking on the effort, go ahead (and thanks!)

Patch:
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,7 +25,7 @@ quick-error = "1.0.0"
 pest = "2.1.0"
 pest_derive = "2.1.0"
 serde = "1.0.0"
-serde_json = "1.0.0"
+serde_json = "1.0.39"
 regex = "1.0.3"
 lazy_static = "1.0.0"
 walkdir = { version = "2.2.3", optional = true }
diff --git a/src/context.rs b/src/context.rs
--- a/src/context.rs
+++ b/src/context.rs
@@ -8,8 +8,64 @@ use serde_json::value::{to_value, Map, Value as Json};
 
 use crate::error::RenderError;
 use crate::grammar::{HandlebarsParser, Rule};
+use crate::value::ScopedJson;
+
 pub type Object = HashMap<String, Json>;
 
+lazy_static! {
+    static ref EMPTY_VEC_DEQUE: VecDeque<String> = VecDeque::new();
+}
+
+#[derive(Clone, Debug)]
+pub enum BlockParamHolder {
+    // a reference to certain context value
+    Path(Vec<String>),
+    // an actual value holder
+    Value(Json),
+}
+
+impl BlockParamHolder {
+    pub fn value(v: Json) -> BlockParamHolder {
+        BlockParamHolder::Value(v)
+    }
+
+    pub fn path(r: &str) -> Result<BlockParamHolder, RenderError> {
+        let mut path_stack: VecDeque<&str> = VecDeque::new();
+        parse_json_visitor_inner(&mut path_stack, r)?;
+
+        Ok(BlockParamHolder::Path(
+            path_stack.iter().cloned().map(|v| v.to_owned()).collect(),
+        ))
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct BlockParams {
+    data: HashMap<String, BlockParamHolder>,
+}
+
+impl BlockParams {
+    pub fn new() -> BlockParams {
+        BlockParams {
+            data: HashMap::new(),
+        }
+    }
+
+    pub fn add_path(&mut self, k: &str, v: &str) -> Result<(), RenderError> {
+        self.data.insert(k.to_owned(), BlockParamHolder::path(v)?);
+        Ok(())
+    }
+
+    pub fn add_value(&mut self, k: &str, v: Json) -> Result<(), RenderError> {
+        self.data.insert(k.to_owned(), BlockParamHolder::value(v));
+        Ok(())
+    }
+
+    pub fn get(&self, k: &str) -> Option<&BlockParamHolder> {
+        self.data.get(k)
+    }
+}
+
 /// The context wrap data you render on your templates.
 ///
 #[derive(Debug, Clone)]
@@ -17,7 +73,6 @@ pub struct Context {
     data: Json,
 }
 
-#[inline]
 fn parse_json_visitor_inner<'a>(
     path_stack: &mut VecDeque<&'a str>,
     path: &'a str,
@@ -58,19 +113,26 @@ fn parse_json_visitor_inner<'a>(
     Ok(())
 }
 
-fn parse_json_visitor<'a>(
-    path_stack: &mut VecDeque<&'a str>,
+fn parse_json_visitor<'a, 'b: 'a>(
     base_path: &'a str,
     path_context: &'a VecDeque<String>,
     relative_path: &'a str,
-) -> Result<(), RenderError> {
+    block_params: &'b VecDeque<BlockParams>,
+) -> Result<(VecDeque<&'a str>, Option<Json>), RenderError> {
+    let mut path_stack = VecDeque::new();
     let parser = HandlebarsParser::parse(Rule::path, relative_path)
         .map(|p| p.flatten())
         .map_err(|_| RenderError::new(format!("Invalid JSON path: {}", relative_path)))?;
 
     let mut path_context_depth: i64 = -1;
+    let mut used_block_param = None;
 
+    // deal with block param and  "../../" in relative path
     for sg in parser {
+        if let Some(holder) = get_in_block_params(block_params, sg.as_str()) {
+            used_block_param = Some(holder);
+            break;
+        }
         if sg.as_rule() == Rule::path_up {
             path_context_depth += 1;
         } else {
@@ -78,22 +140,77 @@ fn parse_json_visitor<'a>(
         }
     }
 
-    if path_context_depth >= 0 {
-        if let Some(context_base_path) = path_context.get(path_context_depth as usize) {
-            parse_json_visitor_inner(path_stack, context_base_path)?;
+    // if the relative path is a block_param_value, skip base_path and context check
+    if used_block_param.is_none() {
+        if path_context_depth >= 0 {
+            if let Some(context_base_path) = path_context.get(path_context_depth as usize) {
+                parse_json_visitor_inner(&mut path_stack, context_base_path)?;
+            } else {
+                parse_json_visitor_inner(&mut path_stack, base_path)?;
+            }
         } else {
-            parse_json_visitor_inner(path_stack, base_path)?;
+            parse_json_visitor_inner(&mut path_stack, base_path)?;
         }
-    } else {
-        parse_json_visitor_inner(path_stack, base_path)?;
     }
 
-    parse_json_visitor_inner(path_stack, relative_path)?;
-    Ok(())
+    match used_block_param {
+        Some(BlockParamHolder::Value(ref v)) => {
+            parse_json_visitor_inner(&mut path_stack, relative_path)?;
+            // drop first seg, which is block_param
+            path_stack.pop_front();
+            Ok((path_stack, Some(v.clone())))
+        }
+        Some(BlockParamHolder::Path(ref paths)) => {
+            parse_json_visitor_inner(&mut path_stack, relative_path)?;
+            // drop first seg, which is block_param
+            path_stack.pop_front();
+
+            for p in paths.iter().rev() {
+                path_stack.push_front(p)
+            }
+
+            Ok((path_stack, None))
+        }
+        None => {
+            parse_json_visitor_inner(&mut path_stack, relative_path)?;
+            Ok((path_stack, None))
+        }
+    }
+}
+
+fn get_data<'a>(d: Option<&'a Json>, p: &str) -> Result<Option<&'a Json>, RenderError> {
+    if p == "this" {
+        return Ok(d);
+    }
+
+    let result = match d {
+        Some(&Json::Array(ref l)) => p
+            .parse::<usize>()
+            .map_err(RenderError::with)
+            .map(|idx_u| l.get(idx_u))?,
+        Some(&Json::Object(ref m)) => m.get(p),
+        Some(_) => None,
+        None => None,
+    };
+    Ok(result)
+}
+
+pub(crate) fn get_in_block_params<'a>(
+    block_contexts: &'a VecDeque<BlockParams>,
+    p: &str,
+) -> Option<&'a BlockParamHolder> {
+    for bc in block_contexts {
+        let v = bc.get(p);
+        if v.is_some() {
+            return v;
+        }
+    }
+
+    None
 }
 
 pub fn merge_json(base: &Json, addition: &Object) -> Json {
-    let mut base_map = match *base {
+    let mut base_map = match base {
         Json::Object(ref m) => m.clone(),
         _ => Map::new(),
     };
@@ -123,32 +240,33 @@ impl Context {
     /// and set relative path to helper argument or so.
     ///
     /// If you want to navigate from top level, set the base path to `"."`
-    pub fn navigate(
-        &self,
+    pub fn navigate<'reg, 'rc>(
+        &'rc self,
         base_path: &str,
         path_context: &VecDeque<String>,
         relative_path: &str,
-    ) -> Result<Option<&Json>, RenderError> {
-        let mut path_stack: VecDeque<&str> = VecDeque::new();
-        parse_json_visitor(&mut path_stack, base_path, path_context, relative_path)?;
-
-        let paths: Vec<&str> = path_stack.iter().cloned().collect();
-        let mut data: Option<&Json> = Some(&self.data);
-        for p in &paths {
-            if *p == "this" {
-                continue;
+        block_params: &VecDeque<BlockParams>,
+    ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+        let (paths, block_param_value) =
+            parse_json_visitor(base_path, path_context, relative_path, block_params)?;
+
+        if let Some(block_param_value) = block_param_value {
+            let mut data = Some(&block_param_value);
+            for p in paths.iter() {
+                data = get_data(data, p)?;
             }
-            data = match data {
-                Some(&Json::Array(ref l)) => p
-                    .parse::<usize>()
-                    .map_err(RenderError::with)
-                    .map(|idx_u| l.get(idx_u))?,
-                Some(&Json::Object(ref m)) => m.get(*p),
-                Some(_) => None,
-                None => break,
+            Ok(data
+                .map(|v| ScopedJson::Derived(v.clone()))
+                .unwrap_or_else(|| ScopedJson::Missing))
+        } else {
+            let mut data = Some(self.data());
+            for p in paths.iter() {
+                data = get_data(data, p)?;
             }
+            Ok(data
+                .map(|v| ScopedJson::Context(v))
+                .unwrap_or_else(|| ScopedJson::Missing))
         }
-        Ok(data)
     }
 
     pub fn data(&self) -> &Json {
@@ -162,7 +280,7 @@ impl Context {
 
 #[cfg(test)]
 mod test {
-    use crate::context::{self, Context};
+    use crate::context::{self, BlockParams, Context};
     use crate::value::{self, JsonRender};
     use hashbrown::HashMap;
     use serde_json::value::Map;
@@ -187,8 +305,7 @@ mod test {
         let v = "hello";
         let ctx = Context::wraps(&v.to_string()).unwrap();
         assert_eq!(
-            ctx.navigate(".", &VecDeque::new(), "this")
-                .unwrap()
+            ctx.navigate(".", &VecDeque::new(), "this", &VecDeque::new())
                 .unwrap()
                 .render(),
             v.to_string()
@@ -211,15 +328,18 @@ mod test {
 
         let ctx = Context::wraps(&person).unwrap();
         assert_eq!(
-            ctx.navigate(".", &VecDeque::new(), "./name/../addr/country")
-                .unwrap()
-                .unwrap()
-                .render(),
+            ctx.navigate(
+                ".",
+                &VecDeque::new(),
+                "./name/../addr/country",
+                &VecDeque::new()
+            )
+            .unwrap()
+            .render(),
             "China".to_string()
         );
         assert_eq!(
-            ctx.navigate(".", &VecDeque::new(), "addr.[country]")
-                .unwrap()
+            ctx.navigate(".", &VecDeque::new(), "addr.[country]", &VecDeque::new())
                 .unwrap()
                 .render(),
             "China".to_string()
@@ -228,33 +348,39 @@ mod test {
         let v = true;
         let ctx2 = Context::wraps(&v).unwrap();
         assert_eq!(
-            ctx2.navigate(".", &VecDeque::new(), "this")
-                .unwrap()
+            ctx2.navigate(".", &VecDeque::new(), "this", &VecDeque::new())
                 .unwrap()
                 .render(),
             "true".to_string()
         );
 
         assert_eq!(
-            ctx.navigate(".", &VecDeque::new(), "titles.[0]")
-                .unwrap()
+            ctx.navigate(".", &VecDeque::new(), "titles.[0]", &VecDeque::new())
                 .unwrap()
                 .render(),
             "programmer".to_string()
         );
 
         assert_eq!(
-            ctx.navigate(".", &VecDeque::new(), "titles.[0]/../../age")
-                .unwrap()
-                .unwrap()
-                .render(),
+            ctx.navigate(
+                ".",
+                &VecDeque::new(),
+                "titles.[0]/../../age",
+                &VecDeque::new()
+            )
+            .unwrap()
+            .render(),
             "27".to_string()
         );
         assert_eq!(
-            ctx.navigate(".", &VecDeque::new(), "this.titles.[0]/../../age")
-                .unwrap()
-                .unwrap()
-                .render(),
+            ctx.navigate(
+                ".",
+                &VecDeque::new(),
+                "this.titles.[0]/../../age",
+                &VecDeque::new()
+            )
+            .unwrap()
+            .render(),
             "27".to_string()
         );
     }
@@ -271,15 +397,13 @@ mod test {
         let ctx2 = Context::wraps(&map_without_this).unwrap();
 
         assert_eq!(
-            ctx1.navigate(".", &VecDeque::new(), "this")
-                .unwrap()
+            ctx1.navigate(".", &VecDeque::new(), "this", &VecDeque::new())
                 .unwrap()
                 .render(),
             "[object]".to_owned()
         );
         assert_eq!(
-            ctx2.navigate(".", &VecDeque::new(), "age")
-                .unwrap()
+            ctx2.navigate(".", &VecDeque::new(), "age", &VecDeque::new())
                 .unwrap()
                 .render(),
             "4".to_owned()
@@ -296,16 +420,14 @@ mod test {
         let ctx_a1 = Context::wraps(&context::merge_json(&map, &hash)).unwrap();
         assert_eq!(
             ctx_a1
-                .navigate(".", &VecDeque::new(), "age")
-                .unwrap()
+                .navigate(".", &VecDeque::new(), "age", &VecDeque::new())
                 .unwrap()
                 .render(),
             "4".to_owned()
         );
         assert_eq!(
             ctx_a1
-                .navigate(".", &VecDeque::new(), "tag")
-                .unwrap()
+                .navigate(".", &VecDeque::new(), "tag", &VecDeque::new())
                 .unwrap()
                 .render(),
             "h1".to_owned()
@@ -314,16 +436,14 @@ mod test {
         let ctx_a2 = Context::wraps(&context::merge_json(&value::to_json(s), &hash)).unwrap();
         assert_eq!(
             ctx_a2
-                .navigate(".", &VecDeque::new(), "this")
-                .unwrap()
+                .navigate(".", &VecDeque::new(), "this", &VecDeque::new())
                 .unwrap()
                 .render(),
             "[object]".to_owned()
         );
         assert_eq!(
             ctx_a2
-                .navigate(".", &VecDeque::new(), "tag")
-                .unwrap()
+                .navigate(".", &VecDeque::new(), "tag", &VecDeque::new())
                 .unwrap()
                 .render(),
             "h1".to_owned()
@@ -337,8 +457,7 @@ mod test {
         };
         let ctx = Context::wraps(&m).unwrap();
         assert_eq!(
-            ctx.navigate(".", &VecDeque::new(), "this_name")
-                .unwrap()
+            ctx.navigate(".", &VecDeque::new(), "this_name", &VecDeque::new())
                 .unwrap()
                 .render(),
             "the_value".to_string()
@@ -379,8 +498,30 @@ mod test {
         });
         let ctx = Context::wraps(&m).unwrap();
         assert_eq!(
-            ctx.navigate("a/b", &VecDeque::new(), "@root/b")
+            ctx.navigate("a/b", &VecDeque::new(), "@root/b", &VecDeque::new())
                 .unwrap()
+                .render(),
+            "2".to_string()
+        );
+    }
+
+    #[test]
+    fn test_block_params() {
+        let m = json!([{
+            "a": [1, 2]
+        }, {
+            "b": [2, 3]
+        }]);
+
+        let ctx = Context::wraps(&m).unwrap();
+        let mut block_param = BlockParams::new();
+        block_param.add_path("z", "[0].a").unwrap();
+
+        let mut block_params = VecDeque::new();
+        block_params.push_front(block_param);
+
+        assert_eq!(
+            ctx.navigate(".", &VecDeque::new(), "z.[1]", &block_params)
                 .unwrap()
                 .render(),
             "2".to_string()
diff --git a/src/helpers/helper_each.rs b/src/helpers/helper_each.rs
--- a/src/helpers/helper_each.rs
+++ b/src/helpers/helper_each.rs
@@ -1,7 +1,6 @@
-use hashbrown::HashMap;
 use serde_json::value::Value as Json;
 
-use crate::context::Context;
+use crate::context::{self, BlockParams, Context};
 use crate::error::RenderError;
 use crate::helpers::{HelperDef, HelperResult};
 use crate::output::Output;
@@ -38,6 +37,9 @@ impl HelperDef for EachHelper {
                 let rendered = match (value.value().is_truthy(false), value.value()) {
                     (true, &Json::Array(ref list)) => {
                         let len = list.len();
+
+                        let array_path = value.path().and_then(|p| rc.concat_path(p));
+
                         for (i, _) in list.iter().enumerate().take(len) {
                             let mut local_rc = rc.derive();
                             if let Some(ref p) = local_path_root {
@@ -48,17 +50,17 @@ impl HelperDef for EachHelper {
                             local_rc.set_local_var("@last".to_string(), to_json(i == len - 1));
                             local_rc.set_local_var("@index".to_string(), to_json(i));
 
-                            if let Some(inner_path) = value.path() {
-                                let new_path =
-                                    format!("{}/{}/[{}]", local_rc.get_path(), inner_path, i);
+                            if let Some(ref p) = array_path {
+                                let new_path = format!("{}/[{}]", p, i);
                                 debug!("each path {:?}", new_path);
                                 local_rc.set_path(new_path);
                             }
 
                             if let Some(block_param) = h.block_param() {
-                                let mut map = HashMap::new();
-                                map.insert(block_param.to_string(), to_json(&list[i]));
-                                local_rc.push_block_context(&map)?;
+                                let mut params = BlockParams::new();
+                                params.add_path(block_param, local_rc.get_path())?;
+
+                                local_rc.push_block_context(params)?;
                             }
 
                             t.render(r, ctx, &mut local_rc, out)?;
@@ -75,7 +77,9 @@ impl HelperDef for EachHelper {
                     }
                     (true, &Json::Object(ref obj)) => {
                         let mut first: bool = true;
-                        for (k, v) in obj.iter() {
+                        let obj_path = value.path().and_then(|p| rc.concat_path(p));
+
+                        for (k, _) in obj.iter() {
                             let mut local_rc = rc.derive();
 
                             if let Some(ref p) = local_path_root {
@@ -88,17 +92,17 @@ impl HelperDef for EachHelper {
 
                             local_rc.set_local_var("@key".to_string(), to_json(k));
 
-                            if let Some(inner_path) = value.path() {
-                                let new_path =
-                                    format!("{}/{}/[{}]", local_rc.get_path(), inner_path, k);
+                            if let Some(ref p) = obj_path {
+                                let new_path = format!("{}/[{}]", p, k);
                                 local_rc.set_path(new_path);
                             }
 
                             if let Some((bp_val, bp_key)) = h.block_param_pair() {
-                                let mut map = HashMap::new();
-                                map.insert(bp_key.to_string(), to_json(k));
-                                map.insert(bp_val.to_string(), to_json(v));
-                                local_rc.push_block_context(&map)?;
+                                let mut params = BlockParams::new();
+                                params.add_path(bp_val, local_rc.get_path())?;
+                                params.add_value(bp_key, to_json(&k))?;
+
+                                local_rc.push_block_context(params)?;
                             }
 
                             t.render(r, ctx, &mut local_rc, out)?;
@@ -319,6 +323,22 @@ mod test {
     }
 
     #[test]
+    fn test_each_object_block_param2() {
+        let mut handlebars = Registry::new();
+        let template = "{{#each this as |v k|}}\
+                        {{#with v as |inner_v|}}{{k}}:{{inner_v}}{{/with}}|\
+                        {{/each}}";
+
+        assert!(handlebars.register_template_string("t0", template).is_ok());
+
+        let m = btreemap! {
+            "ftp".to_string() => 21,
+            "http".to_string() => 80
+        };
+        let r0 = handlebars.render("t0", &m);
+        assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
+    }
+
     fn test_nested_each_with_path_ups() {
         let mut handlebars = Registry::new();
         assert!(handlebars
diff --git a/src/helpers/helper_if.rs b/src/helpers/helper_if.rs
--- a/src/helpers/helper_if.rs
+++ b/src/helpers/helper_if.rs
@@ -16,7 +16,7 @@ impl HelperDef for IfHelper {
         &self,
         h: &Helper<'reg, 'rc>,
         r: &'reg Registry,
-        ctx: &Context,
+        ctx: &'rc Context,
         rc: &mut RenderContext<'reg>,
         out: &mut Output,
     ) -> HelperResult {
diff --git a/src/helpers/helper_raw.rs b/src/helpers/helper_raw.rs
--- a/src/helpers/helper_raw.rs
+++ b/src/helpers/helper_raw.rs
@@ -12,7 +12,7 @@ impl HelperDef for RawHelper {
         &self,
         h: &Helper<'reg, 'rc>,
         r: &'reg Registry,
-        ctx: &Context,
+        ctx: &'rc Context,
         rc: &mut RenderContext<'reg>,
         out: &mut Output,
     ) -> HelperResult {
diff --git a/src/helpers/helper_with.rs b/src/helpers/helper_with.rs
--- a/src/helpers/helper_with.rs
+++ b/src/helpers/helper_with.rs
@@ -1,12 +1,10 @@
-use hashbrown::HashMap;
-
-use crate::context::Context;
+use crate::context::{BlockParams, Context};
 use crate::error::RenderError;
 use crate::helpers::{HelperDef, HelperResult};
 use crate::output::Output;
 use crate::registry::Registry;
 use crate::render::{Helper, RenderContext, Renderable};
-use crate::value::{to_json, JsonTruthy};
+use crate::value::JsonTruthy;
 
 #[derive(Clone, Copy)]
 pub struct WithHelper;
@@ -37,15 +35,20 @@ impl HelperDef for WithHelper {
                 local_rc.push_local_path_root(local_path_root);
             }
             if not_empty {
-                if let Some(inner_path) = param.path() {
-                    let new_path = format!("{}/{}", local_rc.get_path(), inner_path);
-                    local_rc.set_path(new_path);
+                let new_path = param.path().and_then(|p| local_rc.concat_path(p));
+                if let Some(ref new_path) = new_path {
+                    local_rc.set_path(new_path.clone());
                 }
 
                 if let Some(block_param) = h.block_param() {
-                    let mut map = HashMap::new();
-                    map.insert(block_param.to_string(), to_json(param.value()));
-                    local_rc.push_block_context(&map)?;
+                    let mut params = BlockParams::new();
+                    if new_path.is_some() {
+                        params.add_path(block_param, local_rc.get_path())?;
+                    } else {
+                        params.add_value(block_param, param.value().clone())?;
+                    }
+
+                    local_rc.push_block_context(params)?;
                 }
             }
 
diff --git a/src/macros.rs b/src/macros.rs
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -33,7 +33,7 @@ macro_rules! handlebars_helper {
                 h: &$crate::Helper<'reg, 'rc>,
                 _: &'reg $crate::Handlebars,
                 _: &'rc $crate::Context,
-                _: &mut $crate::RenderContext<'reg>,
+                _: &mut $crate::RenderContext,
             ) -> Result<Option<$crate::ScopedJson<'reg, 'rc>>, $crate::RenderError> {
                 let mut param_idx = 0;
 
diff --git a/src/partial.rs b/src/partial.rs
--- a/src/partial.rs
+++ b/src/partial.rs
@@ -1,14 +1,13 @@
-use std::iter::FromIterator;
-
 use hashbrown::HashMap;
 
+use serde_json::value::Value as Json;
+
 use crate::context::{merge_json, Context};
 use crate::error::RenderError;
 use crate::output::Output;
 use crate::registry::Registry;
 use crate::render::{Directive, Evaluable, RenderContext, Renderable};
 use crate::template::Template;
-use crate::value::DEFAULT_VALUE;
 
 fn render_partial<'reg: 'rc, 'rc>(
     t: &'reg Template,
@@ -20,11 +19,9 @@ fn render_partial<'reg: 'rc, 'rc>(
 ) -> Result<(), RenderError> {
     // partial context path
     if let Some(ref p) = d.param(0) {
-        if let Some(ref param_path) = p.path() {
-            let old_path = local_rc.get_path().clone();
+        if let Some(param_path) = p.path().and_then(|p| local_rc.concat_path(p)) {
             local_rc.promote_local_vars();
-            let new_path = format!("{}/{}", old_path, param_path);
-            local_rc.set_path(new_path);
+            local_rc.set_path(param_path);
         }
     };
 
@@ -36,14 +33,12 @@ fn render_partial<'reg: 'rc, 'rc>(
     if d.hash().is_empty() {
         t.render(r, ctx, local_rc, out)
     } else {
-        let hash_ctx =
-            HashMap::from_iter(d.hash().iter().map(|(k, v)| (k.clone(), v.value().clone())));
-        let partial_context = merge_json(
-            local_rc
-                .evaluate(ctx, ".")?
-                .unwrap_or_else(|| &DEFAULT_VALUE),
-            &hash_ctx,
-        );
+        let hash_ctx = d
+            .hash()
+            .iter()
+            .map(|(k, v)| (k.clone(), v.value().clone()))
+            .collect::<HashMap<String, Json>>();
+        let partial_context = merge_json(local_rc.evaluate(ctx, ".")?.as_json(), &hash_ctx);
         let ctx = Context::wraps(&partial_context)?;
         let mut partial_rc = local_rc.new_for_block();
         t.render(r, &ctx, &mut partial_rc, out)
diff --git a/src/render.rs b/src/render.rs
--- a/src/render.rs
+++ b/src/render.rs
@@ -5,10 +5,9 @@ use std::ops::Deref;
 use std::rc::Rc;
 
 use hashbrown::HashMap;
-use serde::Serialize;
 use serde_json::value::Value as Json;
 
-use crate::context::Context;
+use crate::context::{self, BlockParamHolder, BlockParams, Context};
 use crate::error::RenderError;
 use crate::helpers::HelperDef;
 use crate::output::{Output, StringOutput};
@@ -50,11 +49,12 @@ pub struct RenderContextInner<'reg> {
 pub struct BlockRenderContext {
     path: String,
     local_path_root: VecDeque<String>,
-    block_context: VecDeque<Context>,
+    // current block context variables
+    block_context: VecDeque<BlockParams>,
 }
 
-impl Default for BlockRenderContext {
-    fn default() -> BlockRenderContext {
+impl BlockRenderContext {
+    fn new() -> BlockRenderContext {
         BlockRenderContext {
             path: ".".to_owned(),
             local_path_root: VecDeque::new(),
@@ -75,7 +75,7 @@ impl<'reg> RenderContext<'reg> {
             disable_escape: false,
         });
 
-        let block = Rc::new(BlockRenderContext::default());
+        let block = Rc::new(BlockRenderContext::new());
         let modified_context = None;
         RenderContext {
             inner,
@@ -90,7 +90,7 @@ impl<'reg> RenderContext<'reg> {
 
     pub fn new_for_block(&self) -> RenderContext<'reg> {
         let inner = self.inner.clone();
-        let block = Rc::new(BlockRenderContext::default());
+        let block = Rc::new(BlockRenderContext::new());
         let modified_context = self.modified_context.clone();
 
         RenderContext {
@@ -124,20 +124,17 @@ impl<'reg> RenderContext<'reg> {
         self.modified_context = Some(Rc::new(ctx))
     }
 
-    pub fn evaluate<'ctx>(
+    pub fn evaluate<'rc>(
         &self,
-        context: &'ctx Context,
-        path: &str,
-    ) -> Result<Option<&'ctx Json>, RenderError> {
-        context.navigate(self.get_path(), self.get_local_path_root(), path)
-    }
-
-    pub fn evaluate_absolute<'ctx>(
-        &self,
-        context: &'ctx Context,
+        context: &'rc Context,
         path: &str,
-    ) -> Result<Option<&'ctx Json>, RenderError> {
-        context.navigate(".", &VecDeque::new(), path)
+    ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+        context.navigate(
+            self.get_path(),
+            self.get_local_path_root(),
+            path,
+            &self.block.block_context,
+        )
     }
 
     pub fn get_partial(&self, name: &str) -> Option<&&Template> {
@@ -235,6 +232,14 @@ impl<'reg> RenderContext<'reg> {
         self.block_mut().path = path;
     }
 
+    pub fn concat_path(&self, path_seg: &str) -> Option<String> {
+        match context::get_in_block_params(&self.block.block_context, path_seg) {
+            Some(BlockParamHolder::Path(paths)) => Some(paths.join("/")),
+            Some(BlockParamHolder::Value(_)) => None,
+            None => Some(format!("{}/{}", self.get_path(), path_seg)),
+        }
+    }
+
     pub fn get_local_path_root(&self) -> &VecDeque<String> {
         &self.block().local_path_root
     }
@@ -247,34 +252,14 @@ impl<'reg> RenderContext<'reg> {
         self.block_mut().local_path_root.pop_front();
     }
 
-    pub fn push_block_context<T>(&mut self, ctx: &T) -> Result<(), RenderError>
-    where
-        T: Serialize,
-    {
-        self.block_mut()
-            .block_context
-            .push_front(Context::wraps(ctx)?);
+    pub fn push_block_context(&mut self, current_context: BlockParams) -> Result<(), RenderError> {
+        self.block_mut().block_context.push_front(current_context);
         Ok(())
     }
 
     pub fn pop_block_context(&mut self) {
         self.block_mut().block_context.pop_front();
     }
-
-    pub fn evaluate_in_block_context(
-        &self,
-        local_path: &str,
-    ) -> Result<Option<&Json>, RenderError> {
-        let block = self.block();
-        for bc in &block.block_context {
-            let v = bc.navigate(".", &block.local_path_root, local_path)?;
-            if v.is_some() {
-                return Ok(v);
-            }
-        }
-
-        Ok(None)
-    }
 }
 
 impl<'reg> fmt::Debug for RenderContextInner<'reg> {
@@ -505,7 +490,7 @@ pub trait Renderable {
         &'reg self,
         registry: &'reg Registry,
         context: &'rc Context,
-        rc: &'rc mut RenderContext<'reg>,
+        rc: &mut RenderContext<'reg>,
         out: &mut Output,
     ) -> Result<(), RenderError>;
 
@@ -514,7 +499,7 @@ pub trait Renderable {
         &'reg self,
         registry: &'reg Registry,
         ctx: &'rc Context,
-        rc: &'rc mut RenderContext<'reg>,
+        rc: &mut RenderContext<'reg>,
     ) -> Result<String, RenderError> {
         let mut so = StringOutput::new();
         self.render(registry, ctx, rc, &mut so)?;
@@ -528,7 +513,7 @@ pub trait Evaluable {
         &'reg self,
         registry: &'reg Registry,
         context: &'rc Context,
-        rc: &'rc mut RenderContext<'reg>,
+        rc: &mut RenderContext<'reg>,
     ) -> Result<(), RenderError>;
 }
 
@@ -593,15 +578,6 @@ impl Parameter {
                         Some(name.to_owned()),
                         ScopedJson::Derived(value.clone()),
                     ))
-                } else if let Some(block_context_value) = rc.evaluate_in_block_context(name)? {
-                    // try to evaluate using block context if any
-
-                    // we do clone for block context because it's from
-                    // render_context
-                    Ok(PathAndJson::new(
-                        Some(name.to_owned()),
-                        ScopedJson::Derived(block_context_value.clone()),
-                    ))
                 } else if let Some(rc_context) = rc.context() {
                     // the context is modified from a decorator
                     // use the modified one
@@ -610,16 +586,13 @@ impl Parameter {
                     // so we have to clone it to bypass lifetime check
                     Ok(PathAndJson::new(
                         Some(name.to_owned()),
-                        json.map(|j| ScopedJson::Derived(j.clone()))
-                            .unwrap_or_else(|| ScopedJson::Missing),
+                        ScopedJson::Derived(json.as_json().clone()),
                     ))
                 } else {
                     // failback to normal evaluation
                     Ok(PathAndJson::new(
                         Some(name.to_owned()),
-                        rc.evaluate(ctx, name)?
-                            .map(|json| ScopedJson::Context(json))
-                            .unwrap_or_else(|| ScopedJson::Missing),
+                        rc.evaluate(ctx, name)?,
                     ))
                 }
             }
diff --git a/src/value.rs b/src/value.rs
--- a/src/value.rs
+++ b/src/value.rs
@@ -20,7 +20,7 @@ pub enum ScopedJson<'reg: 'rc, 'rc> {
 impl<'reg: 'rc, 'rc> ScopedJson<'reg, 'rc> {
     /// get the JSON reference
     pub fn as_json(&self) -> &Json {
-        match *self {
+        match self {
             ScopedJson::Constant(j) => j,
             ScopedJson::Derived(ref j) => j,
             ScopedJson::Context(j) => j,
@@ -38,6 +38,11 @@ impl<'reg: 'rc, 'rc> ScopedJson<'reg, 'rc> {
             _ => false,
         }
     }
+
+    pub fn into_derived(self) -> ScopedJson<'reg, 'rc> {
+        let v = self.as_json();
+        ScopedJson::Derived(v.clone())
+    }
 }
 
 impl<'reg: 'rc, 'rc> From<Json> for ScopedJson<'reg, 'rc> {


Test patch:
diff --git a/tests/block_context.rs b/tests/block_context.rs
new file mode 100644
--- /dev/null
+++ b/tests/block_context.rs
@@ -0,0 +1,34 @@
+use handlebars::Handlebars;
+use serde_json::json;
+
+#[test]
+fn test_partial_with_blocks() {
+    let hbs = Handlebars::new();
+
+    let data = json!({
+        "a": [
+            {"b": 1},
+            {"b": 2},
+        ],
+    });
+
+    let template = "{{#*inline \"test\"}}{{b}};{{/inline}}{{#each a as |z|}}{{> test z}}{{/each}}";
+    assert_eq!(hbs.render_template(template, &data).unwrap(), "1;2;");
+}
+
+#[test]
+fn test_root_with_blocks() {
+    let hbs = Handlebars::new();
+
+    let data = json!({
+        "a": [
+            {"b": 1},
+            {"b": 2},
+        ],
+        "b": 3,
+    });
+
+    let template =
+        "{{#*inline \"test\"}}{{b}}:{{@root.b}};{{/inline}}{{#each a}}{{> test}}{{/each}}";
+    assert_eq!(hbs.render_template(template, &data).unwrap(), "1:3;2:3;");
+}