Skip to content

Return null from ArgumentsNormalizer::reorderArgs() when positional args after named args create holes beyond parameter count#5637

Merged
staabm merged 8 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-es0nqvu
May 11, 2026
Merged

Return null from ArgumentsNormalizer::reorderArgs() when positional args after named args create holes beyond parameter count#5637
staabm merged 8 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-es0nqvu

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When calling a function with positional arguments after a named argument (e.g. \PHPStan\dumpType(1, 2, 3, d: 'foo', 5)), PHPStan crashed with an internal error "Parameter signatures cannot have holes" instead of reporting proper errors about the invalid argument order.

Changes

  • src/Analyser/ArgumentsNormalizer.php: Replace throw new ShouldNotHappenException(...) with return null in the hole-filling loop of reorderArgs() when a hole index exceeds the parameter signature length.
  • tests/PHPStan/Analyser/ArgumentsNormalizerTest.php: Added two test cases to dataReorderInvalid:
    • Positional arg after named arg with a variadic parameter (mirrors the exact reproducer from the issue)
    • Positional arg after named arg without a variadic parameter

Root cause

In ArgumentsNormalizer::reorderArgs(), positional arguments use their original index from the $callArgs array as keys in $reorderedArgs. When a named argument sits between positional arguments (invalid PHP: "Cannot use positional argument after named argument"), the named arg is handled separately (placed in additionalNamedArgs or appendArgs), but the positional arg following it keeps its original index. This creates a gap in the $reorderedArgs array at an index that has no corresponding entry in $signatureParameters, causing the hole-filling loop to throw.

The fix returns null instead, which all callers already handle gracefully — they either fall back to the original (unreordered) arguments or skip the check. The normal argument validation rules then correctly report "Named argument cannot be followed by a positional argument."

Since all callable types (functions, methods, static methods, constructors, call_user_func) share the same reorderArgs() method, the fix covers all of them automatically. Verified with manual testing of FuncCall, MethodCall, StaticCall, New_, and call_user_func.

Test

  • Added two unit test cases in ArgumentsNormalizerTest::dataReorderInvalid that reproduce the crash and verify reorderArgs() returns null for these invalid argument patterns.
  • All 12045 existing tests pass. PHPStan self-analysis reports no errors.

Fixes phpstan/phpstan#14596

…al args after named args create holes beyond parameter count

- In `reorderArgs()`, positional arguments keep their original index from
  `$callArgs`. When a named argument appears between positional arguments
  (which is invalid PHP), the named arg is removed from the positional
  sequence, creating a hole in the `$reorderedArgs` array at an index
  beyond the parameter signature length.
- The hole-filling loop at line 401 threw `ShouldNotHappenException` when
  encountering a hole index not present in `$signatureParameters`.
- Replace the throw with `return null`, which tells all callers (func calls,
  method calls, static calls, `new`, `ParametersAcceptorSelector`) that
  argument reordering failed, and they fall back gracefully.
- The fix applies to all callable types (functions, methods, static methods,
  constructors) since they all share `reorderArgs()`.
- Added two test cases to `ArgumentsNormalizerTest::dataReorderInvalid`:
  one with a variadic parameter and one without.
Copy link
Copy Markdown
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this fixes a crash-bug it needs a test in AnalyserIntegrationTest

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Done. Added an integration test in AnalyserIntegrationTest::testBug14596() that reproduces the exact crash from the issue (\PHPStan\dumpType(1, 2, 3, d: 'foo', 5) — positional arg after named arg). Verified the test fails with the original ShouldNotHappenException before the fix and passes after. All 12046 tests pass and make phpstan reports no errors.

Comment thread tests/PHPStan/Analyser/data/bug-14596.php
phpstan-bot and others added 3 commits May 11, 2026 07:14
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntax

The test data file defines a class in the Bug14596 namespace, which gets
indexed by Composer's classmap autoloader. Since the file also contains
`positional argument after named argument` (a PHP compile-time error),
any autoload attempt crashes the process — including PHPStan's parallel
workers.

Move class and function definitions to a separate bug-14596-definition.php
file with valid PHP syntax, keeping only the invalid calls in the main
test file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +19 to +24
// method call
$obj = new Foo(1, 2, 3);
$obj->bar(1, 2, 3, d: 'foo', 5);

// static method call
Foo::baz(1, 2, 3, d: 'foo', 5);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phpstan-bot cover this example also in CallMethodsRuleTest, CallStaticMethodsRuleTest and assert expected errors

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed. Added testBug14596() to both CallMethodsRuleTest and CallStaticMethodsRuleTest, with a shared test data file at tests/PHPStan/Rules/Methods/data/bug-14596.php that exercises method calls and static method calls with positional arguments after named arguments. Both tests assert the expected "Named argument cannot be followed by a positional argument." error. All 12048 tests pass and make phpstan reports no errors.

Assert that positional arguments after named arguments on method calls
and static method calls report the expected error instead of crashing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm requested a review from VincentLanglet May 11, 2026 08:03
@staabm staabm merged commit 745b400 into phpstan:2.1.x May 11, 2026
654 of 658 checks passed
@staabm staabm deleted the create-pull-request/patch-es0nqvu branch May 11, 2026 08:17
clxmstaab pushed a commit to staabm/phpstan-src that referenced this pull request May 11, 2026
…al args after named args create holes beyond parameter count (phpstan#5637)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Markus Staab <markus.staab@redaxo.de>
staabm added a commit that referenced this pull request May 11, 2026
…al args after named args create holes beyond parameter count (#5637)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Markus Staab <markus.staab@redaxo.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants