Skip to content

Resolve resource native type declarations in internal stubs as ResourceType - #6379

Closed
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-enamuar
Closed

Resolve resource native type declarations in internal stubs as ResourceType#6379
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-enamuar

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

On PHP 7.x, curl_getinfo($handle) was reported as Parameter #1 $handle of function curl_getinfo expects resource, (resource|false) given. — and even a definitely-narrowed resource produced the nonsensical expects resource, resource given.

The two resources in that message were not the same type: the expected one was ObjectType('resource') (a class named resource), the given one was PHPStan's ResourceType pseudo-type.

Changes

  • src/Type/ParserNodeTypeToPHPStanType.php: resolve() gained an optional bool $isBuiltin parameter. When it is true, a Name type node whose lowercased name is resource resolves to ResourceType. The flag is threaded through the nullable, union and intersection branches.
  • src/Type/TypehintHelper.php: decideTypeFromReflection() gained the same optional bool $isBuiltin parameter and forwards it (also through its union/intersection recursion) to ParserNodeTypeToPHPStanType::resolve().
  • The flag is passed as true from every reflection path that can read a type declaration belonging to a PHP-internal symbol:
    • src/Reflection/Php/PhpFunctionReflection.php — parameters and (native) return type
    • src/Reflection/Php/PhpParameterReflection.php — new constructor argument, used for both getType() and getNativeType()
    • src/Reflection/Php/PhpMethodReflection.php — parameters, return type, native return type and the prototype's tentative return type
    • src/Reflection/Native/NativeMethodReflection.php — prototype tentative return type
    • src/Reflection/Php/PhpClassReflectionExtension.php — property native type and method native return type
    • src/Reflection/ClassReflection.php — enum backing type and class-constant native types
    • src/Reflection/InitializerExprTypeResolver.php — class-constant native type
    • src/Reflection/SignatureMap/FunctionSignatureMapProvider.php and src/Reflection/SignatureMap/Php8SignatureMapProvider.php — always builtin
  • User code keeps the old behaviour: function f(resource $r) still resolves to ObjectType('resource') and still reports class.notFound, which is correct — PHP warns "resource" is not a supported builtin type and will be interpreted as a class name and throws a TypeError when an actual resource is passed.

Analogous cases

  • Other extensions on the same axis (fixed): the bug was never curl-specific. Under PHP 7.x, signatures coming from phpstorm-stubs rather than resources/functionMap.php were affected across fileinfo (finfo_file, finfo_buffer), xml (xml_parse_into_struct), pgsql (pg_exec, pg_numrows, pg_loopen, pg_loread, …), ftp (ftp_alloc, ftp_quit), imap and openssl. All are fixed by the same change.
  • Other member kinds (plumbed, currently unreachable): methods, properties, class constants and enum backing types receive the flag too, even though no current stub declares resource on them, so the family cannot drift apart later.
  • Other pseudo-types leaking as class names (probed, not a PHPStan bug): a scan for ObjectTypes referring to non-existent classes in all internal signatures on PHP 7.2/7.4/8.0/8.4 turns up only upstream phpstorm-stubs typos where a parameter name was written in the type position (java_reload, zend_get_id, register_event_handler, …) plus Socket/GdImage/OCI-Lob misdeclarations. resource was the only real pseudo-type leak.
  • PHP >= 8.0 (probed, already correct): php-8-stubs declare CurlHandle etc., so nothing changed there; the same scan reports zero hits.

Root cause

resource has no native PHP type declaration, so phpstorm-stubs express it with #[LanguageLevelTypeAware(['8.0' => 'CurlHandle'], default: 'resource')]. BuilderHelpers::normalizeType('resource') is not in php-parser's builtin-type list, so it produces a PhpParser\Node\Name — a class name — and ParserNodeTypeToPHPStanType::resolve() faithfully turned that into ObjectType('resource').

Nothing is a subtype of a class that does not exist, so every argument of a genuine resource type was rejected, and the error message was unreadable because ObjectType('resource') and ResourceType both describe themselves as resource.

Most of these signatures used to be shadowed by resources/functionMap.php, which resolves types through TypeNodeResolver where resource correctly maps to ResourceType. Commit 6a59780 ("Clean out redundant stub overwrites") dropped entries such as curl_getinfo, which exposed the underlying resolution bug.

The fix makes the resolution context-aware instead of adding another data patch: inside a PHP-internal symbol, resource means the pseudo-type; in user code it keeps meaning a class name, exactly as PHP reads it.

Test

  • tests/PHPStan/Rules/Functions/CallToFunctionParametersPhp7RuleTest.php + tests/PHPStan/Rules/Functions/data/bug-15185.php — runs CallToFunctionParametersRule with phpVersion: 70400. It contains the reporter's playground snippet plus narrowed-resource, fileinfo, xml and pgsql cases. Without the fix it reports 7 errors (expects resource, (resource|false) given and expects resource, resource given across curl, xml, fileinfo and pgsql); with the fix it reports none.
  • tests/PHPStan/Analyser/Bug15185Test.php + tests/PHPStan/Analyser/data/bug-15185.php — a phpVersion: 70400 type-inference test covering the return-type half of the bug. assertType('resource', $result) after is_resource(pg_exec(...)) yields *NEVER* without the fix, because ObjectType('resource') and ResourceType are disjoint.

Fixes phpstan/phpstan#15185

…ourceType`

- `ParserNodeTypeToPHPStanType::resolve()` and `TypehintHelper::decideTypeFromReflection()` take a new optional `$isBuiltin` flag. When it is set, a `Name` type node spelled `resource` resolves to `ResourceType` instead of `ObjectType('resource')`. The flag is propagated through nullable, union and intersection type nodes.
- The flag is passed from every reflection path that can read a type declaration of a PHP-internal (stubbed) symbol: `PhpFunctionReflection` (parameters + return), `PhpMethodReflection` (parameters, return, prototype tentative return), `PhpParameterReflection` (new constructor argument), `PhpClassReflectionExtension` (property native type, method native return), `ClassReflection` (enum backing type, class constant native types), `InitializerExprTypeResolver`, `NativeMethodReflection`, `FunctionSignatureMapProvider` and `Php8SignatureMapProvider`.
- User-written `resource` type declarations keep resolving to `ObjectType`, so PHPStan still reports `class.notFound` for them - that matches what PHP itself does with such a declaration.
- Swept the whole family: a scan of every function, method and property signature coming from phpstorm-stubs on PHP 7.2/7.4/8.0/8.4 shows no `ObjectType('resource')` left. Besides curl, the same bug affected fileinfo, xml, pgsql, ftp, imap and openssl signatures; only functions were affected in practice, but methods, properties, class constants and enum backing types are plumbed too. The same scan for other non-existent class names in internal signatures only turns up upstream phpstorm-stubs typos (`java_*`, `zend_*`, `Socket`/`GdImage` on PHP 7.4), which are not PHPStan bugs.
@staabm

staabm commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Do we have similar problems with other type added in newer php versions?

@staabm staabm closed this Sep 5, 2026
@staabm
staabm deleted the create-pull-request/patch-enamuar branch September 5, 2026 19:26
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.

2 participants