Skip to content

First-class callables with parameter conditions#418

Merged
spaze merged 2 commits into
mainfrom
spaze/first-class-callable-param-conditions
Apr 28, 2026
Merged

First-class callables with parameter conditions#418
spaze merged 2 commits into
mainfrom
spaze/first-class-callable-param-conditions

Conversation

@spaze

@spaze spaze commented Apr 28, 2026

Copy link
Copy Markdown
Owner

PHPStan detects first-class callable syntax at the point of func(...), where no arguments are present - they are supplied only when the callable is later invoked, which means parameter conditions configured on the disallowed call cannot be evaluated at the detection point.

This PR corrects the behavior across all param condition directives. Conditions that require a matching param value to allow a call - allowParamsAnywhere, allowParamsInAllowed, and their AnyValue and Flags variants - can never be satisfied without arguments, so first-class callables are always reported when any of these is configured, regardless of zone. Conditions that require a matching param value to disallow a call - allowExceptParamsAnywhere, allowExceptParamsInAllowed, and their variants and aliases - can never be triggered without arguments, so first-class callables are never reported in either allowIn* or allowExceptIn* zones when these are configured.

Previously, allowParamsAnywhere incorrectly allowed first-class callables because the null-args path in hasAllowedParams unconditionally returned true. allowExceptParamsInAllowed incorrectly disallowed first-class callables in allowed zones because the null-args path in hasAllowedParamsInAllowed treated it identically to allowParamsInAllowed. Both are fixed. A new section in docs/allow-with-parameters.md documents the complete behavior for all param condition directives.

Closes #415

@spaze spaze self-assigned this Apr 28, 2026
@spaze spaze force-pushed the spaze/first-class-callable-param-conditions branch 2 times, most recently from 7de490c to 1509fe4 Compare April 28, 2026 02:21
@spaze spaze mentioned this pull request Apr 28, 2026
@spaze spaze marked this pull request as ready for review April 28, 2026 02:25
Copilot AI review requested due to automatic review settings April 28, 2026 02:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes how first-class callable syntax (func(...)) interacts with parameter-based allow/disallow conditions by treating “no args at call site” as an unsatisfiable param allow condition and an untriggerable param disallow condition. This aligns the rule behavior with PHPStan’s detection point for first-class callables and closes #415.

Changes:

  • Adjusted Allowed param-check logic so args === null no longer incorrectly satisfies param conditions.
  • Added targeted first-class-callable fixtures + rule tests for allowParamsAnywhere, allowParamsInAllowed, and allowExceptParamsInAllowed scenarios.
  • Documented first-class callable behavior in docs/allow-with-parameters.md and updated lint exclusions for PHP < 8.1.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Allowed/Allowed.php Fix null-args handling for param conditions and make allowed/disallowed zone behavior explicit via allowedByDefault.
docs/allow-with-parameters.md Adds documentation section describing first-class callable behavior with param directives.
composer.json Excludes new PHP 8.1-only fixtures from lint runs on PHP 7.4/8.0.
tests/src/RoyaleExceptFirstClassCallable.php Fixture covering allowExceptInMethods + param directives with first-class callables.
tests/src/RoyaleAllowInFirstClassCallable.php Fixture covering allowInMethods + param directives with first-class callables.
tests/src/FirstClassCallableParamsAnywhere.php Fixture covering allowParamsAnywhere/allowExceptParamsAnywhere with first-class callables.
tests/Calls/FunctionFirstClassCallablesParamsAnywhereTest.php New test asserting first-class callable behavior under allowParamsAnywhere/allowExceptParamsAnywhere.
tests/Calls/FunctionFirstClassCallablesAllowInMethodsWithParamsTest.php New test asserting allowInMethods + allowParamsInAllowed / allowExceptParamsInAllowed.
tests/Calls/FunctionFirstClassCallablesAllowExceptInMethodsWithParamsTest.php New test asserting allowExceptInMethods + allowParamsInAllowed / allowExceptParamsInAllowed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/allow-with-parameters.md Outdated
When a function or method is used as a first-class callable, no arguments are present at the detection point - they are supplied only when the callable is eventually invoked, so parameter conditions cannot be evaluated at the call site.

Conditions that restrict which calls are allowed (`allowParamsAnywhere`, `allowParamsInAllowed`, and their `AnyValue` and `Flags` variants) require a matching param value. Because no args are present the condition can never be satisfied, so first-class callables are always reported when these directives are configured.

Conditions that restrict which calls are disallowed (`allowExceptParamsAnywhere`, `allowExceptParamsInAllowed`, and their variants and aliases) require a matching param value to trigger the disallow. Because no args are present the forbidden condition can never be triggered, so first-class callables are never reported when these directives are configured.
@spaze spaze force-pushed the spaze/first-class-callable-param-conditions branch from 1509fe4 to aecdc11 Compare April 28, 2026 02:51
@spaze spaze requested a review from Copilot April 28, 2026 02:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@spaze spaze merged commit 5b69dc0 into main Apr 28, 2026
154 checks passed
@spaze spaze deleted the spaze/first-class-callable-param-conditions branch April 28, 2026 05:25
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.

allowParamsInAllowed with allowExceptIn* incorrectly allows first-class callables in the disallowed location

2 participants