{"record":{"id":"84745eb49bd75f9e","repo":"twigphp/Twig","slug":"positional-arguments-cannot-be-used-after-named-arguments-84745e","errorCode":null,"errorMessage":"Positional arguments cannot be used after named arguments for macro \"%s\".","messagePattern":"Positional arguments cannot be used after named arguments for macro \"(.+?)\"\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/TwigMacro.php","lineNumber":129,"sourceCode":"                $misordered = $misordered || $sawNamed;\n                ++$positionalCount;\n            } else {\n                $sawNamed = true;\n                if (null === $i = $this->argumentIndexes[$key] ?? null) {\n                    $hasUnknownNamed = true;\n                } else {\n                    if (null === $duplicate && $i < $positionalCount) {\n                        $duplicate = $key;\n                    }\n                    if (isset($this->requiredNames[$key])) {\n                        ++$namedRequired;\n                    }\n                }\n            }\n        }\n\n        if ($misordered) {\n            throw new RuntimeError(\\sprintf('Positional arguments cannot be used after named arguments for macro \"%s\".', $this->name), $lineno, $source);\n        }\n        if (null !== $duplicate) {\n            throw new RuntimeError(\\sprintf('Argument \"%s\" is defined twice for macro \"%s\".', $duplicate, $this->name), $lineno, $source);\n        }\n\n        // For a fully named call, the coverage of the required arguments is exact; a\n        // mixed call falls back to the precise (and slower) per-argument check.\n        $mayMissRequired = 0 === $positionalCount\n            ? $namedRequired < \\count($this->requiredNames)\n            : $positionalCount < $this->requiredCount;\n\n        if ($mayMissRequired || (!$this->variadic && ($hasUnknownNamed || $positionalCount > \\count($this->arguments)))) {\n            $this->triggerLegacyDeprecations($arguments, $positionalCount, $source, $lineno);\n        }\n\n        foreach ($this->renamedArguments as $name => $parameterName) {\n            if (\\array_key_exists($name, $arguments)) {\n                $arguments[$parameterName] = $arguments[$name];","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/TwigMacro.php#L111-L147","documentation":"Twig throws this RuntimeError when a macro call passes positional arguments after named ones, e.g. {{ m(a=1, 2) }}. PHP itself forbids this ordering, so callLegacy() in TwigMacro detects the mixed, misordered call upfront and reports it with the template call-site line and source instead of a cryptic PHP error. It is one of the lenient-to-strict deprecation checks that become hard errors in Twig 4.0.","triggerScenarios":"Calling TwigMacro::callLegacy() with an arguments array that is not a list and contains an integer key occurring after a string (named) key — i.e. a template calling a macro with positional arguments following named arguments.","commonSituations":"Template authors start adding a new named argument to a macro call and leave existing positional arguments after it; dynamically built call arrays where names are added before leftover positional values; templates upgraded from Twig 2/3 that previously relied on lenient handling.","solutions":["Reorder the macro call so all positional arguments come before any named arguments.","Convert the positional arguments to named arguments so the whole call is uniformly named.","If arguments are built dynamically in PHP, append positional values before named ones or key everything by name before invoking the macro."],"exampleFix":"{# before #}\n{{ macro.box(padding=2, 'title') }}\n{# after #}\n{{ macro.box('title', padding=2) }}","handlingStrategy":"validation","validationCode":"// PHP, before invoking a macro programmatically\n$posSeen = false; $namedSeen = false;\nforeach (array_keys($args) as $k) {\n    if (is_int($k)) { $posSeen = $posSeen || $namedSeen; }\n    else { $namedSeen = true; }\n}\nif ($posSeen) { throw new InvalidArgumentException('Reorder: positional args must precede named args'); }","typeGuard":"function argsAreWellOrdered(array $args): bool {\n    $namedSeen = false;\n    foreach (array_keys($args) as $k) {\n        if (is_int($k)) { if ($namedSeen) return false; }\n        else { $namedSeen = true; }\n    }\n    return true;\n}","tryCatchPattern":"try {\n    $out = $macro->callLegacy($args, $source, $lineno);\n} catch (Twig\\Error\\RuntimeError $e) {\n    if (str_contains($e->getMessage(), 'Positional arguments cannot be used after named arguments')) {\n        ksort($args, SORT_STRING); // or reorder so integers come first\n        $out = $macro->callLegacy($args, $source, $lineno);\n    } else { throw $e; }\n}","preventionTips":["Always place positional arguments before named arguments in macro calls.","Prefer fully named macro calls in new templates.","Run your Twig template test suite under strict Twig 4 settings to catch ordering problems early."],"tags":["twig","macro","arguments","template"],"backgroundTag":"invalid-argument-order","analyzedSha":"a414c3a491defb5a60f2fc88ef79ff37c90010cd","analyzedAt":"2026-09-13T15:10:46.849Z","contentChangedAt":"2026-09-13T15:10:46.849Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}