Closed sio4 closed 5 years ago
Hi @markbates, Thanks for a quick checking for this issue. but sadly, this patch does not solve the problem. I tried to find the point of the problem and I found the following point:
The diff below is not for buffalo
but for plush
since this issue problem is crossed across these two.
Please read the comments on the patch first.
--- a/compiler.go
+++ b/compiler.go
@@ -619,6 +619,13 @@ func (c *compiler) evalCallExpression(node *ast.CallExpression) (interface{}, er
}
}
+ // 3. finally it calls `partial()` but it runs on another context.
+ // can we pass `data` of current context as args?
+ if node.Function.String() == "partial" {
+ fmt.Println("XXX quick and dirty work around!")
+ args = args[0:1]
+ args = append(args, reflect.ValueOf(c.ctx.data))
+ }
res := rv.Call(args)
if len(res) > 0 {
if e, ok := res[len(res)-1].Interface().(error); ok {
@@ -634,6 +641,8 @@ func (c *compiler) evalForExpression(node *ast.ForExpression) (interface{}, erro
defer func() {
c.ctx = octx
}()
+ // 1. create new context for `for` block here.
+ // anyway this context does not have values form original.
c.ctx = octx.New()
iter, err := c.evalExpression(node.Iterable)
if err != nil {
@@ -661,8 +670,9 @@ func (c *compiler) evalForExpression(node *ast.ForExpression) (interface{}, erro
case reflect.Slice, reflect.Array:
for i := 0; i < riter.Len(); i++ {
v := riter.Index(i)
- c.ctx.Set(node.KeyName, i)
+ c.ctx.Set(node.KeyName, i) // misc: is it required?
c.ctx.Set(node.ValueName, v.Interface())
+ // 2. it calls evalBlockStatement after set new values
res, err := c.evalBlockStatement(node.Block)
if err != nil {
return nil, errors.WithStack(err)
The starting point of the problem is, as marked as 1
, the loop has its own context for isolated loop and at the point of 2
, it sets block values on its own context. After that, it calls evalBlockStatement()
from this newly generated and filled context.
In mark 3
, it calls wrapper function for partial()
but the function is not defined in this context. so it just uses parent's data
. As a quick and dirty fix for testing, I just re-generate the second argument for partial()
with compiler.ctx.data
and now it can handle values of loop.
Anyway, I think this will work for the partial but very tricky since there is no reason why plush
should know about the present of partial()
. Even though it is a family package of buffalo
which also has BuffaloRenderer()
. I think we need any different approach for this issue, for example start new renderer for loop inside...
Strange thing is, my old app has this "partial within a loop" structure but it works with buffalo 0.11.1.
Is there any good solution?
This patch fixed the code you gave me to reproduce the problem. What error are you getting with this fix?
Oh really? In my env., with buffalo development
pulled some hours ago and plush master
, the example of partial
inside of for
not works. (same error)
Then I will make test app for share tomorrow.
Hi @markbates,
I added and PRed test cases for this issue as #1411. I think these two test cases can be use for clearfing the issue. Can you confirm the test cases are written well? For now, those are failed with current development branch of buffalo
.
I also tried to improve quick patch for plush
as following. It does'n affect others but just modify second argument for partial()
. Anyway I don't think it is beautiful. :-)
Please check the test cases and patch, and consider some right direction for the improvement.
--- a/compiler.go
+++ b/compiler.go
@@ -504,6 +504,21 @@ func (c *compiler) evalCallExpression(node *ast.CallExpression) (interface{}, er
return nil, errors.WithStack(err)
}
+ // 3. if the function is `partial` and user provides second argument,
+ // merge it with current context's data which has loop var.
+ if node.Function.String() == "partial" && pos == 1 {
+ fmt.Printf("XXX - %T, %v\n", v, v)
+ data := map[string]interface{}{}
+ for k, vv := range c.ctx.data {
+ data[k] = vv
+ }
+ for k, vv := range v.(map[string]interface{}) {
+ data[k] = vv
+ }
+ v = data
+ fmt.Printf("XXX - %T, %v\n", v, v)
+ }
+
var ar reflect.Value
expectedT := rt.In(pos)
if v != nil {
@@ -520,6 +535,12 @@ func (c *compiler) evalCallExpression(node *ast.CallExpression) (interface{}, er
args = append(args, ar)
}
+ // 3.1. if the function is `partial` but there is no second argument,
+ // just use current context's data as second argument.
+ if node.Function.String() == "partial" && len(node.Arguments) == 1 {
+ args = append(args, reflect.ValueOf(c.ctx.data))
+ }
+
hc := func(arg reflect.Type) {
if arg.ConvertibleTo(reflect.TypeOf(HelperContext{})) {
hargs := HelperContext{
@@ -619,6 +640,7 @@ func (c *compiler) evalCallExpression(node *ast.CallExpression) (interface{}, er
}
}
+ // 4. finally it calls `partial()` but it runs on another context.
res := rv.Call(args)
if len(res) > 0 {
if e, ok := res[len(res)-1].Interface().(error); ok {
@@ -634,6 +656,8 @@ func (c *compiler) evalForExpression(node *ast.ForExpression) (interface{}, erro
defer func() {
c.ctx = octx
}()
+ // 1. create new context for `for` block here.
+ // anyway this context does not have values form original.
c.ctx = octx.New()
iter, err := c.evalExpression(node.Iterable)
if err != nil {
@@ -661,8 +685,9 @@ func (c *compiler) evalForExpression(node *ast.ForExpression) (interface{}, erro
case reflect.Slice, reflect.Array:
for i := 0; i < riter.Len(); i++ {
v := riter.Index(i)
- c.ctx.Set(node.KeyName, i)
+ c.ctx.Set(node.KeyName, i) // misc: is it required?
c.ctx.Set(node.ValueName, v.Interface())
+ // 2. it calls evalBlockStatement after set new values
res, err := c.evalBlockStatement(node.Block)
if err != nil {
return nil, errors.WithStack(err)
Oh, maybe I found the beginning of the issue. Previously, plush
exports its data
as a missing argument of function if it meets argument type Data
but it removed. (yes, this part is also tricky)
I tried to start new Render(string, new_context)
for the whole block of for
loop but the original template already parsed and I cannot found the way to generate original template block from statements automatically. (Anyway, it will not help because of...)
Currently partial()
uses templateRenderer
, its holder's local variable data
and it cannot access newly filled ctx
of compiler
. I think partial()
should be moved to plush
but I also understand it also somewhat bad approach since plush
only handle string
as input but partial
need to access filesystem.
I think the patch above for plush
is reasonable for now.
closed by #1411
When I tried to migrate my old app into the current version of buffalo, I found the following problem.
I guess context not passed to
partial
when it called withinfor
loop.Description
When I tried to use
partial()
withinfor
loop, it cannot find the parent's context variables. It did just work fine with 0.11.0 (as I remember) but now it does not work.Steps to Reproduce the Problem
parent template:
a partial template named
_row.html
Error message:
Additionally,
<% let user = "something" %>
, it passed well to partial but if I uselet
inside of the loop, it is also not working.form_for
inside of loop,f
also not passed into_form
partial.Expected Behavior
Render content with the parent's value.
Actual Behavior
make error: unknown identifier
Info