diff --git a/CHANGELOG b/CHANGELOG
index ee07fd909..bd327f589 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -19,7 +19,9 @@
* 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 bd00a356d..0632ba747 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 da3ef4a92..3adfe8aea 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): void
{
$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 4ebbc146c..147acc331 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): void
{
$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 2835393c6..876ef2efa 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 4efa4dd34..93eef125f 100644
--- a/src/Node/ModuleNode.php
+++ b/src/Node/ModuleNode.php
@@ -160,7 +160,8 @@ final class ModuleNode extends Node
->raw(" extends Template\n")
->write("{\n")
->indent()
- ->write("private \$source;\n\n")
+ ->write("private \$source;\n")
+ ->write("private \$macros = [];\n\n")
;
}
@@ -304,6 +305,7 @@ final 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 7005eef4d..aaa6af6f4 100644
--- a/src/Parser.php
+++ b/src/Parser.php
@@ -266,19 +266,8 @@ class Parser
public function getImportedSymbol(string $type, string $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(): bool
diff --git a/src/TokenParser/FromTokenParser.php b/src/TokenParser/FromTokenParser.php
index 2ec8c1466..c1d1f4cba 100644
--- a/src/TokenParser/FromTokenParser.php
+++ b/src/TokenParser/FromTokenParser.php
@@ -48,7 +48,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 6be1fa8ae..ca18193db 100644
--- a/src/TokenParser/ImportTokenParser.php
+++ b/src/TokenParser/ImportTokenParser.php
@@ -32,7 +32,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(): string
diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test
new file mode 100644
index 000000000..f06c31c93
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test
@@ -0,0 +1,21 @@
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% from _self import input %}
+
+{% embed 'embed' %}
+ {% block foo %}
+ {{ input("username") }}
+ {% endblock %}
+{% endembed %}
+
+{% macro input(name) -%}
+
+{% endmacro %}
+--TEMPLATE(embed)--
+ {% block foo %}
+ {% endblock %}
+--DATA--
+return []
+--EXCEPTION--
+Twig\Error\SyntaxError: Unknown "input" function in "index.twig" at line 6.
diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test b/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test
new file mode 100644
index 000000000..27bfbaee1
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test
@@ -0,0 +1,28 @@
+--TEST--
+"macro" tag
+--TEMPLATE--
+{%- from _self import input %}
+
+{% block foo %}
+ {%- from "macros" import input %}
+ {{- input('username') }}
+{% endblock %}
+
+{% block bar %}
+ {{- input('username') }}
+{% endblock %}
+
+{% macro input(name) -%}
+
+{% endmacro %}
+--TEMPLATE(macros)--
+{% macro input(name) %}
+
+{% endmacro %}
+--DATA--
+return []
+--EXPECT--
+
+
+
+
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
new file mode 100644
index 000000000..3609881f0
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test
@@ -0,0 +1,21 @@
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% import _self as macros %}
+
+{% embed 'embed' %}
+ {% block foo %}
+ {{ macros.input("username") }}
+ {% endblock %}
+{% endembed %}
+
+{% macro input(name) -%}
+
+{% endmacro %}
+--TEMPLATE(embed)--
+ {% block foo %}
+ {% endblock %}
+--DATA--
+return []
+--EXCEPTION--
+Twig\Error\RuntimeError: Variable "macros" does not exist in "index.twig" at line 6.
diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test b/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test
new file mode 100644
index 000000000..7cf0552f8
--- /dev/null
+++ b/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test
@@ -0,0 +1,28 @@
+--TEST--
+"macro" tag
+--TEMPLATE--
+{%- import _self as macros %}
+
+{% block foo %}
+ {%- import "macros" as macros %}
+ {{- macros.input('username') }}
+{% endblock %}
+
+{% block bar %}
+ {{- macros.input('username') }}
+{% endblock %}
+
+{% macro input(name) -%}
+
+{% endmacro %}
+--TEMPLATE(macros)--
+{% macro input(name) %}
+
+{% endmacro %}
+--DATA--
+return []
+--EXPECT--
+
+
+
+
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 d9d1ecf62..7a5ae1636 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 Template
{
private \$source;
+ private \$macros = [];
public function __construct(Environment \$env)
{
@@ -89,6 +90,7 @@ class __TwigTemplate_%x extends Template
protected function doDisplay(array \$context, array \$blocks = [])
{
+ \$macros = \$this->macros;
// line 1
echo "foo";
}
@@ -100,7 +102,7 @@ class __TwigTemplate_%x extends 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 Template
{
private \$source;
+ private \$macros = [];
public function __construct(Environment \$env)
{
@@ -155,8 +158,9 @@ class __TwigTemplate_%x extends 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 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 Template
{
private \$source;
+ private \$macros = [];
public function __construct(Environment \$env)
{
@@ -234,6 +239,7 @@ class __TwigTemplate_%x extends 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 Template
public function getDebugInfo()
{
- return array ( 41 => 2, 39 => 4, 33 => 2,);
+ return array ( 43 => 2, 41 => 4, 34 => 2,);
}
public function getSourceContext()