diff --git a/doc/coding_standards.rst b/doc/coding_standards.rst index 310cc8c43..d10906fa6 100644 --- a/doc/coding_standards.rst +++ b/doc/coding_standards.rst @@ -40,8 +40,8 @@ standards: {{ foo ~ bar }} {{ true ? true : false }} -* Put exactly one space after the ``:`` sign in hashes and ``,`` in - arrays and hashes: +* Put exactly one space after the ``:`` sign in mappings and ``,`` in sequences + and mappings: .. code-block:: twig @@ -80,8 +80,8 @@ standards: {{ foo|default('foo') }} {{ range(1..10) }} -* Do not put any spaces before and after the opening and the closing of arrays - and hashes: +* Do not put any spaces before and after the opening and the closing of + sequences and mappings: .. code-block:: twig diff --git a/doc/filters/data_uri.rst b/doc/filters/data_uri.rst index e008266b3..131a71bb8 100644 --- a/doc/filters/data_uri.rst +++ b/doc/filters/data_uri.rst @@ -50,6 +50,6 @@ Arguments --------- * ``mime``: The mime type -* ``parameters``: An array of parameters +* ``parameters``: A mapping of parameters .. _RFC 2397: https://tools.ietf.org/html/rfc2397 diff --git a/doc/filters/keys.rst b/doc/filters/keys.rst index 6bed8291e..26c61bcd1 100644 --- a/doc/filters/keys.rst +++ b/doc/filters/keys.rst @@ -1,14 +1,20 @@ ``keys`` ======== -The ``keys`` filter returns the keys of an array. It is useful when you want to -iterate over the keys of an array: +The ``keys`` filter returns the keys of a sequence or a mapping. It is useful +when you want to iterate over the keys of a sequence or a mapping: .. code-block:: twig - {% for key in array|keys %} - ... + {% for key in [1, 2, 3, 4]|keys %} + {{ key }} {% endfor %} + {# outputs: 1 2 3 4 #} + + {% for key in {a: 'a_value', b: 'b_value'}|keys %} + {{ key }} + {% endfor %} + {# outputs: a b #} .. note:: diff --git a/doc/filters/merge.rst b/doc/filters/merge.rst index b1d75c40b..d0b302c8f 100644 --- a/doc/filters/merge.rst +++ b/doc/filters/merge.rst @@ -1,7 +1,9 @@ ``merge`` ========= -The ``merge`` filter merges an array with another array: +The ``merge`` filter merges sequences and mappings: + +For sequences, new values are added at the end of the existing ones: .. code-block:: twig @@ -11,9 +13,9 @@ The ``merge`` filter merges an array with another array: {# values now contains [1, 2, 'apple', 'orange'] #} -New values are added at the end of the existing ones. - -The ``merge`` filter also works on hashes: +For mappings, the merging process occurs on the keys; if the key does not +already exist, it is added but if the key already exists, its value is +overridden: .. code-block:: twig @@ -23,13 +25,9 @@ The ``merge`` filter also works on hashes: {# items now contains {'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car'} #} -For hashes, the merging process occurs on the keys: if the key does not -already exist, it is added but if the key already exists, its value is -overridden. - .. tip:: - If you want to ensure that some values are defined in an array (by given + If you want to ensure that some values are defined in a mapping (by given default values), reverse the two elements in the call: .. code-block:: twig diff --git a/doc/filters/replace.rst b/doc/filters/replace.rst index 0c38b73bf..63e7f4800 100644 --- a/doc/filters/replace.rst +++ b/doc/filters/replace.rst @@ -17,7 +17,7 @@ format is free-form): Arguments --------- -* ``from``: The placeholder values as a hash +* ``from``: The placeholder values as a mapping .. seealso:: diff --git a/doc/filters/sort.rst b/doc/filters/sort.rst index 98b7afe37..4afd60e7d 100644 --- a/doc/filters/sort.rst +++ b/doc/filters/sort.rst @@ -1,7 +1,7 @@ ``sort`` ======== -The ``sort`` filter sorts an array: +The ``sort`` filter sorts sequences and mappings: .. code-block:: twig @@ -15,7 +15,7 @@ The ``sort`` filter sorts an array: association. It supports Traversable objects by transforming those to arrays. -You can pass an arrow function to sort the array: +You can pass an arrow function to configure the sorting: .. code-block:: html+twig diff --git a/doc/filters/split.rst b/doc/filters/split.rst index 386ae3043..f6728ddad 100644 --- a/doc/filters/split.rst +++ b/doc/filters/split.rst @@ -11,7 +11,7 @@ of strings: You can also pass a ``limit`` argument: -* If ``limit`` is positive, the returned array will contain a maximum of +* If ``limit`` is positive, the returned sequence will contain a maximum of limit elements with the last element containing the rest of string; * If ``limit`` is negative, all components except the last -limit are diff --git a/doc/filters/url_encode.rst b/doc/filters/url_encode.rst index c5919be01..f78983134 100644 --- a/doc/filters/url_encode.rst +++ b/doc/filters/url_encode.rst @@ -1,8 +1,8 @@ ``url_encode`` ============== -The ``url_encode`` filter percent encodes a given string as URL segment -or an array as query string: +The ``url_encode`` filter percent encodes a given string as URL segment or a +mapping as query string: .. code-block:: twig diff --git a/doc/functions/cycle.rst b/doc/functions/cycle.rst index 46f8f21ca..3b6db61c4 100644 --- a/doc/functions/cycle.rst +++ b/doc/functions/cycle.rst @@ -1,7 +1,7 @@ ``cycle`` ========= -The ``cycle`` function cycles on an array of values: +The ``cycle`` function cycles on a sequence or mapping: .. code-block:: twig diff --git a/doc/tags/for.rst b/doc/tags/for.rst index 4517f5907..3bd859bd0 100644 --- a/doc/tags/for.rst +++ b/doc/tags/for.rst @@ -1,8 +1,8 @@ ``for`` ======= -Loop over each item in a sequence. For example, to display a list of users -provided in a variable called ``users``: +Loop over each item in a sequence or a mapping. For example, to display a list +of users provided in a variable called ``users``: .. code-block:: html+twig @@ -15,8 +15,8 @@ provided in a variable called ``users``: .. note:: - A sequence can be either an array or an object implementing the - ``Traversable`` interface. + A sequence or a mapping can be either an array or an object implementing + the ``Traversable`` interface. If you do need to iterate over a sequence of numbers, you can use the ``..`` operator: diff --git a/doc/tags/if.rst b/doc/tags/if.rst index 0523cb1ac..8a29af5da 100644 --- a/doc/tags/if.rst +++ b/doc/tags/if.rst @@ -12,7 +12,7 @@ In the simplest form you can use it to test if an expression evaluates to
Our website is in maintenance mode. Please, come back later.
{% endif %} -You can also test if an array is not empty: +You can also test if a sequence or a mapping is not empty: .. code-block:: html+twig @@ -71,8 +71,10 @@ use more complex ``expressions`` there too: INF (Infinity) true whitespace-only string true string "0" or '0' false - empty array false + empty sequence false + empty mapping false null false - non-empty array true + non-empty sequence true + non-empty mapping true object true ====================== ==================== diff --git a/doc/tags/with.rst b/doc/tags/with.rst index 420c82ac6..268bb373d 100644 --- a/doc/tags/with.rst +++ b/doc/tags/with.rst @@ -13,8 +13,8 @@ scope are not visible outside of the scope: foo is not visible here any longer Instead of defining variables at the beginning of the scope, you can pass a -hash of variables you want to define in the ``with`` tag; the previous example -is equivalent to the following one: +mapping of variables you want to define in the ``with`` tag; the previous +example is equivalent to the following one: .. code-block:: twig @@ -23,7 +23,7 @@ is equivalent to the following one: {% endwith %} foo is not visible here any longer - {# it works with any expression that resolves to a hash #} + {# it works with any expression that resolves to a mapping #} {% set vars = {foo: 42} %} {% with vars %} ... diff --git a/doc/templates.rst b/doc/templates.rst index 1026dd8ea..c7e2e3130 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -102,7 +102,7 @@ If a variable or attribute does not exist, the behavior depends on the For convenience's sake ``foo.bar`` does the following things on the PHP layer: - * check if ``foo`` is an array and ``bar`` a valid element; + * check if ``foo`` is a sequence or a mapping and ``bar`` a valid element; * if not, and if ``foo`` is an object, check that ``bar`` is a valid property; * if not, and if ``foo`` is an object, check that ``bar`` is a valid method (even if ``bar`` is the constructor - use ``__construct()`` instead); @@ -115,7 +115,7 @@ If a variable or attribute does not exist, the behavior depends on the Twig also supports a specific syntax for accessing items on PHP arrays, ``foo['bar']``: - * check if ``foo`` is an array and ``bar`` a valid element; + * check if ``foo`` is a sequence or a mapping and ``bar`` a valid element; * if not, and if ``strict_variables`` is ``false``, return ``null``; * if not, throw an exception. @@ -531,10 +531,10 @@ exist: writing the number down. If a dot is present the number is a float, otherwise an integer. -* ``["foo", "bar"]``: Arrays are defined by a sequence of expressions +* ``["foo", "bar"]``: Sequences are defined by a sequence of expressions separated by a comma (``,``) and wrapped with squared brackets (``[]``). -* ``{"foo": "bar"}``: Hashes are defined by a list of keys and values +* ``{"foo": "bar"}``: Mappings are defined by a list of keys and values separated by a comma (``,``) and wrapped with curly braces (``{}``): .. code-block:: twig @@ -542,7 +542,7 @@ exist: {# keys as string #} {'foo': 'foo', 'bar': 'bar'} - {# keys as names (equivalent to the previous hash) #} + {# keys as names (equivalent to the previous mapping) #} {foo: 'foo', bar: 'bar'} {# keys as integer #} @@ -563,7 +563,7 @@ exist: * ``null``: ``null`` represents no specific value. This is the value returned when a variable does not exist. ``none`` is an alias for ``null``. -Arrays and hashes can be nested: +Sequences and mappings can be nested: .. code-block:: twig @@ -698,8 +698,8 @@ operand is contained in the right: .. tip:: - You can use this filter to perform a containment test on strings, arrays, - or objects implementing the ``Traversable`` interface. + You can use this filter to perform a containment test on strings, + sequences, mappings, or objects implementing the ``Traversable`` interface. To perform a negative test, use the ``not in`` operator: @@ -785,8 +785,8 @@ The following operators don't fit into any of the other categories: {# returns the value of foo if it is defined and not null, 'no' otherwise #} {{ foo ?? 'no' }} -* ``...``: The spread operator can be used to expand arrays or hashes (it cannot - be used to expand the arguments of a function call): +* ``...``: The spread operator can be used to expand sequences or mappings (it + cannot be used to expand the arguments of a function call): .. code-block:: twig @@ -819,7 +819,7 @@ Operator Score of precedence Description ``ends with``, ``has some``, ``has every`` ``..`` 25 Range of values -``+``, ``-`` 30 Addition and substraction on numbers +``+``, ``-`` 30 Addition and subtraction on numbers ``~`` 40 String concatenation ``not`` 50 Negates a statement ``*``, ``/``, ``//``, ``%`` 60 Arithmetic operations on numbers @@ -827,7 +827,7 @@ Operator Score of precedence Description ``**`` 200 Raises a number to the power of another ``??`` 300 Default value when a variable is null ``+``, ``-`` 500 Unary operations on numbers -``|``,``[]``,``.`` - Filters, array, hash, and attribute access +``|``,``[]``,``.`` - Filters, sequence, mapping, and attribute access ============================= =================================== ===================================================== Without using any parentheses, the operator precedence rules are used to diff --git a/doc/tests/empty.rst b/doc/tests/empty.rst index 0233eca48..3abdb8bbd 100644 --- a/doc/tests/empty.rst +++ b/doc/tests/empty.rst @@ -1,8 +1,8 @@ ``empty`` ========= -``empty`` checks if a variable is an empty string, an empty array, an empty -hash, exactly ``false``, or exactly ``null``. +``empty`` checks if a variable is an empty string, an empty sequence, an empty +mapping, exactly ``false``, or exactly ``null``. For objects that implement the ``Countable`` interface, ``empty`` will check the return value of the ``count()`` method. diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 80d2b2a3f..76c89e493 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -278,9 +278,9 @@ class ExpressionParser // no break default: if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) { - $node = $this->parseArrayExpression(); + $node = $this->parseSequenceExpression(); } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) { - $node = $this->parseHashExpression(); + $node = $this->parseMappingExpression(); } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); } else { @@ -320,15 +320,22 @@ class ExpressionParser } public function parseArrayExpression() + { + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__); + + return $this->parseSequenceExpression(); + } + + public function parseSequenceExpression() { $stream = $this->parser->getStream(); - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'A sequence element was expected'); $node = new ArrayExpression([], $stream->getCurrent()->getLine()); $first = true; while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { if (!$first) { - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A sequence element must be followed by a comma'); // trailing ,? if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { @@ -346,21 +353,31 @@ class ExpressionParser $node->addElement($this->parseExpression()); } } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened sequence is not properly closed'); return $node; } + /** + * @deprecated since 3.11, use parseMappingExpression() instead + */ public function parseHashExpression() + { + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__); + + return $this->parseMappingExpression(); + } + + public function parseMappingExpression() { $stream = $this->parser->getStream(); - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A mapping element was expected'); $node = new ArrayExpression([], $stream->getCurrent()->getLine()); $first = true; while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { if (!$first) { - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A mapping value must be followed by a comma'); // trailing ,? if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { @@ -377,7 +394,7 @@ class ExpressionParser continue; } - // a hash key can be: + // a mapping key can be: // // * a number -- 12 // * a string -- 'a' @@ -399,15 +416,15 @@ class ExpressionParser } else { $current = $stream->getCurrent(); - throw new SyntaxError(\sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A mapping key must be followed by a colon (:)'); $value = $this->parseExpression(); $node->addElement($value, $key); } - $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened mapping is not properly closed'); return $node; } @@ -631,7 +648,7 @@ class ExpressionParser $value = $this->parsePrimaryExpression(); if (!$this->checkConstantExpression($value)) { - throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext()); + throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext()); } } else { $value = $this->parseExpression(0, $allowArrow); diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 06dea7a6f..6532ea1fb 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -329,7 +329,7 @@ final class CoreExtension extends AbstractExtension } if (!\count($values)) { - throw new RuntimeError('The "cycle" function does not work on empty arrays.'); + throw new RuntimeError('The "cycle" function does not work on empty sequences/mappings.'); } return $values[$position % \count($values)]; @@ -399,7 +399,7 @@ final class CoreExtension extends AbstractExtension $values = self::toArray($values); if (0 === \count($values)) { - throw new RuntimeError('The random function cannot pick from an empty array.'); + throw new RuntimeError('The random function cannot pick from an empty sequence/mapping.'); } return $values[array_rand($values, 1)]; @@ -532,7 +532,7 @@ final class CoreExtension extends AbstractExtension public static function replace($str, $from): string { if (!is_iterable($from)) { - throw new RuntimeError(\sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', get_debug_type($from))); + throw new RuntimeError(\sprintf('The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "%s".', get_debug_type($from))); } return strtr($str ?? '', self::toArray($from)); @@ -629,7 +629,7 @@ final class CoreExtension extends AbstractExtension foreach ($arrays as $argNumber => $array) { if (!is_iterable($array)) { - throw new RuntimeError(\sprintf('The merge filter only works with arrays or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); + throw new RuntimeError(\sprintf('The merge filter only works with sequences/mappings or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); } $result = array_merge($result, self::toArray($array)); @@ -903,7 +903,7 @@ final class CoreExtension extends AbstractExtension if ($array instanceof \Traversable) { $array = iterator_to_array($array); } elseif (!\is_array($array)) { - throw new RuntimeError(\sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); + throw new RuntimeError(\sprintf('The sort filter only works with sequences/mappings or "Traversable", got "%s".', \gettype($array))); } if (null !== $arrow) { @@ -1416,7 +1416,7 @@ final class CoreExtension extends AbstractExtension public static function batch($items, $size, $fill = null, $preserveKeys = true): array { if (!is_iterable($items)) { - throw new RuntimeError(\sprintf('The "batch" filter expects an array or "Traversable", got "%s".', get_debug_type($items))); + throw new RuntimeError(\sprintf('The "batch" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($items))); } $size = ceil($size); @@ -1483,9 +1483,9 @@ final class CoreExtension extends AbstractExtension $message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, $object::class); } elseif (\is_array($object)) { if (empty($object)) { - $message = \sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); + $message = \sprintf('Key "%s" does not exist as the sequence/mapping is empty.', $arrayItem); } else { - $message = \sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + $message = \sprintf('Key "%s" for sequence/mapping with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); } } elseif (/* Template::ARRAY_CALL */ 'array' === $type) { if (null === $object) { @@ -1515,7 +1515,7 @@ final class CoreExtension extends AbstractExtension if (null === $object) { $message = \sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); } elseif (\is_array($object)) { - $message = \sprintf('Impossible to invoke a method ("%s") on an array.', $item); + $message = \sprintf('Impossible to invoke a method ("%s") on a sequence/mapping.', $item); } else { $message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); } @@ -1653,7 +1653,7 @@ final class CoreExtension extends AbstractExtension if ($array instanceof \Traversable) { $array = iterator_to_array($array); } elseif (!\is_array($array)) { - throw new RuntimeError(\sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + throw new RuntimeError(\sprintf('The column filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); } return array_column($array, $name, $index); @@ -1665,7 +1665,7 @@ final class CoreExtension extends AbstractExtension public static function filter(Environment $env, $array, $arrow) { if (!is_iterable($array)) { - throw new RuntimeError(\sprintf('The "filter" filter expects an array or "Traversable", got "%s".', get_debug_type($array))); + throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($array))); } self::checkArrowInSandbox($env, $arrow, 'filter', 'filter'); @@ -1701,7 +1701,7 @@ final class CoreExtension extends AbstractExtension self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter'); if (!\is_array($array) && !$array instanceof \Traversable) { - throw new RuntimeError(\sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + throw new RuntimeError(\sprintf('The "reduce" filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); } $accumulator = $initial; diff --git a/src/Node/WithNode.php b/src/Node/WithNode.php index c78136c81..a7b7e70d9 100644 --- a/src/Node/WithNode.php +++ b/src/Node/WithNode.php @@ -49,7 +49,7 @@ class WithNode extends Node ->raw(";\n") ->write(\sprintf("if (!is_iterable(\$%s)) {\n", $varsName)) ->indent() - ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ") + ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a mapping.', ") ->repr($node->getTemplateLine()) ->raw(", \$this->getSourceContext());\n") ->outdent() diff --git a/tests/ExpressionParserTest.php b/tests/ExpressionParserTest.php index bb000225a..abf5ae3a8 100644 --- a/tests/ExpressionParserTest.php +++ b/tests/ExpressionParserTest.php @@ -55,9 +55,9 @@ class ExpressionParserTest extends TestCase } /** - * @dataProvider getTestsForArray + * @dataProvider getTestsForSequence */ - public function testArrayExpression($template, $expected) + public function testSequenceExpression($template, $expected) { $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]); $stream = $env->tokenize($source = new Source($template, '')); @@ -68,9 +68,9 @@ class ExpressionParserTest extends TestCase } /** - * @dataProvider getFailingTestsForArray + * @dataProvider getFailingTestsForSequence */ - public function testArraySyntaxError($template) + public function testSequenceSyntaxError($template) { $this->expectException(SyntaxError::class); @@ -79,7 +79,7 @@ class ExpressionParserTest extends TestCase $parser->parse($env->tokenize(new Source($template, 'index'))); } - public function getFailingTestsForArray() + public function getFailingTestsForSequence() { return [ ['{{ [1, "a": "b"] }}'], @@ -88,10 +88,10 @@ class ExpressionParserTest extends TestCase ]; } - public function getTestsForArray() + public function getTestsForSequence() { return [ - // simple array + // simple sequence ['{{ [1, 2] }}', new ArrayExpression([ new ConstantExpression(0, 1), new ConstantExpression(1, 1), @@ -101,7 +101,7 @@ class ExpressionParserTest extends TestCase ], 1), ], - // array with trailing , + // sequence with trailing , ['{{ [1, 2, ] }}', new ArrayExpression([ new ConstantExpression(0, 1), new ConstantExpression(1, 1), @@ -111,7 +111,7 @@ class ExpressionParserTest extends TestCase ], 1), ], - // simple hash + // simple mapping ['{{ {"a": "b", "b": "c"} }}', new ArrayExpression([ new ConstantExpression('a', 1), new ConstantExpression('b', 1), @@ -121,7 +121,7 @@ class ExpressionParserTest extends TestCase ], 1), ], - // hash with trailing , + // mapping with trailing , ['{{ {"a": "b", "b": "c", } }}', new ArrayExpression([ new ConstantExpression('a', 1), new ConstantExpression('b', 1), @@ -131,7 +131,7 @@ class ExpressionParserTest extends TestCase ], 1), ], - // hash in an array + // mapping in a sequence ['{{ [1, {"a": "b", "b": "c"}] }}', new ArrayExpression([ new ConstantExpression(0, 1), new ConstantExpression(1, 1), @@ -147,7 +147,7 @@ class ExpressionParserTest extends TestCase ], 1), ], - // array in a hash + // sequence in a mapping ['{{ {"a": [1, 2], "b": "c"} }}', new ArrayExpression([ new ConstantExpression('a', 1), new ArrayExpression([ @@ -168,7 +168,7 @@ class ExpressionParserTest extends TestCase new NameExpression('b', 1), ], 1)], - // array with spread operator + // sequence with spread operator ['{{ [1, 2, ...foo] }}', new ArrayExpression([ new ConstantExpression(0, 1), @@ -181,7 +181,7 @@ class ExpressionParserTest extends TestCase $this->createNameExpression('foo', ['spread' => true]), ], 1)], - // hash with spread operator + // mapping with spread operator ['{{ {"a": "b", "b": "c", ...otherLetters} }}', new ArrayExpression([ new ConstantExpression('a', 1), @@ -301,7 +301,7 @@ class ExpressionParserTest extends TestCase public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template) { $this->expectException(SyntaxError::class); - $this->expectExceptionMessage('A default value for an argument must be a constant (a boolean, a string, a number, or an array) in "index" at line 1'); + $this->expectExceptionMessage('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping) in "index" at line 1'); $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]); $parser = new Parser($env); diff --git a/tests/Fixtures/exceptions/exception_in_extension_extends.test b/tests/Fixtures/exceptions/exception_in_extension_extends.test index 2ab298059..fee521878 100644 --- a/tests/Fixtures/exceptions/exception_in_extension_extends.test +++ b/tests/Fixtures/exceptions/exception_in_extension_extends.test @@ -9,4 +9,4 @@ Exception thrown from a child for an extension error --DATA-- return [] --EXCEPTION-- -Twig\Error\RuntimeError: The random function cannot pick from an empty array in "base.twig" at line 4. +Twig\Error\RuntimeError: The random function cannot pick from an empty sequence/mapping in "base.twig" at line 4. diff --git a/tests/Fixtures/exceptions/exception_in_extension_include.test b/tests/Fixtures/exceptions/exception_in_extension_include.test index e2281b290..ab09cabb7 100644 --- a/tests/Fixtures/exceptions/exception_in_extension_include.test +++ b/tests/Fixtures/exceptions/exception_in_extension_include.test @@ -9,4 +9,4 @@ Exception thrown from an include for an extension error --DATA-- return [] --EXCEPTION-- -Twig\Error\RuntimeError: The random function cannot pick from an empty array in "content.twig" at line 4. +Twig\Error\RuntimeError: The random function cannot pick from an empty sequence/mapping in "content.twig" at line 4. diff --git a/tests/Fixtures/expressions/spread_hash_operator.test b/tests/Fixtures/expressions/spread_mapping_operator.test similarity index 95% rename from tests/Fixtures/expressions/spread_hash_operator.test rename to tests/Fixtures/expressions/spread_mapping_operator.test index c2429f00e..e944eee8a 100644 --- a/tests/Fixtures/expressions/spread_hash_operator.test +++ b/tests/Fixtures/expressions/spread_mapping_operator.test @@ -1,5 +1,5 @@ --TEST-- -Twig supports the spread operator on hashes +Twig supports the spread operator on mappings --TEMPLATE-- {% for key, value in { firstName: 'Ryan', lastName: 'Weaver', favoriteFood: 'popcorn', ...{favoriteFood: 'pizza', sport: 'running'} } %} {{ key }}: {{ value }} diff --git a/tests/Fixtures/filters/replace_invalid_arg.test b/tests/Fixtures/filters/replace_invalid_arg.test index ba6fea412..ea1632500 100644 --- a/tests/Fixtures/filters/replace_invalid_arg.test +++ b/tests/Fixtures/filters/replace_invalid_arg.test @@ -5,4 +5,4 @@ Exception for invalid argument type in replace call --DATA-- return ['stdClass' => new \stdClass()] --EXCEPTION-- -Twig\Error\RuntimeError: The "replace" filter expects an array or "Traversable" as replace values, got "stdClass" in "index.twig" at line 2. +Twig\Error\RuntimeError: The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "stdClass" in "index.twig" at line 2. diff --git a/tests/Fixtures/functions/cycle_empty_mapping.test b/tests/Fixtures/functions/cycle_empty_mapping.test new file mode 100644 index 000000000..6296c2c39 --- /dev/null +++ b/tests/Fixtures/functions/cycle_empty_mapping.test @@ -0,0 +1,8 @@ +--TEST-- +"cycle" function returns an error on empty mappings +--TEMPLATE-- +{{ cycle({}, 0) }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\RuntimeError: The "cycle" function does not work on empty sequences/mappings in "index.twig" at line 2. diff --git a/tests/Fixtures/functions/cycle_empty.test b/tests/Fixtures/functions/cycle_empty_sequence.test similarity index 57% rename from tests/Fixtures/functions/cycle_empty.test rename to tests/Fixtures/functions/cycle_empty_sequence.test index bb338452a..01d9fe127 100644 --- a/tests/Fixtures/functions/cycle_empty.test +++ b/tests/Fixtures/functions/cycle_empty_sequence.test @@ -1,8 +1,8 @@ --TEST-- -"cycle" function returns an error on empty arrays +"cycle" function returns an error on empty sequences --TEMPLATE-- {{ cycle([], 0) }} --DATA-- return [] --EXCEPTION-- -Twig\Error\RuntimeError: The "cycle" function does not work on empty arrays in "index.twig" at line 2. +Twig\Error\RuntimeError: The "cycle" function does not work on empty sequences/mappings in "index.twig" at line 2. diff --git a/tests/Fixtures/regression/strings_like_numbers.test b/tests/Fixtures/regression/strings_like_numbers.test index 62fe88485..3884226fa 100644 --- a/tests/Fixtures/regression/strings_like_numbers.test +++ b/tests/Fixtures/regression/strings_like_numbers.test @@ -1,8 +1,8 @@ --TEST-- Twig does not confuse strings with integers in getAttribute() --TEMPLATE-- -{{ hash['2e2'] }} +{{ mapping['2e2'] }} --DATA-- -return ['hash' => ['2e2' => 'works']] +return ['mapping' => ['2e2' => 'works']] --EXPECT-- works diff --git a/tests/Fixtures/tags/with/with_no_hash.test b/tests/Fixtures/tags/with/with_no_mapping.test similarity index 56% rename from tests/Fixtures/tags/with/with_no_hash.test rename to tests/Fixtures/tags/with/with_no_mapping.test index 7083050b4..7b22c5171 100644 --- a/tests/Fixtures/tags/with/with_no_hash.test +++ b/tests/Fixtures/tags/with/with_no_mapping.test @@ -1,10 +1,10 @@ --TEST-- -"with" tag with an expression that is not a hash +"with" tag with an expression that is not a mapping --TEMPLATE-- {% with vars %} {{ foo }}{{ bar }} {% endwith %} --DATA-- -return ['vars' => 'no-hash'] +return ['vars' => 'no-mapping'] --EXCEPTION-- -Twig\Error\RuntimeError: Variables passed to the "with" tag must be a hash in "index.twig" at line 2. +Twig\Error\RuntimeError: Variables passed to the "with" tag must be a mapping in "index.twig" at line 2. diff --git a/tests/TemplateTest.php b/tests/TemplateTest.php index 9762cb8d8..0d562a6b1 100644 --- a/tests/TemplateTest.php +++ b/tests/TemplateTest.php @@ -66,17 +66,17 @@ class TemplateTest extends TestCase return [ ['{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1.'], ['{{ null["a"] }}', 'Impossible to access a key ("a") on a null variable in "%s" at line 1.'], - ['{{ empty_array["a"] }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'], - ['{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'], + ['{{ empty_array["a"] }}', 'Key "a" does not exist as the sequence/mapping is empty in "%s" at line 1.'], + ['{{ array["a"] }}', 'Key "a" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'], ['{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig\Tests\TemplateArrayAccessObject" does not exist in "%s" at line 1.'], ['{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1.'], ['{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1.'], ['{{ null.a }}', 'Impossible to access an attribute ("a") on a null variable in "%s" at line 1.'], ['{{ null.a() }}', 'Impossible to invoke a method ("a") on a null variable in "%s" at line 1.'], - ['{{ array.a() }}', 'Impossible to invoke a method ("a") on an array in "%s" at line 1.'], - ['{{ empty_array.a }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'], - ['{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'], - ['{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1.'], + ['{{ array.a() }}', 'Impossible to invoke a method ("a") on a sequence/mapping in "%s" at line 1.'], + ['{{ empty_array.a }}', 'Key "a" does not exist as the sequence/mapping is empty in "%s" at line 1.'], + ['{{ array.a }}', 'Key "a" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'], + ['{{ attribute(array, -10) }}', 'Key "-10" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'], ['{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()"/"hasa()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'], ['{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property "missing_method" nor one of the methods "missing_method()", "getmissing_method()"/"ismissing_method()"/"hasmissing_method()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'], ['{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.'], @@ -175,14 +175,14 @@ class TemplateTest extends TestCase $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']); $this->assertSame('EmptyString', $array[null]); - $this->assertSame('Zero', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, false), 'false is treated as 0 when accessing an array (equals PHP behavior)'); - $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, true), 'true is treated as 1 when accessing an array (equals PHP behavior)'); - $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, 1.5), 'float is casted to int when accessing an array (equals PHP behavior)'); - $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1'), '"1" is treated as integer 1 when accessing an array (equals PHP behavior)'); - $this->assertSame('MinusOne', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, -1.5), 'negative float is casted to int when accessing an array (equals PHP behavior)'); - $this->assertSame('FloatButString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1.5'), '"1.5" is treated as-is when accessing an array (equals PHP behavior)'); - $this->assertSame('IntegerButStringWithLeadingZeros', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '01'), '"01" is treated as-is when accessing an array (equals PHP behavior)'); - $this->assertSame('EmptyString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, null), 'null is treated as "" when accessing an array (equals PHP behavior)'); + $this->assertSame('Zero', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, false), 'false is treated as 0 when accessing a sequence/mapping (equals PHP behavior)'); + $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, true), 'true is treated as 1 when accessing a sequence/mapping (equals PHP behavior)'); + $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, 1.5), 'float is casted to int when accessing a sequence/mapping (equals PHP behavior)'); + $this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1'), '"1" is treated as integer 1 when accessing a sequence/mapping (equals PHP behavior)'); + $this->assertSame('MinusOne', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, -1.5), 'negative float is casted to int when accessing a sequence/mapping (equals PHP behavior)'); + $this->assertSame('FloatButString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1.5'), '"1.5" is treated as-is when accessing a sequence/mapping (equals PHP behavior)'); + $this->assertSame('IntegerButStringWithLeadingZeros', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '01'), '"01" is treated as-is when accessing a sequence/mapping (equals PHP behavior)'); + $this->assertSame('EmptyString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, null), 'null is treated as "" when accessing a sequence/mapping (equals PHP behavior)'); } /** @@ -377,7 +377,7 @@ class TemplateTest extends TestCase $tests = array_merge($tests, [ [false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute ("a") on a integer variable ("42") in "index.twig".'], [false, null, 'string', 'a', [], $anyType, 'Impossible to access an attribute ("a") on a string variable ("string") in "index.twig".'], - [false, null, [], 'a', [], $anyType, 'Key "a" does not exist as the array is empty in "index.twig".'], + [false, null, [], 'a', [], $anyType, 'Key "a" does not exist as the sequence/mapping is empty in "index.twig".'], ]); return $tests;