Skip to content

Commit

Permalink
Pre-lowercase callmaps
Browse files Browse the repository at this point in the history
  • Loading branch information
danog committed Dec 1, 2024
1 parent 972f75b commit 64f4efc
Show file tree
Hide file tree
Showing 19 changed files with 21,407 additions and 21,278 deletions.
10 changes: 10 additions & 0 deletions bin/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ARG VERSION
FROM php:${VERSION}

ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/

RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions pcntl uv-beta ffi pgsql intl gmp mbstring pdo_mysql xml dom iconv zip igbinary gd && \
rm /usr/local/bin/install-php-extensions

ADD php.ini /usr/local/etc/php/php.ini
25 changes: 25 additions & 0 deletions bin/gen_base_callmap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

$callmap = [];

require __DIR__ . '/gen_callmap_utils.php';

foreach (get_defined_functions()['internal'] as $name) {
$func = new ReflectionFunction($name);

$args = paramsToEntries($func);

$callmap[$name] = $args;
}

foreach (get_declared_classes() as $class) {
foreach ((new ReflectionClass($class))->getMethods() as $method) {
$args = paramsToEntries($method);

$callmap[$class.'::'.$method->getName()] = $args;
}
}

var_dump($callmap);
15 changes: 15 additions & 0 deletions bin/gen_base_callmap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

VERSIONS="7.0 7.1 7.2 7.3 7.4 8.0 8.1 8.2 8.3 8.4"

cd bin

for f in $VERSIONS; do
docker build --build-arg VERSION=$f . -t psalm_test_$f &
done

wait

for f in $VERSIONS; do

done
163 changes: 5 additions & 158 deletions bin/gen_callmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,171 +6,18 @@

require 'vendor/autoload.php';

require __DIR__ . '/gen_callmap_utils.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;
use Psalm\Type\Atomic\TNull;

/**
* Returns the correct reflection type for function or method name.
*/
function getReflectionFunction(string $functionName): ?ReflectionFunctionAbstract
{
try {
if (strpos($functionName, '::') !== false) {
if (PHP_VERSION_ID < 8_03_00) {
return new ReflectionMethod($functionName);
}

return ReflectionMethod::createFromMethodName($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'])) {
$func = $param->getDeclaringFunction()->getName();
assertTypeValidity($expectedType, $normalizedEntry['type'], "Param $func '{$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);
$callMapType = $expectedType;
}
} catch (Throwable) {
}

if ($expectedType->hasMixed()) {
return;
}
$callMapType = $callMapType->getBuilder();
if ($expectedType->isNullable() !== $callMapType->isNullable()) {
if ($expectedType->isNullable()) {
$callMapType->addType(new TNull());
} else {
$callMapType->removeType('null');
}
}
$specified = $callMapType->getId(true);
// //$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) {
function writeCallMap(string $file, array $callMap): void
{
file_put_contents($file, '<?php // phpcs:ignoreFile
return '.var_export($callMap, true).';');
Expand Down Expand Up @@ -203,4 +50,4 @@ function writeCallMap(string $file, array $callMap) {
}
}

writeCallMap($diffFile, $diff);
writeCallMap($diffFile, $diff);
Loading

0 comments on commit 64f4efc

Please sign in to comment.