Skip to content

[CodeQuality] Narrow assertTrue(is_<type>()) to assertIs<Type>() - #770

Open
Soean wants to merge 2 commits into
rectorphp:mainfrom
Soean:assert-true-is-type-to-assert-is-type
Open

[CodeQuality] Narrow assertTrue(is_<type>()) to assertIs<Type>()#770
Soean wants to merge 2 commits into
rectorphp:mainfrom
Soean:assert-true-is-type-to-assert-is-type

Conversation

@Soean

@Soean Soean commented Aug 13, 2026

Copy link
Copy Markdown

AssertTrueFalseToSpecificMethodRector already narrows assertTrue(is_readable(...)), assertTrue(is_null(...)) and friends, but the is_<type>() family was missing — so $this->assertTrue(is_callable($value)) stayed as a generic boolean assertion instead of becoming $this->assertIsCallable($value).
The generic form loses the failure message: assertTrue() can only report "false is not true", while assertIsCallable() reports the actual value and the type it was expected to have.

Added mappings

function assertTrue / assertNotFalse assertFalse / assertNotTrue
is_array assertIsArray assertIsNotArray
is_bool assertIsBool assertIsNotBool
is_callable assertIsCallable assertIsNotCallable
is_float assertIsFloat assertIsNotFloat
is_int assertIsInt assertIsNotInt
is_iterable assertIsIterable assertIsNotIterable
is_numeric assertIsNumeric assertIsNotNumeric
is_object assertIsObject assertIsNotObject
is_scalar assertIsScalar assertIsNotScalar
is_string assertIsString assertIsNotString

Each of the ten was checked against IsType::matches() to confirm the constraint runs the very same function, so the assertions are equivalent.

is_callable() is skipped when it carries more than one argument — its $syntax_only and $callable_name parameters have no counterpart in assertIsCallable() and would otherwise be moved up into the $message slot.

Also in here: named and spread args

Args are moved up by position, so a named arg only survives when the assert method happens to use the same parameter name. That holds for is_readable(filename:), is_writable(filename:), file_exists(filename:), array_key_exists(key:, array:) and str_contains(haystack:, needle:). It does not hold for any of the new entries, as PHP names the parameter $value while PHPUnit names it $actual:

$this->assertTrue(is_string(value: $value));
// became assertIsString(value: $value) -> Unknown named parameter $value

The bail-out is blanket rather than per entry, which also covers is_dir ($filename vs $directory), is_null, is_nan, is_infinite and is_a — those carry the same mismatch today. The cost is that the handful of pairs whose names do line up get skipped too, which seemed a better trade than tracking a parameter name per mapped function. Spread args are skipped for the same reason, as their count is unknown.

It sits in its own hasUnmovableArgs() method, so it is easy to pull out if you would rather keep this PR to the new mappings.

Soean added 2 commits August 13, 2026 11:39
…ssertTrueFalseToSpecificMethodRector

Adds the is_<type>() family to the function map: is_array, is_bool,
is_callable, is_float, is_int, is_iterable, is_numeric, is_object,
is_scalar and is_string.
    $this->assertTrue(is_callable($value))  -> $this->assertIsCallable($value)
    $this->assertFalse(is_callable($value)) -> $this->assertIsNotCallable($value)
is_callable() is skipped when it carries more than one argument, as its
$syntax_only and $callable_name parameters have no counterpart in
assertIsCallable() and would end up in the $message slot.
is_resource() is left out on purpose: the IsType constraint counts
"resource (closed)" as a resource, while is_resource() returns false for
closed resources, so assertIsResource() is not an equivalent.
Also bail out on named and spread args. The rule moves the inner function
arguments up into the assert call, but the assert methods name their
parameters differently, so a named arg produces a call that cannot be
resolved:
    $this->assertTrue(is_readable(filename: $file));
    // became assertIsReadable(filename: $file) -> Unknown named parameter
Spread args have the same problem, as their count is unknown at compile
time. This part is pre-existing and affects the whole map, not just the
new entries.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant