Skip to content

Commit

Permalink
Resolve remaining predefined constants
Browse files Browse the repository at this point in the history
This resolves the remaining predefined constants that are configured via `dynamicConstantNames` and differ from what reflection is able to resolve.
  • Loading branch information
herndlm authored and ondrejmirtes committed Jan 30, 2022
1 parent d92d616 commit 01a0d58
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 25 deletions.
200 changes: 193 additions & 7 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
use function usort;
use const PHP_INT_MAX;
use const PHP_INT_MIN;
use const PHP_INT_SIZE;

class MutatingScope implements Scope
{
Expand Down Expand Up @@ -2008,6 +2009,182 @@ private function resolveType(Expr $node): Type
if ($this->reflectionProvider->hasConstant($node->name, $this)) {
/** @var string $resolvedConstantName */
$resolvedConstantName = $this->reflectionProvider->resolveConstantName($node->name, $this);
// core, https://www.php.net/manual/en/reserved.constants.php
if ($resolvedConstantName === 'PHP_VERSION') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_MAJOR_VERSION') {
return IntegerRangeType::fromInterval(5, null);
}
if ($resolvedConstantName === 'PHP_MINOR_VERSION') {
return IntegerRangeType::fromInterval(0, null);
}
if ($resolvedConstantName === 'PHP_RELEASE_VERSION') {
return IntegerRangeType::fromInterval(0, null);
}
if ($resolvedConstantName === 'PHP_VERSION_ID') {
return IntegerRangeType::fromInterval(50207, null);
}
if ($resolvedConstantName === 'PHP_ZTS') {
return new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
]);
}
if ($resolvedConstantName === 'PHP_DEBUG') {
return new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
]);
}
if ($resolvedConstantName === 'PHP_MAXPATHLEN') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === 'PHP_OS') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_OS_FAMILY') {
return new UnionType([
new ConstantStringType('Windows'),
new ConstantStringType('BSD'),
new ConstantStringType('Darwin'),
new ConstantStringType('Solaris'),
new ConstantStringType('Linux'),
new ConstantStringType('Unknown'),
]);
}
if ($resolvedConstantName === 'PHP_SAPI') {
return new UnionType([
new ConstantStringType('apache'),
new ConstantStringType('apache2handler'),
new ConstantStringType('cgi'),
new ConstantStringType('cli'),
new ConstantStringType('cli-server'),
new ConstantStringType('embed'),
new ConstantStringType('fpm-fcgi'),
new ConstantStringType('litespeed'),
new ConstantStringType('phpdbg'),
new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]),
]);
}
if ($resolvedConstantName === 'PHP_EOL') {
return new UnionType([
new ConstantStringType("\n"),
new ConstantStringType("\r\n"),
]);
}
if ($resolvedConstantName === 'PHP_INT_MAX') {
return PHP_INT_SIZE === 8
? new UnionType([new ConstantIntegerType(2147483647), new ConstantIntegerType(9223372036854775807)])
: new ConstantIntegerType(2147483647);
}
if ($resolvedConstantName === 'PHP_INT_MIN') {
// Why the -1 you might wonder, the answer is to fit it into an int :/ see https://3v4l.org/4SHIQ
return PHP_INT_SIZE === 8
? new UnionType([new ConstantIntegerType(-9223372036854775807 - 1), new ConstantIntegerType(-2147483647 - 1)])
: new ConstantIntegerType(-2147483647 - 1);
}
if ($resolvedConstantName === 'PHP_INT_SIZE') {
return new UnionType([
new ConstantIntegerType(4),
new ConstantIntegerType(8),
]);
}
if ($resolvedConstantName === 'PHP_FLOAT_DIG') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === 'PHP_EXTENSION_DIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_PREFIX') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_BINDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_BINARY') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_MANDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_LIBDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_DATADIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_SYSCONFDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_LOCALSTATEDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_CONFIG_FILE_PATH') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_SHLIB_SUFFIX') {
return new UnionType([
new ConstantStringType('so'),
new ConstantStringType('dll'),
]);
}
if ($resolvedConstantName === 'PHP_FD_SETSIZE') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === '__COMPILER_HALT_OFFSET__') {
return IntegerRangeType::fromInterval(0, null);
}
// core other, https://www.php.net/manual/en/info.constants.php
if ($resolvedConstantName === 'PHP_WINDOWS_VERSION_MAJOR') {
return IntegerRangeType::fromInterval(4, null);
}
if ($resolvedConstantName === 'PHP_WINDOWS_VERSION_MINOR') {
return IntegerRangeType::fromInterval(0, null);
}
if ($resolvedConstantName === 'PHP_WINDOWS_VERSION_BUILD') {
return IntegerRangeType::fromInterval(1, null);
}
// dir, https://www.php.net/manual/en/dir.constants.php
if ($resolvedConstantName === 'DIRECTORY_SEPARATOR') {
return new UnionType([
new ConstantStringType('/'),
Expand All @@ -2020,16 +2197,25 @@ private function resolveType(Expr $node): Type
new ConstantStringType(';'),
]);
}
if ($resolvedConstantName === 'PHP_EOL') {
return new UnionType([
new ConstantStringType("\n"),
new ConstantStringType("\r\n"),
// iconv, https://www.php.net/manual/en/iconv.constants.php
if ($resolvedConstantName === 'ICONV_IMPL') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === '__COMPILER_HALT_OFFSET__') {
return new IntegerType();
// libxml, https://www.php.net/manual/en/libxml.constants.php
if ($resolvedConstantName === 'LIBXML_VERSION') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === 'PHP_INT_MAX') {
if ($resolvedConstantName === 'LIBXML_DOTTED_VERSION') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
// openssl, https://www.php.net/manual/en/openssl.constants.php
if ($resolvedConstantName === 'OPENSSL_VERSION_NUMBER') {
return IntegerRangeType::fromInterval(1, null);
}

Expand Down
22 changes: 20 additions & 2 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use stdClass;
use function define;
use function extension_loaded;
use const PHP_INT_SIZE;
use const PHP_VERSION_ID;

class NodeScopeResolverTest extends TypeInferenceTestCase
Expand Down Expand Up @@ -34,7 +35,9 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/date.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/instanceof.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/integer-range-types.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/random-int.php');
if (PHP_INT_SIZE === 8) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/random-int.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-return-type-extensions.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-key.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/intersection-static.php');
Expand Down Expand Up @@ -512,7 +515,9 @@ public function dataFileAsserts(): iterable

yield from $this->gatherAssertTypes(__DIR__ . '/data/div-by-zero.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5072.php');
if (PHP_INT_SIZE === 8) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5072.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5530.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1861.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4843.php');
Expand Down Expand Up @@ -598,6 +603,19 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6293.php');
}

if (PHP_VERSION_ID >= 70200) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/predefined-constants-php72.php');
}
if (PHP_VERSION_ID >= 70400) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/predefined-constants-php74.php');
}
if (PHP_INT_SIZE === 8) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/predefined-constants-64bit.php');
} else {
yield from $this->gatherAssertTypes(__DIR__ . '/data/predefined-constants-32bit.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/predefined-constants.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/classPhpDocs-phpstanPropertyPrefix.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/array-destructuring-types.php');
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/ScopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public function testGetConstantType(): void
$scope = $scopeFactory->create(ScopeContext::create(__DIR__ . '/data/compiler-halt-offset.php'));
$node = new ConstFetch(new FullyQualified('__COMPILER_HALT_OFFSET__'));
$type = $scope->getType($node);
$this->assertSame('int', $type->describe(VerbosityLevel::precise()));
$this->assertSame('int<0, max>', $type->describe(VerbosityLevel::precise()));
}

}
16 changes: 8 additions & 8 deletions tests/PHPStan/Analyser/data/bug-4434.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ class HelloWorld
public function testSendEmailToLog(): void
{
foreach ([1] as $emailFile) {
assertType('int', PHP_MAJOR_VERSION);
assertType('int', \PHP_MAJOR_VERSION);
assertType('int<5, max>', PHP_MAJOR_VERSION);
assertType('int<5, max>', \PHP_MAJOR_VERSION);
if (PHP_MAJOR_VERSION === 7) {
assertType('int', PHP_MAJOR_VERSION);
assertType('int', \PHP_MAJOR_VERSION);
} else {
assertType('int<min, 6>|int<8, max>', PHP_MAJOR_VERSION);
assertType('int<min, 6>|int<8, max>', \PHP_MAJOR_VERSION);
assertType('int<5, 6>|int<8, max>', PHP_MAJOR_VERSION);
assertType('int<5, 6>|int<8, max>', \PHP_MAJOR_VERSION);
}
}
}
Expand All @@ -28,14 +28,14 @@ class HelloWorld2
public function testSendEmailToLog(): void
{
foreach ([1] as $emailFile) {
assertType('int', PHP_MAJOR_VERSION);
assertType('int', \PHP_MAJOR_VERSION);
assertType('int<5, max>', PHP_MAJOR_VERSION);
assertType('int<5, max>', \PHP_MAJOR_VERSION);
if (PHP_MAJOR_VERSION === 100) {
assertType('int', PHP_MAJOR_VERSION);
assertType('int', \PHP_MAJOR_VERSION);
} else {
assertType('int<min, 99>|int<101, max>', PHP_MAJOR_VERSION);
assertType('int<min, 99>|int<101, max>', \PHP_MAJOR_VERSION);
assertType('int<5, 99>|int<101, max>', PHP_MAJOR_VERSION);
assertType('int<5, 99>|int<101, max>', \PHP_MAJOR_VERSION);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-5072.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ public function incorrect(array $params): void

public function incorrectWithConstant(): void
{
assertType('int<1, max>', max(1, PHP_INT_MAX));
assertType('2147483647|9223372036854775807', max(1, PHP_INT_MAX));
}
}
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/data/predefined-constants-32bit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

use function PHPStan\Testing\assertType;

// core, https://www.php.net/manual/en/reserved.constants.php
assertType('2147483647', PHP_INT_MAX);
assertType('-2147483648', PHP_INT_MIN);
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/data/predefined-constants-64bit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

use function PHPStan\Testing\assertType;

// core, https://www.php.net/manual/en/reserved.constants.php
assertType('2147483647|9223372036854775807', PHP_INT_MAX);
assertType('-9223372036854775808|-2147483648', PHP_INT_MIN);
11 changes: 11 additions & 0 deletions tests/PHPStan/Analyser/data/predefined-constants-php72.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

use function PHPStan\Testing\assertType;

// core, https://www.php.net/manual/en/reserved.constants.php
assertType('\'BSD\'|\'Darwin\'|\'Linux\'|\'Solaris\'|\'Unknown\'|\'Windows\'', PHP_OS_FAMILY);
assertType('int<1, max>', PHP_FLOAT_DIG);
assertType('float', PHP_FLOAT_EPSILON);
assertType('float', PHP_FLOAT_MIN);
assertType('float', PHP_FLOAT_MAX);
assertType('int<1, max>', PHP_FD_SETSIZE);
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/data/predefined-constants-php74.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

use function PHPStan\Testing\assertType;

// core, https://www.php.net/manual/en/reserved.constants.php
assertType('0', PHP_WINDOWS_EVENT_CTRL_C);
assertType('1', PHP_WINDOWS_EVENT_CTRL_BREAK);

0 comments on commit 01a0d58

Please sign in to comment.