2021-08-08 10:39:54 +02:00
< ? php
2021-12-15 04:58:32 +01:00
2021-09-23 19:29:15 +02:00
namespace Psalm\Tests\Internal\Codebase ;
2021-08-08 10:39:54 +02:00
2022-06-22 12:59:47 +02:00
use InvalidArgumentException ;
2022-06-26 00:01:12 +02:00
use PHPUnit\Framework\AssertionFailedError ;
2022-06-22 12:59:47 +02:00
use PHPUnit\Framework\ExpectationFailedException ;
2022-06-15 10:56:15 +02:00
use Psalm\Codebase ;
2022-06-13 15:10:23 +02:00
use Psalm\Internal\Analyzer\ProjectAnalyzer ;
2021-08-08 10:39:54 +02:00
use Psalm\Internal\Codebase\InternalCallMapHandler ;
2022-06-15 09:27:40 +02:00
use Psalm\Internal\Codebase\Reflection ;
2022-06-13 15:10:23 +02:00
use Psalm\Internal\Provider\FakeFileProvider ;
use Psalm\Internal\Provider\Providers ;
2022-06-15 09:27:40 +02:00
use Psalm\Internal\Type\Comparator\UnionTypeComparator ;
2022-06-13 15:10:23 +02:00
use Psalm\Tests\Internal\Provider\FakeParserCacheProvider ;
2021-12-03 20:11:20 +01:00
use Psalm\Tests\TestCase ;
2022-06-13 15:10:23 +02:00
use Psalm\Tests\TestConfig ;
2022-06-15 09:27:40 +02:00
use Psalm\Type ;
2022-12-04 09:19:57 +01:00
use ReflectionException ;
2022-06-13 15:10:23 +02:00
use ReflectionFunction ;
2022-12-04 09:19:57 +01:00
use ReflectionFunctionAbstract ;
use ReflectionMethod ;
2022-06-13 15:10:23 +02:00
use ReflectionParameter ;
2022-06-15 10:56:15 +02:00
use ReflectionType ;
2022-06-15 13:28:09 +02:00
2022-07-06 10:33:34 +02:00
use function array_shift ;
2022-06-22 15:05:24 +02:00
use function class_exists ;
2022-06-15 13:28:09 +02:00
use function count ;
use function explode ;
use function function_exists ;
use function in_array ;
2022-06-22 15:37:51 +02:00
use function is_array ;
2022-06-22 15:32:35 +02:00
use function is_int ;
2022-06-15 13:28:09 +02:00
use function json_encode ;
use function preg_match ;
use function print_r ;
2022-06-22 15:32:35 +02:00
use function strcmp ;
2022-06-15 13:28:09 +02:00
use function strncmp ;
use function strpos ;
use function substr ;
2022-07-06 10:33:34 +02:00
use function version_compare ;
2021-08-08 10:39:54 +02:00
2022-06-22 15:32:35 +02:00
use const PHP_MAJOR_VERSION ;
use const PHP_MINOR_VERSION ;
2022-07-06 10:33:34 +02:00
use const PHP_VERSION ;
2022-06-22 15:32:35 +02:00
2023-02-15 05:30:45 +01:00
/** @group callmap */
2021-12-03 20:11:20 +01:00
class InternalCallMapHandlerTest extends TestCase
2021-08-08 10:39:54 +02:00
{
2023-02-16 02:55:49 +01:00
/**
* Regex patterns for callmap entries that should be skipped .
*
* These will not be checked against reflection . This prevents a
* large ignore list for extension functions have invalid reflection
* or are not maintained .
*
* @ var list < string >
*/
private static array $skippedPatterns = [
'/\'\d$/' , // skip alternate signatures
'/^redis/' , // redis extension
'/^imagick/' , // imagick extension
'/^uopz/' , // uopz extension
];
2022-06-15 13:16:24 +02:00
/**
2022-06-22 15:32:35 +02:00
* Specify a function name as value , or a function name as key and
* an array containing the PHP versions in which to ignore this function as values .
2022-12-14 20:34:41 +01:00
*
2022-06-22 15:32:35 +02:00
* @ var array < int | string , string | list < string >>
2022-06-15 13:16:24 +02:00
*/
2022-12-16 19:58:47 +01:00
private static array $ignoredFunctions = [
2022-06-22 14:57:40 +02:00
'apcu_entry' ,
2022-06-22 13:28:17 +02:00
'array_multisort' ,
2022-12-04 09:19:57 +01:00
'arrayiterator::asort' ,
'arrayiterator::ksort' ,
'arrayiterator::offsetexists' ,
'arrayiterator::offsetget' ,
'arrayiterator::offsetset' ,
'arrayiterator::offsetunset' ,
'arrayiterator::seek' ,
'arrayiterator::setflags' ,
'arrayiterator::uasort' ,
'arrayiterator::uksort' ,
'arrayiterator::unserialize' ,
'arrayobject::__construct' ,
'arrayobject::asort' ,
'arrayobject::exchangearray' ,
'arrayobject::ksort' ,
'arrayobject::offsetexists' ,
'arrayobject::offsetget' ,
'arrayobject::offsetset' ,
'arrayobject::offsetunset' ,
'arrayobject::setiteratorclass' ,
'arrayobject::uasort' ,
'arrayobject::uksort' ,
'arrayobject::unserialize' ,
'cachingiterator::offsetexists' ,
'cachingiterator::offsetget' ,
'cachingiterator::offsetset' ,
'cachingiterator::offsetunset' ,
'callbackfilteriterator::__construct' ,
'closure::bind' ,
'closure::bindto' ,
'closure::call' ,
'closure::fromcallable' ,
'collator::asort' ,
'collator::getattribute' ,
'collator::setattribute' ,
'collator::sort' ,
'collator::sortwithsortkeys' ,
'curlfile::__construct' ,
'curlfile::setmimetype' ,
'curlfile::setpostfilename' ,
2022-06-22 13:28:17 +02:00
'date_isodate_set' ,
2022-12-04 09:19:57 +01:00
'datefmt_create' => [ '8.0' ],
'dateinterval::__construct' ,
'dateinterval::createfromdatestring' ,
'datetime::createfromformat' ,
'datetime::diff' ,
'datetime::modify' ,
'datetime::setisodate' ,
'datetime::settime' ,
'datetime::settimestamp' ,
'datetimezone::gettransitions' ,
'directoryiterator::__construct' ,
'directoryiterator::getfileinfo' ,
'directoryiterator::getpathinfo' ,
'directoryiterator::openfile' ,
'directoryiterator::seek' ,
'directoryiterator::setfileclass' ,
'directoryiterator::setinfoclass' ,
'domattr::insertbefore' ,
'domattr::isdefaultnamespace' ,
'domattr::issamenode' ,
'domattr::lookupprefix' ,
'domattr::removechild' ,
'domattr::replacechild' ,
'domcdatasection::__construct' ,
'domcomment::__construct' ,
'domdocument::createattribute' ,
'domdocument::createattributens' ,
'domdocument::createelement' ,
'domdocument::createelementns' ,
'domdocument::createtextnode' ,
'domdocument::getelementbyid' ,
'domdocument::getelementsbytagname' ,
'domdocument::getelementsbytagnamens' ,
'domdocument::importnode' ,
'domdocument::registernodeclass' ,
'domelement::__construct' ,
'domelement::getattribute' ,
'domelement::getattributenode' ,
'domelement::getattributenodens' ,
'domelement::getattributens' ,
'domelement::getelementsbytagname' ,
'domelement::getelementsbytagnamens' ,
'domelement::hasattribute' ,
'domelement::hasattributens' ,
'domelement::removeattribute' ,
'domelement::removeattributenode' ,
'domelement::removeattributens' ,
'domelement::setattribute' ,
'domelement::setattributens' ,
'domelement::setidattribute' ,
'domelement::setidattributenode' ,
'domelement::setidattributens' ,
'domimplementation::createdocument' ,
'domimplementation::createdocumenttype' ,
'domnamednodemap::getnameditem' ,
'domnamednodemap::getnameditemns' ,
'domnode::appendchild' ,
'domnode::c14n' ,
'domnode::c14nfile' ,
'domnode::insertbefore' ,
'domnode::isdefaultnamespace' ,
'domnode::issamenode' ,
'domnode::lookupprefix' ,
'domnode::removechild' ,
'domnode::replacechild' ,
'domprocessinginstruction::__construct' ,
'domtext::__construct' ,
'domxpath::__construct' ,
'domxpath::evaluate' ,
'domxpath::query' ,
'domxpath::registernamespace' ,
'domxpath::registerphpfunctions' ,
2022-06-22 13:28:17 +02:00
'easter_date' ,
2022-12-04 09:19:57 +01:00
'fiber::start' ,
'filesystemiterator::__construct' ,
'filesystemiterator::getfileinfo' ,
'filesystemiterator::getpathinfo' ,
'filesystemiterator::openfile' ,
'filesystemiterator::seek' ,
'filesystemiterator::setfileclass' ,
'filesystemiterator::setflags' ,
'filesystemiterator::setinfoclass' ,
'finfo::__construct' ,
'finfo::buffer' ,
'finfo::file' ,
'finfo::set_flags' ,
'generator::throw' ,
'globiterator::__construct' ,
'globiterator::getfileinfo' ,
'globiterator::getpathinfo' ,
'globiterator::openfile' ,
'globiterator::seek' ,
'globiterator::setfileclass' ,
'globiterator::setflags' ,
'globiterator::setinfoclass' ,
'gnupg::adddecryptkey' ,
'gnupg::addencryptkey' ,
'gnupg::addsignkey' ,
'gnupg::decrypt' ,
'gnupg::decryptverify' ,
'gnupg::encrypt' ,
'gnupg::encryptsign' ,
'gnupg::export' ,
'gnupg::import' ,
'gnupg::keyinfo' ,
'gnupg::seterrormode' ,
'gnupg::sign' ,
'gnupg::verify' ,
2022-06-22 14:57:40 +02:00
'gnupg_adddecryptkey' ,
'gnupg_addencryptkey' ,
'gnupg_addsignkey' ,
'gnupg_cleardecryptkeys' ,
'gnupg_clearencryptkeys' ,
'gnupg_clearsignkeys' ,
'gnupg_decrypt' ,
'gnupg_decryptverify' ,
'gnupg_encrypt' ,
'gnupg_encryptsign' ,
'gnupg_export' ,
'gnupg_geterror' ,
'gnupg_getprotocol' ,
'gnupg_import' ,
'gnupg_init' ,
'gnupg_keyinfo' ,
'gnupg_setarmor' ,
'gnupg_seterrormode' ,
'gnupg_setsignmode' ,
'gnupg_sign' ,
'gnupg_verify' ,
'imagefilledpolygon' ,
2022-06-22 13:28:17 +02:00
'imagegd' ,
'imagegd2' ,
'imageopenpolygon' ,
'imagepolygon' ,
2022-12-04 09:19:57 +01:00
'intlbreakiterator::getlocale' ,
'intlbreakiterator::getpartsiterator' ,
2022-06-22 14:57:40 +02:00
'intlcal_from_date_time' ,
2022-06-22 13:28:17 +02:00
'intlcal_get_weekend_transition' ,
2022-12-04 09:19:57 +01:00
'intlcalendar::add' ,
'intlcalendar::createinstance' ,
'intlcalendar::fielddifference' ,
'intlcalendar::fromdatetime' ,
'intlcalendar::getkeywordvaluesforlocale' ,
'intlcalendar::getlocale' ,
'intlcalendar::getweekendtransition' ,
'intlcalendar::isweekend' ,
'intlcalendar::roll' ,
'intlcalendar::setlenient' ,
'intlcalendar::setminimaldaysinfirstweek' ,
'intlcalendar::setrepeatedwalltimeoption' ,
'intlcalendar::setskippedwalltimeoption' ,
'intlcalendar::settime' ,
'intlcalendar::settimezone' ,
'intlchar::charage' ,
'intlchar::chardigitvalue' ,
'intlchar::chardirection' ,
'intlchar::charfromname' ,
'intlchar::charmirror' ,
'intlchar::charname' ,
'intlchar::chartype' ,
'intlchar::chr' ,
'intlchar::digit' ,
'intlchar::enumcharnames' ,
'intlchar::enumchartypes' ,
'intlchar::foldcase' ,
'intlchar::fordigit' ,
'intlchar::getbidipairedbracket' ,
'intlchar::getblockcode' ,
'intlchar::getcombiningclass' ,
'intlchar::getfc_nfkc_closure' ,
'intlchar::getintpropertyvalue' ,
'intlchar::getnumericvalue' ,
'intlchar::getpropertyname' ,
'intlchar::getpropertyvaluename' ,
'intlchar::hasbinaryproperty' ,
'intlchar::isalnum' ,
'intlchar::isalpha' ,
'intlchar::isbase' ,
'intlchar::isblank' ,
'intlchar::iscntrl' ,
'intlchar::isdefined' ,
'intlchar::isdigit' ,
'intlchar::isgraph' ,
'intlchar::isidignorable' ,
'intlchar::isidpart' ,
'intlchar::isidstart' ,
'intlchar::isisocontrol' ,
'intlchar::isjavaidpart' ,
'intlchar::isjavaidstart' ,
'intlchar::isjavaspacechar' ,
'intlchar::islower' ,
'intlchar::ismirrored' ,
'intlchar::isprint' ,
'intlchar::ispunct' ,
'intlchar::isspace' ,
'intlchar::istitle' ,
'intlchar::isualphabetic' ,
'intlchar::isulowercase' ,
'intlchar::isupper' ,
'intlchar::isuuppercase' ,
'intlchar::isuwhitespace' ,
'intlchar::iswhitespace' ,
'intlchar::isxdigit' ,
'intlchar::ord' ,
'intlchar::tolower' ,
'intlchar::totitle' ,
'intlchar::toupper' ,
'intlcodepointbreakiterator::following' ,
'intlcodepointbreakiterator::getlocale' ,
'intlcodepointbreakiterator::getpartsiterator' ,
'intlcodepointbreakiterator::isboundary' ,
'intlcodepointbreakiterator::next' ,
'intlcodepointbreakiterator::preceding' ,
'intlexception::__construct' ,
2022-06-22 12:59:47 +02:00
'intlgregcal_create_instance' ,
'intlgregcal_is_leap_year' ,
2022-12-04 09:19:57 +01:00
'intlgregoriancalendar::__construct' ,
'intlgregoriancalendar::add' ,
'intlgregoriancalendar::createinstance' ,
'intlgregoriancalendar::fielddifference' ,
'intlgregoriancalendar::fromdatetime' ,
'intlgregoriancalendar::getkeywordvaluesforlocale' ,
'intlgregoriancalendar::getlocale' ,
'intlgregoriancalendar::getweekendtransition' ,
'intlgregoriancalendar::isweekend' ,
'intlgregoriancalendar::roll' ,
'intlgregoriancalendar::setgregorianchange' ,
'intlgregoriancalendar::setlenient' ,
'intlgregoriancalendar::setminimaldaysinfirstweek' ,
'intlgregoriancalendar::setrepeatedwalltimeoption' ,
'intlgregoriancalendar::setskippedwalltimeoption' ,
'intlgregoriancalendar::settime' ,
'intlgregoriancalendar::settimezone' ,
'intlrulebasedbreakiterator::__construct' ,
'intlrulebasedbreakiterator::getlocale' ,
'intlrulebasedbreakiterator::getpartsiterator' ,
'intltimezone::countequivalentids' ,
'intltimezone::createtimezone' ,
'intltimezone::createtimezoneidenumeration' ,
'intltimezone::fromdatetimezone' ,
'intltimezone::getcanonicalid' ,
'intltimezone::getdisplayname' ,
'intltimezone::getequivalentid' ,
'intltimezone::getidforwindowsid' ,
'intltimezone::getoffset' ,
'intltimezone::getregion' ,
'intltimezone::getwindowsid' ,
'intltimezone::hassamerules' ,
2022-06-22 12:59:47 +02:00
'intltz_create_enumeration' ,
2022-06-22 13:28:17 +02:00
'intltz_get_canonical_id' ,
'intltz_get_display_name' ,
2022-12-04 09:19:57 +01:00
'iteratoriterator::__construct' ,
'jsonexception::__construct' ,
'limititerator::__construct' ,
'limititerator::seek' ,
'locale::filtermatches' ,
'locale::getdisplaylanguage' ,
'locale::getdisplayname' ,
'locale::getdisplayregion' ,
'locale::getdisplayscript' ,
'locale::getdisplayvariant' ,
2022-06-22 13:28:17 +02:00
'lzf_compress' ,
'lzf_decompress' ,
2022-06-22 14:57:40 +02:00
'mailparse_msg_extract_part' ,
'mailparse_msg_extract_part_file' ,
'mailparse_msg_extract_whole_part_file' ,
'mailparse_msg_free' ,
'mailparse_msg_get_part' ,
'mailparse_msg_get_part_data' ,
'mailparse_msg_get_structure' ,
'mailparse_msg_parse' ,
'mailparse_stream_encode' ,
2022-12-04 09:19:57 +01:00
'memcache::add' ,
'memcache::addserver' ,
'memcache::append' ,
'memcache::cas' ,
'memcache::connect' ,
'memcache::decrement' ,
'memcache::delete' ,
'memcache::findserver' ,
'memcache::flush' ,
'memcache::getserverstatus' ,
'memcache::increment' ,
'memcache::pconnect' ,
'memcache::prepend' ,
'memcache::replace' ,
'memcache::set' ,
'memcache::setfailurecallback' ,
'memcache::setserverparams' ,
2022-06-22 14:57:40 +02:00
'memcache_add' ,
'memcache_add_server' ,
'memcache_append' ,
'memcache_cas' ,
'memcache_close' ,
'memcache_connect' ,
'memcache_decrement' ,
'memcache_delete' ,
'memcache_flush' ,
'memcache_get_extended_stats' ,
'memcache_get_server_status' ,
'memcache_get_stats' ,
'memcache_get_version' ,
'memcache_increment' ,
'memcache_pconnect' ,
'memcache_prepend' ,
'memcache_replace' ,
'memcache_set' ,
'memcache_set_compress_threshold' ,
'memcache_set_failure_callback' ,
'memcache_set_server_params' ,
2023-02-15 20:33:34 +01:00
'memcached::cas' , // memcached 3.2.0 has incorrect reflection
'memcached::casbykey' , // memcached 3.2.0 has incorrect reflection
2022-12-04 09:19:57 +01:00
'memcachepool::add' ,
'memcachepool::addserver' ,
'memcachepool::append' ,
'memcachepool::cas' ,
'memcachepool::connect' ,
'memcachepool::decrement' ,
'memcachepool::delete' ,
'memcachepool::findserver' ,
'memcachepool::flush' ,
'memcachepool::get' ,
'memcachepool::getserverstatus' ,
'memcachepool::increment' ,
'memcachepool::prepend' ,
'memcachepool::replace' ,
'memcachepool::set' ,
'memcachepool::setcompressthreshold' ,
'memcachepool::setfailurecallback' ,
'memcachepool::setserverparams' ,
'messageformatter::format' ,
'messageformatter::formatmessage' ,
'messageformatter::parse' ,
'messageformatter::parsemessage' ,
'mongodb\bson\binary::__construct' ,
'multipleiterator::attachiterator' ,
'mysqli::poll' ,
2022-06-22 13:28:17 +02:00
'mysqli_poll' ,
'mysqli_real_connect' ,
2022-12-04 09:19:57 +01:00
'mysqli_stmt::__construct' ,
'mysqli_stmt::bind_param' ,
2022-06-22 13:28:17 +02:00
'mysqli_stmt_bind_param' ,
2022-12-04 09:19:57 +01:00
'normalizer::getrawdecomposition' ,
'normalizer::isnormalized' ,
'normalizer::normalize' ,
2022-06-22 13:28:17 +02:00
'normalizer_get_raw_decomposition' ,
2022-12-04 09:19:57 +01:00
'numberformatter::formatcurrency' ,
'numberformatter::getattribute' ,
'numberformatter::getsymbol' ,
'numberformatter::gettextattribute' ,
'numberformatter::parse' ,
'numberformatter::parsecurrency' ,
'numberformatter::setattribute' ,
'numberformatter::setsymbol' ,
'numberformatter::settextattribute' ,
'oauth::fetch' ,
'oauth::getaccesstoken' ,
'oauth::setcapath' ,
'oauth::settimeout' ,
'oauth::settimestamp' ,
'oauthprovider::consumerhandler' ,
'oauthprovider::isrequesttokenendpoint' ,
'oauthprovider::timestampnoncehandler' ,
'oauthprovider::tokenhandler' ,
2022-06-22 14:57:40 +02:00
'oci_collection_append' ,
'oci_collection_assign' ,
'oci_collection_element_assign' ,
'oci_collection_element_get' ,
'oci_collection_max' ,
'oci_collection_size' ,
'oci_collection_trim' ,
'oci_fetch_object' ,
'oci_field_is_null' ,
'oci_field_name' ,
'oci_field_precision' ,
'oci_field_scale' ,
'oci_field_size' ,
'oci_field_type' ,
'oci_field_type_raw' ,
'oci_free_collection' ,
'oci_free_descriptor' ,
'oci_lob_append' ,
'oci_lob_eof' ,
'oci_lob_erase' ,
'oci_lob_export' ,
'oci_lob_flush' ,
'oci_lob_import' ,
'oci_lob_load' ,
'oci_lob_read' ,
'oci_lob_rewind' ,
'oci_lob_save' ,
'oci_lob_seek' ,
'oci_lob_size' ,
'oci_lob_tell' ,
'oci_lob_truncate' ,
'oci_lob_write' ,
'oci_register_taf_callback' ,
'oci_result' ,
'ocigetbufferinglob' ,
'ocisetbufferinglob' ,
'odbc_procedurecolumns' ,
'odbc_procedures' ,
'odbc_result' ,
2022-06-22 13:28:17 +02:00
'openssl_pkcs7_read' ,
2022-12-04 09:19:57 +01:00
'pdo::__construct' ,
'pdo::exec' ,
'pdo::prepare' ,
'pdo::quote' ,
'pdostatement::bindcolumn' ,
'pdostatement::bindparam' ,
'pdostatement::fetchobject' ,
'pdostatement::getattribute' ,
'phar::__construct' ,
'phar::addemptydir' ,
'phar::addfile' ,
'phar::addfromstring' ,
'phar::buildfromdirectory' ,
'phar::buildfromiterator' ,
'phar::cancompress' ,
'phar::copy' ,
'phar::count' ,
'phar::createdefaultstub' ,
'phar::delete' ,
'phar::extractto' ,
'phar::mapphar' ,
'phar::mount' ,
'phar::mungserver' ,
'phar::offsetexists' ,
'phar::offsetget' ,
'phar::offsetset' ,
'phar::offsetunset' ,
'phar::running' ,
'phar::setdefaultstub' ,
'phar::setsignaturealgorithm' ,
'phar::unlinkarchive' ,
'phar::webphar' ,
'phardata::__construct' ,
'phardata::addemptydir' ,
'phardata::addfile' ,
'phardata::addfromstring' ,
'phardata::buildfromdirectory' ,
'phardata::buildfromiterator' ,
'phardata::copy' ,
'phardata::delete' ,
'phardata::extractto' ,
'phardata::offsetexists' ,
'phardata::offsetget' ,
'phardata::offsetset' ,
'phardata::offsetunset' ,
'phardata::setdefaultstub' ,
'phardata::setsignaturealgorithm' ,
'pharfileinfo::__construct' ,
'pharfileinfo::chmod' ,
'pharfileinfo::iscompressed' ,
'recursivearrayiterator::asort' ,
'recursivearrayiterator::ksort' ,
'recursivearrayiterator::offsetexists' ,
'recursivearrayiterator::offsetget' ,
'recursivearrayiterator::offsetset' ,
'recursivearrayiterator::offsetunset' ,
'recursivearrayiterator::seek' ,
'recursivearrayiterator::setflags' ,
'recursivearrayiterator::uasort' ,
'recursivearrayiterator::uksort' ,
'recursivearrayiterator::unserialize' ,
'recursivecachingiterator::__construct' ,
'recursivecachingiterator::offsetexists' ,
'recursivecachingiterator::offsetget' ,
'recursivecachingiterator::offsetset' ,
'recursivecachingiterator::offsetunset' ,
'recursivecallbackfilteriterator::__construct' ,
'recursivedirectoryiterator::__construct' ,
'recursivedirectoryiterator::getfileinfo' ,
'recursivedirectoryiterator::getpathinfo' ,
'recursivedirectoryiterator::haschildren' ,
'recursivedirectoryiterator::openfile' ,
'recursivedirectoryiterator::seek' ,
'recursivedirectoryiterator::setfileclass' ,
'recursivedirectoryiterator::setflags' ,
'recursivedirectoryiterator::setinfoclass' ,
'recursiveiteratoriterator::__construct' ,
'recursiveiteratoriterator::setmaxdepth' ,
'recursiveregexiterator::__construct' ,
'recursiveregexiterator::setflags' ,
'recursiveregexiterator::setmode' ,
'recursiveregexiterator::setpregflags' ,
'recursivetreeiterator::__construct' ,
'recursivetreeiterator::setmaxdepth' ,
'recursivetreeiterator::setpostfix' ,
'recursivetreeiterator::setprefixpart' ,
'reflectionclass::__construct' ,
'reflectionclass::implementsinterface' ,
'reflectionclassconstant::__construct' ,
'reflectionfunction::__construct' ,
'reflectiongenerator::__construct' ,
'reflectionmethod::setaccessible' ,
'reflectionobject::__construct' ,
'reflectionobject::getconstants' ,
'reflectionobject::getreflectionconstants' ,
'reflectionobject::implementsinterface' ,
'reflectionparameter::__construct' ,
'reflectionproperty::__construct' ,
'reflectionproperty::setaccessible' ,
'regexiterator::__construct' ,
'regexiterator::setflags' ,
'regexiterator::setmode' ,
'regexiterator::setpregflags' ,
'resourcebundle::__construct' ,
'resourcebundle::create' ,
'resourcebundle::getlocales' ,
'sessionhandler::gc' ,
'sessionhandler::open' ,
'simplexmlelement::__construct' ,
'simplexmlelement::addattribute' ,
'simplexmlelement::addchild' ,
'simplexmlelement::attributes' ,
'simplexmlelement::children' ,
'simplexmlelement::getdocnamespaces' ,
'simplexmlelement::registerxpathnamespace' ,
'simplexmlelement::xpath' ,
'spldoublylinkedlist::add' ,
'spldoublylinkedlist::offsetset' ,
'spldoublylinkedlist::setiteratormode' ,
'spldoublylinkedlist::unserialize' ,
'splfileinfo::__construct' ,
'splfileinfo::getfileinfo' ,
'splfileinfo::getpathinfo' ,
'splfileinfo::openfile' ,
'splfileinfo::setfileclass' ,
'splfileinfo::setinfoclass' ,
'splfileobject::__construct' ,
'splfileobject::fgetcsv' ,
'splfileobject::flock' ,
'splfileobject::fputcsv' ,
'splfileobject::fseek' ,
'splfileobject::fwrite' ,
'splfileobject::getfileinfo' ,
'splfileobject::getpathinfo' ,
'splfileobject::openfile' ,
'splfileobject::seek' ,
'splfileobject::setcsvcontrol' ,
'splfileobject::setfileclass' ,
'splfileobject::setinfoclass' ,
'splfileobject::setmaxlinelen' ,
'splfixedarray::fromarray' ,
'splfixedarray::offsetset' ,
'splmaxheap::compare' ,
'splminheap::compare' ,
'splobjectstorage::addall' ,
'splobjectstorage::attach' ,
'splobjectstorage::count' ,
'splobjectstorage::offsetset' ,
'splobjectstorage::removeall' ,
'splobjectstorage::removeallexcept' ,
'splobjectstorage::setinfo' ,
'splobjectstorage::unserialize' ,
'splpriorityqueue::compare' ,
'splqueue::offsetset' ,
'splqueue::unserialize' ,
'splstack::add' ,
'splstack::offsetset' ,
'splstack::unserialize' ,
'spltempfileobject::__construct' ,
'spltempfileobject::fgetcsv' ,
'spltempfileobject::flock' ,
'spltempfileobject::fputcsv' ,
'spltempfileobject::fseek' ,
'spltempfileobject::fwrite' ,
'spltempfileobject::getfileinfo' ,
'spltempfileobject::getpathinfo' ,
'spltempfileobject::openfile' ,
'spltempfileobject::seek' ,
'spltempfileobject::setcsvcontrol' ,
'spltempfileobject::setfileclass' ,
'spltempfileobject::setinfoclass' ,
'spltempfileobject::setmaxlinelen' ,
'sqlite3::__construct' ,
'sqlite3::open' ,
2022-06-22 14:57:40 +02:00
'sqlsrv_connect' ,
'sqlsrv_errors' ,
'sqlsrv_fetch_array' ,
'sqlsrv_fetch_object' ,
'sqlsrv_get_field' ,
'sqlsrv_prepare' ,
'sqlsrv_query' ,
'sqlsrv_server_info' ,
2022-11-06 06:58:50 +01:00
'ssh2_forward_accept' ,
2022-12-04 09:19:57 +01:00
'transliterator::transliterate' ,
'uconverter::convert' ,
'uconverter::fromucallback' ,
'uconverter::reasontext' ,
'uconverter::transcode' ,
2022-06-22 14:57:40 +02:00
'xdiff_file_bdiff' ,
'xdiff_file_bdiff_size' ,
'xdiff_file_diff' ,
'xdiff_file_diff_binary' ,
'xdiff_file_merge3' ,
'xdiff_file_rabdiff' ,
'xdiff_string_bdiff' ,
'xdiff_string_bdiff_size' ,
'xdiff_string_bpatch' ,
'xdiff_string_diff' ,
'xdiff_string_diff_binary' ,
'xdiff_string_merge3' ,
'xdiff_string_patch' ,
'xdiff_string_patch_binary' ,
'xdiff_string_rabdiff' ,
2022-12-04 09:19:57 +01:00
'xmlreader::getattributens' ,
'xmlreader::movetoattributens' ,
'xmlreader::next' ,
'xmlreader::open' ,
'xmlreader::xml' ,
'xsltprocessor::registerphpfunctions' ,
'xsltprocessor::transformtodoc' ,
'ziparchive::iscompressionmethodsupported' ,
'ziparchive::isencryptionmethodsupported' ,
'ziparchive::setcompressionindex' ,
'ziparchive::setcompressionname' ,
'ziparchive::setencryptionindex' ,
2022-06-14 15:16:13 +02:00
];
2022-06-14 15:43:13 +02:00
2022-06-26 00:01:12 +02:00
/**
* List of function names to ignore only for return type checks .
*
2022-12-04 09:19:57 +01:00
* @ var array < int | string , string | list < string >>
2022-06-26 00:01:12 +02:00
*/
2022-12-16 19:58:47 +01:00
private static array $ignoredReturnTypeOnlyFunctions = [
2022-12-04 09:19:57 +01:00
'appenditerator::getinneriterator' => [ '8.1' , '8.2' ],
'appenditerator::getiteratorindex' => [ '8.1' , '8.2' ],
'arrayobject::getiterator' => [ '8.1' , '8.2' ],
'cachingiterator::getinneriterator' => [ '8.1' , '8.2' ],
'callbackfilteriterator::getinneriterator' => [ '8.1' , '8.2' ],
'curl_multi_getcontent' ,
'datetime::add' => [ '8.1' , '8.2' ],
2023-02-12 08:24:43 +01:00
'datetime::createfromimmutable' => [ '8.1' ],
2022-12-04 09:19:57 +01:00
'datetime::createfrominterface' ,
'datetime::setdate' => [ '8.1' , '8.2' ],
'datetime::settimezone' => [ '8.1' , '8.2' ],
'datetime::sub' => [ '8.1' , '8.2' ],
'datetimeimmutable::createfrominterface' ,
'fiber::getcurrent' ,
'filteriterator::getinneriterator' => [ '8.1' , '8.2' ],
'infiniteiterator::getinneriterator' => [ '8.1' , '8.2' ],
'iteratoriterator::getinneriterator' => [ '8.1' , '8.2' ],
'limititerator::getinneriterator' => [ '8.1' , '8.2' ],
'locale::canonicalize' => [ '8.1' , '8.2' ],
'locale::getallvariants' => [ '8.1' , '8.2' ],
'locale::getkeywords' => [ '8.1' , '8.2' ],
'locale::getprimarylanguage' => [ '8.1' , '8.2' ],
'locale::getregion' => [ '8.1' , '8.2' ],
'locale::getscript' => [ '8.1' , '8.2' ],
'locale::parselocale' => [ '8.1' , '8.2' ],
'messageformatter::create' => [ '8.1' , '8.2' ],
'multipleiterator::current' => [ '8.1' , '8.2' ],
'mysqli::get_charset' => [ '8.1' , '8.2' ],
'mysqli_stmt::get_warnings' => [ '8.1' , '8.2' ],
2022-06-26 00:01:12 +02:00
'mysqli_stmt_get_warnings' ,
'mysqli_stmt_insert_id' ,
2022-12-04 09:19:57 +01:00
'norewinditerator::getinneriterator' => [ '8.1' , '8.2' ],
2022-06-26 00:01:12 +02:00
'passthru' ,
2022-12-04 09:19:57 +01:00
'recursivecachingiterator::getinneriterator' => [ '8.1' , '8.2' ],
'recursivecallbackfilteriterator::getinneriterator' => [ '8.1' , '8.2' ],
'recursivefilteriterator::getinneriterator' => [ '8.1' , '8.2' ],
'recursiveregexiterator::getinneriterator' => [ '8.1' , '8.2' ],
'reflectionclass::getstaticproperties' => [ '8.1' , '8.2' ],
'reflectionclass::newinstanceargs' => [ '8.1' , '8.2' ],
'reflectionfunction::getclosurescopeclass' => [ '8.1' , '8.2' ],
'reflectionfunction::getclosurethis' => [ '8.1' , '8.2' ],
'reflectionmethod::getclosurescopeclass' => [ '8.1' , '8.2' ],
'reflectionmethod::getclosurethis' => [ '8.1' , '8.2' ],
'reflectionobject::getstaticproperties' => [ '8.1' , '8.2' ],
'reflectionobject::newinstanceargs' => [ '8.1' , '8.2' ],
'regexiterator::getinneriterator' => [ '8.1' , '8.2' ],
2023-01-15 10:57:22 +01:00
'register_shutdown_function' => [ '8.0' , '8.1' ],
2022-12-04 09:19:57 +01:00
'splfileobject::fscanf' => [ '8.1' , '8.2' ],
'spltempfileobject::fscanf' => [ '8.1' , '8.2' ],
'xsltprocessor::transformtoxml' => [ '8.1' , '8.2' ],
];
/**
* List of function names to ignore because they cannot be reflected .
*
* These could be truly inaccessible , or they could be functions removed in newer PHP versions .
* Removed functions should be removed from CallMap and added to the appropriate delta .
*
* @ var array < int | string , string | list < string >>
*/
2022-12-16 19:58:47 +01:00
private static array $ignoredUnreflectableFunctions = [
2022-12-04 09:19:57 +01:00
'closure::__invoke' ,
'curlfile::__wakeup' ,
'domimplementation::__construct' ,
'generator::__wakeup' ,
'gmp::__construct' ,
'gmp::__tostring' ,
'intliterator::__construct' ,
'mysqli::disable_reads_from_master' ,
'mysqli::rpl_query_type' ,
'mysqli::send_query' ,
'mysqli::set_local_infile_default' ,
'mysqli::set_local_infile_handler' ,
'mysqli_driver::embedded_server_end' ,
'mysqli_driver::embedded_server_start' ,
'pdo::__sleep' ,
'pdo::__wakeup' ,
'pdo::cubrid_schema' ,
'pdo::pgsqlcopyfromarray' ,
'pdo::pgsqlcopyfromfile' ,
'pdo::pgsqlcopytoarray' ,
'pdo::pgsqlcopytofile' ,
'pdo::pgsqlgetnotify' ,
'pdo::pgsqlgetpid' ,
'pdo::pgsqllobcreate' ,
'pdo::pgsqllobopen' ,
'pdo::pgsqllobunlink' ,
'pdo::sqlitecreateaggregate' ,
'pdo::sqlitecreatecollation' ,
'pdo::sqlitecreatefunction' ,
'pdostatement::__sleep' ,
'pdostatement::__wakeup' ,
'phar::compressallfilesbzip2' ,
'phar::compressallfilesgz' ,
'phar::uncompressallfiles' ,
'pharfileinfo::iscompressedbzip2' ,
'pharfileinfo::iscompressedgz' ,
'pharfileinfo::setcompressedbzip2' ,
'pharfileinfo::setcompressedgz' ,
'pharfileinfo::setuncompressed' ,
'simplexmlelement::__get' ,
'simplexmlelement::offsetexists' ,
'simplexmlelement::offsetget' ,
'simplexmlelement::offsetset' ,
'simplexmlelement::offsetunset' ,
'spldoublylinkedlist::__construct' ,
'splfileinfo::__wakeup' ,
'splfixedarray::current' ,
'splfixedarray::key' ,
'splfixedarray::next' ,
'splfixedarray::rewind' ,
'splfixedarray::valid' ,
'splheap::__construct' ,
'splmaxheap::__construct' ,
'splobjectstorage::__construct' ,
'splpriorityqueue::__construct' ,
'splstack::__construct' ,
'weakmap::__construct' ,
'weakmap::current' ,
'weakmap::key' ,
'weakmap::next' ,
'weakmap::rewind' ,
'weakmap::valid' ,
2022-06-26 00:01:12 +02:00
];
2022-12-16 19:58:47 +01:00
private static Codebase $codebase ;
2022-06-15 13:13:24 +02:00
2022-06-15 10:56:15 +02:00
public static function setUpBeforeClass () : void
{
$project_analyzer = new ProjectAnalyzer (
new TestConfig (),
new Providers (
new FakeFileProvider (),
2022-12-18 17:15:15 +01:00
new FakeParserCacheProvider (),
),
2022-06-15 10:56:15 +02:00
);
self :: $codebase = $project_analyzer -> getCodebase ();
}
2022-06-22 15:32:35 +02:00
public function testIgnoresAreSortedAndUnique () : void
2022-06-22 14:57:40 +02:00
{
2022-06-22 15:32:35 +02:00
$previousFunction = " " ;
foreach ( self :: $ignoredFunctions as $key => $value ) {
2022-06-22 15:37:51 +02:00
/** @var string */
2022-06-22 15:32:35 +02:00
$function = is_int ( $key ) ? $value : $key ;
2022-09-22 00:41:06 +02:00
$diff = strcmp ( $function , $previousFunction );
2022-12-04 09:19:57 +01:00
$this -> assertGreaterThan ( 0 , $diff , " ' { $function } ' should come before ' { $previousFunction } ' in InternalCallMapHandlerTest:: \$ ignoredFunctions " );
2022-09-22 00:41:06 +02:00
2022-06-22 15:32:35 +02:00
$previousFunction = $function ;
}
2022-06-22 14:57:40 +02:00
}
2021-08-08 10:39:54 +02:00
/**
2022-01-11 16:45:29 +01:00
* @ covers \Psalm\Internal\Codebase\InternalCallMapHandler :: getCallMap
2021-08-08 10:39:54 +02:00
*/
public function testGetcallmapReturnsAValidCallmap () : void
{
$callMap = InternalCallMapHandler :: getCallMap ();
self :: assertArrayKeysAreStrings ( $callMap , " Returned CallMap has non-string keys " );
self :: assertArrayValuesAreArrays ( $callMap , " Returned CallMap has non-array values " );
foreach ( $callMap as $function => $signature ) {
self :: assertArrayKeysAreZeroOrString ( $signature , " Function " . $function . " in returned CallMap has invalid keys " );
self :: assertArrayValuesAreStrings ( $signature , " Function " . $function . " in returned CallMap has non-string values " );
foreach ( $signature as $type ) {
self :: assertStringIsParsableType ( $type , " Function " . $function . " in returned CallMap contains invalid type declaration " . $type );
}
}
}
2022-06-13 15:10:23 +02:00
2022-06-15 13:23:32 +02:00
/**
2022-11-12 02:14:21 +01:00
* @ return iterable < string , array { 0 : callable - string , 1 : array < int | string , string > } >
2022-06-15 13:23:32 +02:00
*/
2022-06-13 15:10:23 +02:00
public function callMapEntryProvider () : iterable
{
2022-06-15 14:46:50 +02:00
/**
* This call is needed since InternalCallMapHandler uses the singleton that is initialized by it .
**/
new ProjectAnalyzer (
2022-06-13 15:10:23 +02:00
new TestConfig (),
new Providers (
new FakeFileProvider (),
2022-12-18 17:15:15 +01:00
new FakeParserCacheProvider (),
),
2022-06-13 15:10:23 +02:00
);
$callMap = InternalCallMapHandler :: getCallMap ();
2022-06-15 13:28:09 +02:00
foreach ( $callMap as $function => $entry ) {
2023-02-16 02:55:49 +01:00
foreach ( static :: $skippedPatterns as $skipPattern ) {
if ( preg_match ( $skipPattern , $function )) {
continue 2 ;
}
}
// Skip functions with alternate signatures
if ( isset ( $callMap [ " $function '1 " ])) {
continue ;
}
2022-12-04 09:19:57 +01:00
$classNameEnd = strpos ( $function , '::' );
if ( $classNameEnd !== false ) {
$className = substr ( $function , 0 , $classNameEnd );
if ( ! class_exists ( $className , false )) {
continue ;
}
} elseif ( ! function_exists ( $function )) {
2022-06-15 09:11:14 +02:00
continue ;
}
2022-12-04 09:19:57 +01:00
2022-06-13 15:10:23 +02:00
yield " $function : " . json_encode ( $entry ) => [ $function , $entry ];
}
}
2022-06-22 15:05:24 +02:00
private function isIgnored ( string $functionName ) : bool
2022-06-22 12:59:47 +02:00
{
if ( in_array ( $functionName , self :: $ignoredFunctions )) {
return true ;
}
2022-06-22 15:32:35 +02:00
2022-06-22 15:37:51 +02:00
if ( isset ( self :: $ignoredFunctions [ $functionName ])
&& is_array ( self :: $ignoredFunctions [ $functionName ])
&& in_array ( PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION , self :: $ignoredFunctions [ $functionName ])) {
2022-06-22 15:32:35 +02:00
return true ;
}
2022-06-22 12:59:47 +02:00
return false ;
}
2022-06-26 00:01:12 +02:00
private function isReturnTypeOnlyIgnored ( string $functionName ) : bool
{
2022-12-04 09:19:57 +01:00
if ( in_array ( $functionName , static :: $ignoredReturnTypeOnlyFunctions , true )) {
return true ;
}
if ( isset ( self :: $ignoredReturnTypeOnlyFunctions [ $functionName ])
&& is_array ( self :: $ignoredReturnTypeOnlyFunctions [ $functionName ])
&& in_array ( PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION , self :: $ignoredReturnTypeOnlyFunctions [ $functionName ])) {
return true ;
}
return false ;
}
private function isUnreflectableIgnored ( string $functionName ) : bool
{
if ( in_array ( $functionName , static :: $ignoredUnreflectableFunctions , true )) {
return true ;
}
if ( isset ( self :: $ignoredUnreflectableFunctions [ $functionName ])
&& is_array ( self :: $ignoredUnreflectableFunctions [ $functionName ])
&& in_array ( PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION , self :: $ignoredUnreflectableFunctions [ $functionName ])) {
return true ;
}
return false ;
2022-06-26 00:01:12 +02:00
}
2022-06-22 12:59:47 +02:00
/**
2022-06-22 15:32:35 +02:00
* @ depends testIgnoresAreSortedAndUnique
2022-06-22 13:28:17 +02:00
* @ depends testGetcallmapReturnsAValidCallmap
2022-06-22 12:59:47 +02:00
* @ dataProvider callMapEntryProvider
2022-06-22 13:28:17 +02:00
* @ coversNothing
* @ psalm - param callable - string $functionName
2022-06-26 00:01:12 +02:00
* @ param array < int | string , string > $callMapEntry
2022-06-22 12:59:47 +02:00
*/
public function testIgnoredFunctionsStillFail ( string $functionName , array $callMapEntry ) : void
{
2022-06-26 00:01:12 +02:00
$functionIgnored = $this -> isIgnored ( $functionName );
2022-12-04 09:19:57 +01:00
$unreflectableIgnored = $this -> isUnreflectableIgnored ( $functionName );
if ( ! $functionIgnored && ! $this -> isReturnTypeOnlyIgnored ( $functionName ) && ! $unreflectableIgnored ) {
2022-06-22 12:59:47 +02:00
// Dummy assertion to mark it as passed
$this -> assertTrue ( true );
return ;
}
2022-12-04 09:19:57 +01:00
$function = $this -> getReflectionFunction ( $functionName );
if ( $unreflectableIgnored && $function !== null ) {
$this -> fail ( " Remove ' { $functionName } ' from InternalCallMapHandlerTest:: \$ ignoredUnreflectableFunctions " );
} elseif ( $function === null ) {
$this -> assertTrue ( true );
return ;
}
2022-06-26 00:01:12 +02:00
/** @var string $entryReturnType */
$entryReturnType = array_shift ( $callMapEntry );
if ( $functionIgnored ) {
try {
/** @var array<string, string> $callMapEntry */
$this -> assertEntryParameters ( $function , $callMapEntry );
$this -> assertEntryReturnType ( $function , $entryReturnType );
} catch ( AssertionFailedError $e ) {
$this -> assertTrue ( true );
return ;
} catch ( ExpectationFailedException $e ) {
$this -> assertTrue ( true );
return ;
}
$this -> fail ( " Remove ' { $functionName } ' from InternalCallMapHandlerTest:: \$ ignoredFunctions " );
}
2022-06-22 12:59:47 +02:00
try {
2022-06-26 00:01:12 +02:00
$this -> assertEntryReturnType ( $function , $entryReturnType );
} catch ( AssertionFailedError $e ) {
$this -> assertTrue ( true );
return ;
2022-06-22 15:05:24 +02:00
} catch ( ExpectationFailedException $e ) {
2022-06-26 00:01:12 +02:00
$this -> assertTrue ( true );
return ;
2022-06-22 12:59:47 +02:00
}
2022-06-26 00:01:12 +02:00
$this -> fail ( " Remove ' { $functionName } ' from InternalCallMapHandlerTest:: \$ ignoredReturnTypeOnlyFunctions " );
2022-06-22 12:59:47 +02:00
}
2022-06-13 15:10:23 +02:00
/**
* This function will test functions that are in the callmap AND currently defined
2022-12-14 20:34:41 +01:00
*
2022-06-13 15:10:23 +02:00
* @ coversNothing
* @ depends testGetcallmapReturnsAValidCallmap
2022-06-22 15:32:35 +02:00
* @ depends testIgnoresAreSortedAndUnique
2022-06-13 15:10:23 +02:00
* @ dataProvider callMapEntryProvider
2022-06-22 13:28:17 +02:00
* @ psalm - param callable - string $functionName
2022-06-26 00:01:12 +02:00
* @ param array < int | string , string > $callMapEntry
2022-06-13 15:10:23 +02:00
*/
public function testCallMapCompliesWithReflection ( string $functionName , array $callMapEntry ) : void
{
2022-06-22 12:59:47 +02:00
if ( $this -> isIgnored ( $functionName )) {
2022-06-15 10:56:15 +02:00
$this -> markTestSkipped ( " Function $functionName is ignored in config " );
}
2022-06-15 15:05:06 +02:00
2022-12-04 09:19:57 +01:00
$function = $this -> getReflectionFunction ( $functionName );
if ( $function === null ) {
if ( ! $this -> isUnreflectableIgnored ( $functionName )) {
$this -> fail ( 'Unable to reflect method. Add name to $ignoredUnreflectableFunctions if exists in latest PHP version.' );
}
return ;
}
2022-06-26 00:01:12 +02:00
/** @var string $entryReturnType */
$entryReturnType = array_shift ( $callMapEntry );
2022-06-15 15:05:06 +02:00
/** @var array<string, string> $callMapEntry */
2022-06-26 00:01:12 +02:00
$this -> assertEntryParameters ( $function , $callMapEntry );
if ( ! $this -> isReturnTypeOnlyIgnored ( $functionName )) {
$this -> assertEntryReturnType ( $function , $entryReturnType );
}
2022-06-13 15:10:23 +02:00
}
2022-12-04 09:19:57 +01:00
/**
* Returns the correct reflection type for function or method name .
*/
private function getReflectionFunction ( string $functionName ) : ? ReflectionFunctionAbstract
{
try {
if ( strpos ( $functionName , '::' ) !== false ) {
return new ReflectionMethod ( $functionName );
}
/** @var callable-string $functionName */
return new ReflectionFunction ( $functionName );
} catch ( ReflectionException $e ) {
return null ;
}
}
2022-06-15 14:46:50 +02:00
/**
2022-06-26 00:01:12 +02:00
* @ param array < string , string > $entryParameters
2022-06-15 14:46:50 +02:00
*/
2022-12-04 09:19:57 +01:00
private function assertEntryParameters ( ReflectionFunctionAbstract $function , array $entryParameters ) : void
2022-06-13 15:10:23 +02:00
{
/**
* Parse the parameter names from the map .
2022-12-14 20:34:41 +01:00
*
2022-11-12 02:14:21 +01:00
* @ var array < string , array { byRef : bool , refMode : 'rw' | 'w' | 'r' , variadic : bool , optional : bool , type : string } >
2022-06-13 15:10:23 +02:00
*/
$normalizedEntries = [];
2022-06-26 00:01:12 +02:00
foreach ( $entryParameters as $key => $entry ) {
2022-06-13 15:10:23 +02:00
$normalizedKey = $key ;
2022-06-15 15:05:06 +02:00
/**
2022-11-12 02:14:21 +01:00
* @ var array { byRef : bool , refMode : 'rw' | 'w' | 'r' , variadic : bool , optional : bool , type : string } $normalizedEntry
2022-06-15 15:05:06 +02:00
*/
2022-06-13 15:10:23 +02:00
$normalizedEntry = [
'variadic' => false ,
'byRef' => false ,
'optional' => false ,
'type' => $entry ,
];
2022-06-15 10:56:15 +02:00
if ( strncmp ( $normalizedKey , '&' , 1 ) === 0 ) {
2022-06-13 15:10:23 +02:00
$normalizedEntry [ 'byRef' ] = true ;
2022-06-15 10:56:15 +02:00
$normalizedKey = substr ( $normalizedKey , 1 );
2022-06-13 15:10:23 +02:00
}
2022-06-15 10:56:15 +02:00
2022-06-13 15:10:23 +02:00
if ( strncmp ( $normalizedKey , '...' , 3 ) === 0 ) {
$normalizedEntry [ 'variadic' ] = true ;
$normalizedKey = substr ( $normalizedKey , 3 );
}
2022-06-15 10:56:15 +02:00
// Read the reference mode
if ( $normalizedEntry [ 'byRef' ]) {
$parts = explode ( '_' , $normalizedKey , 2 );
if ( count ( $parts ) === 2 ) {
2022-11-05 22:34:42 +01:00
if ( ! ( $parts [ 0 ] === 'rw' || $parts [ 0 ] === 'w' || $parts [ 0 ] === 'r' )) {
throw new InvalidArgumentException ( 'Invalid refMode: ' . $parts [ 0 ]);
}
2022-06-15 10:56:15 +02:00
$normalizedEntry [ 'refMode' ] = $parts [ 0 ];
$normalizedKey = $parts [ 1 ];
} else {
$normalizedEntry [ 'refMode' ] = 'rw' ;
}
}
// Strip prefixes.
2022-06-13 15:10:23 +02:00
if ( substr ( $normalizedKey , - 1 , 1 ) === " = " ) {
$normalizedEntry [ 'optional' ] = true ;
$normalizedKey = substr ( $normalizedKey , 0 , - 1 );
}
2022-12-22 19:16:38 +01:00
//$this->assertTrue($this->hasParameter($function, $normalizedKey), "Calmap has extra param entry {$normalizedKey}");
2022-06-15 10:56:15 +02:00
$normalizedEntry [ 'name' ] = $normalizedKey ;
2022-06-13 15:10:23 +02:00
$normalizedEntries [ $normalizedKey ] = $normalizedEntry ;
}
2022-12-22 19:16:38 +01:00
2022-06-26 00:01:12 +02:00
foreach ( $function -> getParameters () as $parameter ) {
$this -> assertArrayHasKey ( $parameter -> getName (), $normalizedEntries , " Callmap is missing entry for param { $parameter -> getName () } in { $function -> getName () } : " . print_r ( $normalizedEntries , true ));
2022-06-15 15:05:06 +02:00
$this -> assertParameter ( $normalizedEntries [ $parameter -> getName ()], $parameter );
2022-06-13 15:10:23 +02:00
}
}
2022-12-22 19:16:38 +01:00
/* Used by above assert
private function hasParameter ( ReflectionFunctionAbstract $function , string $name ) : bool
{
foreach ( $function -> getParameters () as $parameter )
{
if ( $parameter -> getName () === $name ) {
return true ;
}
}
return false ;
}
*/
2022-06-13 15:10:23 +02:00
/**
2022-11-12 02:14:21 +01:00
* @ param array { byRef : bool , name ? : string , refMode : 'rw' | 'w' | 'r' , variadic : bool , optional : bool , type : string } $normalizedEntry
2022-06-13 15:10:23 +02:00
*/
2022-06-15 15:05:06 +02:00
private function assertParameter ( array $normalizedEntry , ReflectionParameter $param ) : void
2022-06-13 15:10:23 +02:00
{
$name = $param -> getName ();
2022-06-22 12:59:47 +02:00
$this -> assertSame ( $param -> isOptional (), $normalizedEntry [ 'optional' ], " Expected param ' { $name } ' to " . ( $param -> isOptional () ? " be " : " not be " ) . " optional " );
$this -> assertSame ( $param -> isVariadic (), $normalizedEntry [ 'variadic' ], " Expected param ' { $name } ' to " . ( $param -> isVariadic () ? " be " : " not be " ) . " variadic " );
$this -> assertSame ( $param -> isPassedByReference (), $normalizedEntry [ 'byRef' ], " Expected param ' { $name } ' to " . ( $param -> isPassedByReference () ? " be " : " not be " ) . " by reference " );
2022-06-13 15:10:23 +02:00
$expectedType = $param -> getType ();
2022-06-14 15:16:13 +02:00
2022-06-22 13:28:17 +02:00
if ( isset ( $expectedType ) && ! empty ( $normalizedEntry [ 'type' ])) {
2022-12-22 19:16:38 +01:00
$this -> assertTypeValidity ( $expectedType , $normalizedEntry [ 'type' ], " Param ' { $name } ' " );
2022-06-13 15:10:23 +02:00
}
}
2022-12-04 09:19:57 +01:00
public function assertEntryReturnType ( ReflectionFunctionAbstract $function , string $entryReturnType ) : void
2022-06-26 00:01:12 +02:00
{
if ( version_compare ( PHP_VERSION , '8.1.0' , '>=' )) {
2022-07-06 10:33:34 +02:00
/** @var ReflectionType|null $expectedType */
2022-06-26 00:01:12 +02:00
$expectedType = $function -> hasTentativeReturnType () ? $function -> getTentativeReturnType () : $function -> getReturnType ();
} else {
$expectedType = $function -> getReturnType ();
}
2022-12-04 09:19:57 +01:00
$this -> assertNotEmpty ( $entryReturnType , 'CallMap entry has empty return type' );
2022-11-06 06:58:50 +01:00
if ( $expectedType !== null ) {
2022-12-22 19:16:38 +01:00
$this -> assertTypeValidity ( $expectedType , $entryReturnType , 'Return' );
2022-11-06 06:58:50 +01:00
}
2022-06-26 00:01:12 +02:00
}
2022-06-13 15:10:23 +02:00
/**
* Since string equality is too strict , we do some extra checking here
*/
2022-12-22 19:16:38 +01:00
private function assertTypeValidity ( ReflectionType $reflected , string $specified , string $msgPrefix ) : void
2022-06-13 15:10:23 +02:00
{
2022-06-15 09:27:40 +02:00
$expectedType = Reflection :: getPsalmTypeFromReflectionType ( $reflected );
2022-07-08 08:08:00 +02:00
$callMapType = Type :: parseString ( $specified );
2022-06-15 09:27:40 +02:00
2022-06-22 12:59:47 +02:00
try {
2022-12-22 19:16:38 +01:00
$this -> assertTrue ( UnionTypeComparator :: isContainedBy ( self :: $codebase , $callMapType , $expectedType ), " { $msgPrefix } type ' { $specified } ' should be contained by reflected type ' { $reflected } ' " );
2022-06-22 15:05:24 +02:00
} catch ( InvalidArgumentException $e ) {
2022-06-22 12:59:47 +02:00
if ( preg_match ( '/^Could not get class storage for (.*)$/' , $e -> getMessage (), $matches )
&& ! class_exists ( $matches [ 1 ])
) {
2022-06-26 00:01:12 +02:00
$this -> fail ( " Class used in CallMap does not exist: { $matches [ 1 ] } " );
2022-06-22 12:59:47 +02:00
}
}
2022-07-08 08:08:00 +02:00
// Reflection::getPsalmTypeFromReflectionType adds |null to mixed types so skip comparison
2022-12-22 19:16:38 +01:00
if ( ! $expectedType -> hasMixed ()) {
$this -> assertSame ( $expectedType -> isNullable (), $callMapType -> isNullable (), " { $msgPrefix } type ' { $specified } ' should be nullable " );
2022-07-08 08:08:00 +02:00
}
2022-06-13 15:10:23 +02:00
}
2021-08-08 10:39:54 +02:00
}