Skip to content

ReflectionCompositeTypeAssumedNamed ​

error native on by default PHP ≥ 8.0

Group: Probable bugs

Inspect the composite reflection type members.

Example ​

Finding:

php
<?php
function consume(int|string $x) {} $r = new ReflectionFunction("consume"); $t = $r->getParameters()[0]->getType(); $t->getName();

Reported:

  • line 2: Inspect the composite reflection type members.

Detection ​

  • Resolve getType on a ReflectionParameter, ReflectionProperty or getReturnType on a ReflectionFunctionAbstract for a known union type (PHP 8.0+) or intersection type (PHP 8.1+). Report getName invoked on that result, because only ReflectionNamedType provides it. Follow local assignments and indexed parameter selection.
  • D1a. A declared type is composite when it has an intersection (&, including a parenthesised group inside a union) or when at least two union members remain after discarding a leading ? and any null member. ?T, T|null and null|T are a single nullable named type and are never reported; int|string|null is.
  • Emit one finding per violating operation. Flow facts require a reachable path with no intervening mutation, escaped alias, unknown call involving the tracked value or analysis-budget exhaustion. Uncertain facts cannot prove a violation.

Exceptions (no report) ​

  • Calls on members returned by getTypes and unknown reflected declarations are excluded. Nullable single named types remain ReflectionNamedType (D1a).

  • No report when the conditions guarding the call prove the receiver value is a ReflectionNamedType. A test proves it when it is $t instanceof ReflectionNamedType (or is_a() with that class) and holds, or when it is such a test for the composite class the declared type reflects as (ReflectionUnionType for a union, ReflectionIntersectionType for a pure intersection) and fails. Negation with !, parentheses, comparisons with true/false, a local holding the test's result, &&/and and ||/or combine these the usual way: a true conjunction or a false disjunction needs one proving operand, the opposite needs both. Guarding positions:

    • the body of an if/elseif/while whose condition proves it when true, and any later elseif condition, elseif body or else body of a chain in which an earlier condition proves it when false;
    • the matching arm of a ternary (including the right side of the short form);
    • the right operand of &&/and (left proves when true) or ||/or (left proves when false);
    • a match (true) arm whose every condition proves it when true;
    • any code after a test whose other side always leaves (if (!$t instanceof ReflectionNamedType) { return; }, with or without later elseif/else arms, or an else that leaves), or after a builtin assert() of a proving condition.

    The test must concern the same unmodified receiver value: reassigning the variable between the test and the call, a test on another value, a dynamic class operand, a guard in an outer function scope, a branch that falls through or a jump label between the test and the call keep the report. The wrong side of a guard (if ($t instanceof ReflectionUnionType) { $t->getName(); }, the else of a ReflectionNamedType test) is still reported.

  • Exclude shadowed builtin symbols, unresolved required types, incomplete syntax and unsupported PHP versions.

Fix ​

No automatic fix. Choosing a repair requires runtime information, error-handling policy or a semantic decision.

All edits use exact byte ranges, preserve comments and evaluation order, and require syntax supported by the target PHP version. A fix must never add an evaluation or silently change unrelated arguments.

PHP versions ​

Minimum PHP 8.0.

Configure ​

In custos.json:

json
{
  "rules": {
    "ReflectionCompositeTypeAssumedNamed": {
      "enabled": false
    }
  }
}

Suppress ​

Before the statement or declaration (or the first statement of the file):

php
// @custos-ignore ReflectionCompositeTypeAssumedNamed

Released under the MIT License. Native rules and rules modeled on Php Inspections (EA Extended); independent clean-room implementation.