Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dictionaries for 8.4 #11169

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
06ae404
Add support for PHP 8.4
GromNaN Apr 29, 2024
f1e9ba2
E_STRICT deprecated in PHP 8.4
GromNaN Oct 3, 2024
8ef89ff
Fix getReflectionFunction
GromNaN Oct 9, 2024
8900850
Merge branch 'master' into php84
danog Nov 24, 2024
01924c5
Update InternalCallMapHandlerTest.php
danog Nov 24, 2024
5343494
Update psalm-baseline.xml
danog Nov 24, 2024
7c600dd
Update ci.yml
danog Nov 24, 2024
95d5f6f
Update dictionaries for 8.1
danog Nov 25, 2024
ad9b64e
Fix
danog Nov 26, 2024
9af1744
Fixes
danog Nov 26, 2024
1bcef70
Merge remote-tracking branch 'g/php84' into update_dictionaries
danog Nov 26, 2024
66ae90a
bump
danog Nov 26, 2024
ebab021
Regen
danog Nov 26, 2024
192b17f
Add
danog Nov 26, 2024
7abef43
Bump
danog Nov 26, 2024
e2d1e3a
cleanup
danog Nov 26, 2024
ac94583
Add callmap generator
danog Nov 26, 2024
58bdb95
fixes
danog Nov 26, 2024
744844f
Bump
danog Nov 27, 2024
a05ac99
fix
danog Nov 27, 2024
12e5f79
bump
danog Nov 30, 2024
99ff70c
Finalize
danog Nov 30, 2024
8b3a70a
Merge remote-tracking branch 'origin/master' into update_dictionaries
danog Nov 30, 2024
41507e1
bump
danog Nov 30, 2024
68529b5
Merge remote-tracking branch 'origin/master' into update_dictionaries
danog Nov 30, 2024
972f75b
Fix
danog Nov 30, 2024
64f4efc
Pre-lowercase callmaps
danog Dec 1, 2024
1d45aa8
Cleanup
danog Dec 1, 2024
bb092f7
Sort during normalization
danog Dec 1, 2024
8cfb3c1
Autogenerate base callmaps
danog Dec 1, 2024
6b9f365
Refactor
danog Dec 1, 2024
7c5a2ba
bump
danog Dec 1, 2024
a0b5325
bump
danog Dec 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ jobs:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
count: ${{ fromJson(needs.chunk-matrix.outputs.count) }}
chunk: ${{ fromJson(needs.chunk-matrix.outputs.chunks) }}

Expand All @@ -148,7 +149,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php-version }}"
ini-values: zend.assertions=1, assert.exception=1, opcache.enable_cli=1, opcache.jit=function, opcache.jit_buffer_size=512M
ini-values: zend.assertions=1, assert.exception=1
tools: composer:v2
coverage: none
extensions: none, curl, dom, filter, intl, json, libxml, mbstring, opcache, openssl, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter
Expand Down
192 changes: 192 additions & 0 deletions bin/gen_callmap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?php

declare(strict_types=1);

// Written by SamMousa in https://github.com/vimeo/psalm/issues/8101, finalized by @danog

require 'vendor/autoload.php';

use DG\BypassFinals;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Codebase\Reflection;
use Psalm\Internal\Provider\FileProvider;
use Psalm\Internal\Provider\Providers;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Tests\TestConfig;
use Psalm\Type;

/**
* Returns the correct reflection type for function or method name.
*/
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;
}
}

/**
* @param array<string, string> $entryParameters
*/
function assertEntryParameters(ReflectionFunctionAbstract $function, array &$entryParameters): void
{
assertEntryReturnType($function, $entryParameters[0]);
/**
* Parse the parameter names from the map.
*
* @var array<string, array{byRef: bool, refMode: 'rw'|'w'|'r', variadic: bool, optional: bool, type: string}>
*/
$normalizedEntries = [];

foreach ($entryParameters as $key => &$entry) {
if ($key === 0) {
continue;
}
$normalizedKey = $key;
/**
* @var array{byRef: bool, refMode: 'rw'|'w'|'r', variadic: bool, optional: bool, type: string} $normalizedEntry
*/
$normalizedEntry = [
'variadic' => false,
'byRef' => false,
'optional' => false,
'type' => &$entry,
];
if (strncmp($normalizedKey, '&', 1) === 0) {
$normalizedEntry['byRef'] = true;
$normalizedKey = substr($normalizedKey, 1);
}

if (strncmp($normalizedKey, '...', 3) === 0) {
$normalizedEntry['variadic'] = true;
$normalizedKey = substr($normalizedKey, 3);
}

// Read the reference mode
if ($normalizedEntry['byRef']) {
$parts = explode('_', $normalizedKey, 2);
if (count($parts) === 2) {
if (!($parts[0] === 'rw' || $parts[0] === 'w' || $parts[0] === 'r')) {
throw new InvalidArgumentException('Invalid refMode: '.$parts[0]);
}
$normalizedEntry['refMode'] = $parts[0];
$normalizedKey = $parts[1];
} else {
$normalizedEntry['refMode'] = 'rw';
}
}

// Strip prefixes.
if (substr($normalizedKey, -1, 1) === "=") {
$normalizedEntry['optional'] = true;
$normalizedKey = substr($normalizedKey, 0, -1);
}

$normalizedEntry['name'] = $normalizedKey;
$normalizedEntries[$normalizedKey] = $normalizedEntry;
}

foreach ($function->getParameters() as $parameter) {
if (isset($normalizedEntries[$parameter->getName()])) {
assertParameter($normalizedEntries[$parameter->getName()], $parameter);
}
}
}

/**
* @param array{byRef: bool, name?: string, refMode: 'rw'|'w'|'r', variadic: bool, optional: bool, type: string} $normalizedEntry
*/
function assertParameter(array &$normalizedEntry, ReflectionParameter $param): void
{
$name = $param->getName();

$expectedType = $param->getType();

if (isset($expectedType) && !empty($normalizedEntry['type'])) {
assertTypeValidity($expectedType, $normalizedEntry['type'], "Param '{$name}'");
}
}

function assertEntryReturnType(ReflectionFunctionAbstract $function, string &$entryReturnType): void
{
if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
$expectedType = $function->hasTentativeReturnType() ? $function->getTentativeReturnType() : $function->getReturnType();
} else {
$expectedType = $function->getReturnType();
}

if ($expectedType !== null) {
assertTypeValidity($expectedType, $entryReturnType, 'Return');
}
}

/**
* Since string equality is too strict, we do some extra checking here
*/
function assertTypeValidity(ReflectionType $reflected, string &$specified, string $msgPrefix): void
{
$expectedType = Reflection::getPsalmTypeFromReflectionType($reflected);
$callMapType = Type::parseString($specified === '' ? 'mixed' : $specified);

$codebase = ProjectAnalyzer::getInstance()->getCodebase();
try {
if (!UnionTypeComparator::isContainedBy($codebase, $callMapType, $expectedType, false, false, null, false, false) && !str_contains($specified, 'static')) {
$specified = $expectedType->getId(true);
}
} catch (Throwable) {
}

// Reflection::getPsalmTypeFromReflectionType adds |null to mixed types so skip comparison
/*if (!$expectedType->hasMixed()) {
$this->assertSame($expectedType->isNullable(), $callMapType->isNullable(), "{$msgPrefix} type '{$specified}' missing null from reflected type '{$reflected}'");
//$this->assertSame($expectedType->hasBool(), $callMapType->hasBool(), "{$msgPrefix} type '{$specified}' missing bool from reflected type '{$reflected}'");
$this->assertSame($expectedType->hasArray(), $callMapType->hasArray(), "{$msgPrefix} type '{$specified}' missing array from reflected type '{$reflected}'");
$this->assertSame($expectedType->hasInt(), $callMapType->hasInt(), "{$msgPrefix} type '{$specified}' missing int from reflected type '{$reflected}'");
$this->assertSame($expectedType->hasFloat(), $callMapType->hasFloat(), "{$msgPrefix} type '{$specified}' missing float from reflected type '{$reflected}'");
}*/
}

BypassFinals::enable();

function writeCallMap(string $file, array $callMap) {
file_put_contents($file, '<?php // phpcs:ignoreFile
namespace Phan\Language\Internal;

return '.var_export($callMap, true).';');
}

new ProjectAnalyzer(new TestConfig, new Providers(new FileProvider));
$callMap = require "dictionaries/CallMap.php";
$orig = $callMap;

$codebase = ProjectAnalyzer::getInstance()->getCodebase();

foreach ($callMap as $functionName => &$entry) {
$refl = getReflectionFunction($functionName);
if (!$refl) {
continue;
}
assertEntryParameters($refl, $entry);
} unset($entry);

writeCallMap("dictionaries/CallMap.php", $callMap);

$diffFile = "dictionaries/CallMap_84_delta.php";

$diff = require $diffFile;

foreach ($callMap as $functionName => $entry) {
if ($orig[$functionName] !== $entry) {
$diff['changed'][$functionName]['old'] = $orig[$functionName];
$diff['changed'][$functionName]['new'] = $entry;
}
}

writeCallMap($diffFile, $diff);
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"composer/xdebug-handler": "^2.0 || ^3.0",
"dnoegel/php-xdg-base-dir": "^0.1.1",
"felixfbecker/advanced-json-rpc": "^3.1",
"felixfbecker/language-server-protocol": "^1.5.2",
"felixfbecker/language-server-protocol": "^1.5.3",
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0",
"netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"nikic/php-parser": "^5.0.0",
Expand Down
Loading
Loading