diff --git a/CHANGELOG b/CHANGELOG
index b45c64596..5137c3904 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,8 @@
* 2.11.0 (2019-XX-XX)
- * n/a
+ * fixed macros "import" when using the same name in the parent and child templates
+ * fixed recursive macros
+ * macros imported "globally" in a template are now available in macros without re-importing them
* 2.10.0 (2019-05-14)
diff --git a/src/Node/BlockNode.php b/src/Node/BlockNode.php
index cafceb95b..4da6e6ff7 100644
--- a/src/Node/BlockNode.php
+++ b/src/Node/BlockNode.php
@@ -32,6 +32,7 @@ class BlockNode extends Node
->addDebugInfo($this)
->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n")
->indent()
+ ->write("\$macros = \$this->macros;\n")
;
$compiler
diff --git a/src/Node/Expression/MethodCallExpression.php b/src/Node/Expression/MethodCallExpression.php
index 7450c5180..1ad50e607 100644
--- a/src/Node/Expression/MethodCallExpression.php
+++ b/src/Node/Expression/MethodCallExpression.php
@@ -27,8 +27,9 @@ class MethodCallExpression extends AbstractExpression
public function compile(Compiler $compiler)
{
$compiler
- ->subcompile($this->getNode('node'))
- ->raw('->')
+ ->raw('$macros[')
+ ->repr($this->getNode('node')->getAttribute('name'))
+ ->raw(']->')
->raw($this->getAttribute('method'))
->raw('(')
;
diff --git a/src/Node/ImportNode.php b/src/Node/ImportNode.php
index e66954bf9..94f35b6c0 100644
--- a/src/Node/ImportNode.php
+++ b/src/Node/ImportNode.php
@@ -22,19 +22,27 @@ use Twig\Node\Expression\NameExpression;
*/
class ImportNode extends Node
{
- public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null)
+ public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true)
{
- parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno, $tag);
+ parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag);
}
public function compile(Compiler $compiler)
{
$compiler
->addDebugInfo($this)
- ->write('')
- ->subcompile($this->getNode('var'))
- ->raw(' = ')
+ ->write('$macros[')
+ ->repr($this->getNode('var')->getAttribute('name'))
+ ->raw('] = ')
;
+
+ if ($this->getAttribute('global')) {
+ $compiler
+ ->raw('$this->macros[')
+ ->repr($this->getNode('var')->getAttribute('name'))
+ ->raw('] = ')
+ ;
+ }
if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) {
$compiler->raw('$this');
diff --git a/src/Node/MacroNode.php b/src/Node/MacroNode.php
index 8e927a51d..6bb190937 100644
--- a/src/Node/MacroNode.php
+++ b/src/Node/MacroNode.php
@@ -63,9 +63,7 @@ class MacroNode extends Node
->raw(")\n")
->write("{\n")
->indent()
- ;
-
- $compiler
+ ->write("\$macros = \$this->macros;\n")
->write("\$context = \$this->env->mergeGlobals([\n")
->indent()
;
diff --git a/src/Node/ModuleNode.php b/src/Node/ModuleNode.php
index 745ec3a39..b23a342e8 100644
--- a/src/Node/ModuleNode.php
+++ b/src/Node/ModuleNode.php
@@ -166,7 +166,8 @@ class ModuleNode extends Node
->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass(false)))
->write("{\n")
->indent()
- ->write("private \$source;\n\n")
+ ->write("private \$source;\n")
+ ->write("private \$macros = [];\n\n")
;
}
@@ -310,6 +311,7 @@ class ModuleNode extends Node
$compiler
->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n")
->indent()
+ ->write("\$macros = \$this->macros;\n")
->subcompile($this->getNode('display_start'))
->subcompile($this->getNode('body'))
;
diff --git a/src/Parser.php b/src/Parser.php
index 3265d73a7..8a937e329 100644
--- a/src/Parser.php
+++ b/src/Parser.php
@@ -279,19 +279,8 @@ class Parser
public function getImportedSymbol($type, $alias)
{
- if (null !== $this->peekBlockStack()) {
- foreach ($this->importedSymbols as $functions) {
- if (isset($functions[$type][$alias])) {
- if (count($this->blockStack) > 1) {
- return null;
- }
-
- return $functions[$type][$alias];
- }
- }
- } else {
- return $this->importedSymbols[0][$type][$alias] ?? null;
- }
+ // if the symbol does not exist in the current scope (0), try in the main/global scope (last index)
+ return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null);
}
public function isMainScope()
diff --git a/src/TokenParser/FromTokenParser.php b/src/TokenParser/FromTokenParser.php
index fd12126a2..dd49f2fd3 100644
--- a/src/TokenParser/FromTokenParser.php
+++ b/src/TokenParser/FromTokenParser.php
@@ -47,7 +47,7 @@ final class FromTokenParser extends AbstractTokenParser
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$var = new AssignNameExpression($this->parser->getVarName(), $token->getLine());
- $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag());
+ $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope());
foreach ($targets as $name => $alias) {
$this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var);
diff --git a/src/TokenParser/ImportTokenParser.php b/src/TokenParser/ImportTokenParser.php
index 984fb43ba..b5674c199 100644
--- a/src/TokenParser/ImportTokenParser.php
+++ b/src/TokenParser/ImportTokenParser.php
@@ -31,7 +31,7 @@ final class ImportTokenParser extends AbstractTokenParser
$this->parser->addImportedSymbol('template', $var->getAttribute('name'));
- return new ImportNode($macro, $var, $token->getLine(), $this->getTag());
+ return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope());
}
public function getTag()
diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test b/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test
index 87ac25c31..168c9b3a9 100644
--- a/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test
+++ b/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test
@@ -14,5 +14,5 @@
{% endmacro %}
--DATA--
return []
---EXCEPTION--
-Twig\Error\SyntaxError: Unknown "another" function in "index.twig" at line 7.
+--EXPECT--
+OK
diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test
index f737bf0d8..384b02d8f 100644
--- a/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test
+++ b/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test
@@ -14,5 +14,5 @@
{% endmacro %}
--DATA--
return []
---EXCEPTION--
-Twig\Error\SyntaxError: Unknown "input" function in "index.twig" at line 6.
+--EXPECT--
+
diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test b/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test
new file mode 100644
index 000000000..09a29839d
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test
@@ -0,0 +1,27 @@
+--TEST--
+"import" tag
+--TEMPLATE--
+{% from _self import recursive_macro %}
+
+{{ recursive_macro(10) }}
+
+{% macro recursive_macro(n) %}
+ {% if n > 0 %}
+ {{- recursive_macro(n - 1) -}}
+ {% endif %}
+ {{- n }}
+{% endmacro %}
+--DATA--
+return []
+--EXPECT--
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test
index 37d2aa203..3609881f0 100644
--- a/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test
+++ b/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test
@@ -18,4 +18,4 @@
--DATA--
return []
--EXCEPTION--
-Twig\Error\RuntimeError: Accessing \Twig\Template attributes is forbidden in "index.twig" at line 6.
+Twig\Error\RuntimeError: Variable "macros" does not exist in "index.twig" at line 6.
diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test b/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test
index d224482e1..1851f0974 100644
--- a/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test
+++ b/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test
@@ -14,5 +14,5 @@
{% endmacro %}
--DATA--
return []
---EXCEPTION--
-Twig\Error\RuntimeError: Variable "foo" does not exist in "index.twig" at line 7.
+--EXPECT--
+OK
diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test
index 35906f851..821f64bf7 100644
--- a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test
+++ b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test
@@ -15,4 +15,4 @@
--DATA--
return []
--EXCEPTION--
-Twig\Error\RuntimeError: Accessing \Twig\Template attributes is forbidden in "index.twig" at line 6.
+Twig\Error\RuntimeError: Variable "lmacros" does not exist in "index.twig" at line 6.
diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test
index 1f0e74ccd..697d665f8 100644
--- a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test
+++ b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test
@@ -14,5 +14,5 @@
{% endmacro %}
--DATA--
return []
---EXCEPTION--
-Twig\Error\RuntimeError: Accessing \Twig\Template attributes is forbidden in "index.twig" at line 6.
+--EXPECT--
+
diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test b/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test
new file mode 100644
index 000000000..8d9b3caa4
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test
@@ -0,0 +1,30 @@
+--TEST--
+"import" tag
+--TEMPLATE--
+{% extends "parent" %}
+
+{% macro anotherThing() -%}
+ Do it too
+{% endmacro %}
+
+{% import _self as macros %}
+{% block content %}
+ {{ parent() }}
+ {{ macros.anotherThing() }}
+{% endblock %}
+--TEMPLATE(parent)--
+{% macro thing() %}
+ Do it
+{% endmacro %}
+
+{% import _self as macros %}
+{% block content %}
+ {{ macros.thing() }}
+{% endblock %}
+--DATA--
+return []
+--EXPECT--
+Do it
+
+
+ Do it too
diff --git a/test/Twig/Tests/Node/BlockTest.php b/test/Twig/Tests/Node/BlockTest.php
index 8b4a1eca4..c91a4d0f7 100644
--- a/test/Twig/Tests/Node/BlockTest.php
+++ b/test/Twig/Tests/Node/BlockTest.php
@@ -34,6 +34,7 @@ class Twig_Tests_Node_BlockTest extends NodeTestCase
// line 1
public function block_foo(\$context, array \$blocks = [])
{
+ \$macros = \$this->macros;
echo "foo";
}
EOF
diff --git a/test/Twig/Tests/Node/ImportTest.php b/test/Twig/Tests/Node/ImportTest.php
index 2118bff59..f811bfb4c 100644
--- a/test/Twig/Tests/Node/ImportTest.php
+++ b/test/Twig/Tests/Node/ImportTest.php
@@ -36,7 +36,7 @@ class Twig_Tests_Node_ImportTest extends NodeTestCase
$tests[] = [$node, <<loadTemplate("foo.twig", null, 1)->unwrap();
+\$macros["macro"] = \$this->macros["macro"] = \$this->loadTemplate("foo.twig", null, 1)->unwrap();
EOF
];
diff --git a/test/Twig/Tests/Node/MacroTest.php b/test/Twig/Tests/Node/MacroTest.php
index 1dda543cd..afa68adf4 100644
--- a/test/Twig/Tests/Node/MacroTest.php
+++ b/test/Twig/Tests/Node/MacroTest.php
@@ -43,6 +43,7 @@ class Twig_Tests_Node_MacroTest extends NodeTestCase
// line 1
public function macro_foo(\$__foo__ = null, \$__bar__ = "Foo", ...\$__varargs__)
{
+ \$macros = \$this->macros;
\$context = \$this->env->mergeGlobals([
"foo" => \$__foo__,
"bar" => \$__bar__,
diff --git a/test/Twig/Tests/Node/ModuleTest.php b/test/Twig/Tests/Node/ModuleTest.php
index 1e2d2b640..812b1ef0b 100644
--- a/test/Twig/Tests/Node/ModuleTest.php
+++ b/test/Twig/Tests/Node/ModuleTest.php
@@ -74,6 +74,7 @@ use Twig\Template;
class __TwigTemplate_%x extends \Twig\Template
{
private \$source;
+ private \$macros = [];
public function __construct(Environment \$env)
{
@@ -89,6 +90,7 @@ class __TwigTemplate_%x extends \Twig\Template
protected function doDisplay(array \$context, array \$blocks = [])
{
+ \$macros = \$this->macros;
// line 1
echo "foo";
}
@@ -100,7 +102,7 @@ class __TwigTemplate_%x extends \Twig\Template
public function getDebugInfo()
{
- return array ( 35 => 1,);
+ return array ( 37 => 1,);
}
public function getSourceContext()
@@ -136,6 +138,7 @@ use Twig\Template;
class __TwigTemplate_%x extends \Twig\Template
{
private \$source;
+ private \$macros = [];
public function __construct(Environment \$env)
{
@@ -155,8 +158,9 @@ class __TwigTemplate_%x extends \Twig\Template
protected function doDisplay(array \$context, array \$blocks = [])
{
+ \$macros = \$this->macros;
// line 2
- \$context["macro"] = \$this->loadTemplate("foo.twig", "foo.twig", 2)->unwrap();
+ \$macros["macro"] = \$this->macros["macro"] = \$this->loadTemplate("foo.twig", "foo.twig", 2)->unwrap();
// line 1
\$this->parent = \$this->loadTemplate("layout.twig", "foo.twig", 1);
\$this->parent->display(\$context, array_merge(\$this->blocks, \$blocks));
@@ -174,7 +178,7 @@ class __TwigTemplate_%x extends \Twig\Template
public function getDebugInfo()
{
- return array ( 41 => 1, 39 => 2, 33 => 1,);
+ return array ( 43 => 1, 41 => 2, 34 => 1,);
}
public function getSourceContext()
@@ -215,6 +219,7 @@ use Twig\Template;
class __TwigTemplate_%x extends \Twig\Template
{
private \$source;
+ private \$macros = [];
public function __construct(Environment \$env)
{
@@ -234,6 +239,7 @@ class __TwigTemplate_%x extends \Twig\Template
protected function doDisplay(array \$context, array \$blocks = [])
{
+ \$macros = \$this->macros;
// line 4
\$context["foo"] = "foo";
// line 2
@@ -252,7 +258,7 @@ class __TwigTemplate_%x extends \Twig\Template
public function getDebugInfo()
{
- return array ( 41 => 2, 39 => 4, 33 => 2,);
+ return array ( 43 => 2, 41 => 4, 34 => 2,);
}
public function getSourceContext()