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

Declaring more precise types and purity boundaries on ext-reflection symbols in .phpstub files #8722

Merged
19 changes: 17 additions & 2 deletions src/Psalm/Config.php
Expand Up @@ -2085,6 +2085,16 @@ public function visitPreloadedStubFiles(Codebase $codebase, ?Progress $progress
$core_generic_files[] = $stringable_path;
}

if (PHP_VERSION_ID < 8_02_00 && $codebase->analysis_php_version_id >= 8_02_00) {
$stringable_path = dirname(__DIR__, 2) . '/stubs/Php82.phpstub';

if (!file_exists($stringable_path)) {
throw new UnexpectedValueException('Cannot locate PHP 8.2 classes');
}

$core_generic_files[] = $stringable_path;
}
Ocramius marked this conversation as resolved.
Show resolved Hide resolved

$stub_files = array_merge($core_generic_files, $this->preloaded_stub_files);

if (!$stub_files) {
Expand Down Expand Up @@ -2125,16 +2135,21 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null):
$dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'SPL.phpstub',
];

if (PHP_VERSION_ID >= 8_00_00 && $codebase->analysis_php_version_id >= 8_00_00) {
if ($codebase->analysis_php_version_id >= 8_00_00) {
$stringable_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Php80.phpstub';
$this->internal_stubs[] = $stringable_path;
}

if (PHP_VERSION_ID >= 8_01_00 && $codebase->analysis_php_version_id >= 8_01_00) {
if ($codebase->analysis_php_version_id >= 8_01_00) {
$stringable_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Php81.phpstub';
$this->internal_stubs[] = $stringable_path;
}

if ($codebase->analysis_php_version_id >= 8_02_00) {
$stringable_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Php82.phpstub';
$this->internal_stubs[] = $stringable_path;
}

foreach ($this->php_extensions as $ext => $enabled) {
if ($enabled) {
$this->internal_stubs[] = $dir_lvl_2 . DIRECTORY_SEPARATOR . "stubs"
Expand Down
113 changes: 110 additions & 3 deletions stubs/Php80.phpstub
Expand Up @@ -17,14 +17,24 @@ class ReflectionAttribute
{
}

/**
* @return non-empty-string
*
* @psalm-pure
*/
public function getName() : string
{
}

/**
* @psalm-pure
* @return int-mask-of<Attribute::TARGET_*>
*/
public function getTarget() : int
{
}

/** @psalm-pure */
public function isRepeated() : bool
{
}
Expand All @@ -48,13 +58,101 @@ class ReflectionAttribute
}
}

/**
* @template-covariant T as object
*
* @property-read class-string<T> $name
*/
class ReflectionClass implements Reflector {
Ocramius marked this conversation as resolved.
Show resolved Hide resolved
/**
* @return non-empty-string|false
* @psalm-pure
*/
public function getFileName(): string|false {}

/**
* @return positive-int|false
* @psalm-pure
*/
public function getStartLine(): int|false {}

/**
* @return positive-int|false
* @psalm-pure
*/
public function getEndLine(): int|false {}

/**
* @return non-empty-string|false
* @psalm-pure
*/
public function getDocComment(): string|false {}

/**
* @param ReflectionClass|class-string $class
*
* @psalm-pure
*/
public function isSubclassOf(self|string $class): bool {}

/**
* @param self|class-string $interface
*
* @psalm-pure
*/
public function implementsInterface(self|string $interface): bool {}

/**
* @return non-empty-string|false
*
* @psalm-pure
*/
public function getExtensionName(): string|false {}
}

/** @psalm-immutable */
class ReflectionClassConstant
{
public const IS_PUBLIC = 1;
public const IS_PROTECTED = 2;
public const IS_PRIVATE = 4;

/** @return non-empty-string|false */
public function getDocComment(): string|false {}
}

abstract class ReflectionFunctionAbstract implements Reflector
{
/**
* @return non-empty-string|false
*
* @psalm-pure
*/
public function getDocComment(): string|false {}

/**
* @return positive-int|false
*
* @psalm-pure
*/
public function getStartLine(): int|false {}

/**
* @return positive-int|false
*
* @psalm-pure
*/
public function getEndLine(): int|false {}

/**
* @return non-empty-string|false
*
* @psalm-pure
*/
public function getFileName(): string|false {}
}

/** @psalm-immutable */
class Attribute
{
public int $flags;
Expand All @@ -76,11 +174,20 @@ class Attribute
}
}

class ReflectionUnionType extends ReflectionType {
class ReflectionProperty implements Reflector
{
/**
* @return non-empty-list<ReflectionNamedType>
* @return non-empty-string|false
*
* @psalm-pure
*/
public function getTypes() {}
public function getDocComment(): string|false {}
}

/** @psalm-immutable */
class ReflectionUnionType extends ReflectionType {
/** @return non-empty-list<ReflectionNamedType> */
Ocramius marked this conversation as resolved.
Show resolved Hide resolved
public function getTypes(): array {}
}

class UnhandledMatchError extends Error {}
Expand Down
48 changes: 34 additions & 14 deletions stubs/Php81.phpstub
Expand Up @@ -26,6 +26,32 @@ namespace {
public static function tryFrom(string|int $value): ?static;
}

class ReflectionClass implements Reflector {
/** @psalm-pure */
public function isEnum(): bool {}
}

class ReflectionProperty implements Reflector
{
/**
* Starting from PHP 8.1, this method is pure, and has no effect.
*
* @psalm-pure
*/
public function setAccessible(bool $accessible): void {}
}

class ReflectionMethod extends ReflectionFunctionAbstract
{
/**
* Starting from PHP 8.1, this method is pure, and has no effect.
*
* @psalm-pure
*/
public function setAccessible(bool $accessible): void {}
}

/** @psalm-immutable */
class ReflectionEnum extends ReflectionClass implements Reflector
{
public function getBackingType(): ?ReflectionType;
Expand All @@ -36,32 +62,26 @@ namespace {
public function isBacked(): bool;
}

/** @psalm-immutable */
class ReflectionEnumUnitCase extends ReflectionClassConstant implements Reflector
{
/**
* @psalm-pure
*/
public function getEnum(): ReflectionEnum;

/**
* @psalm-pure
*/
public function getValue(): UnitEnum;
}

/** @psalm-immutable */
class ReflectionEnumBackedCase extends ReflectionEnumUnitCase implements Reflector
{
/**
* @psalm-pure
*/
public function getBackingValue(): int|string;
}

/** @psalm-immutable */
class ReflectionIntersectionType extends ReflectionType {
/**
* @return non-empty-list<ReflectionType>
*/
public function getTypes() {}
/** @return non-empty-list<ReflectionNamedType> */
public function getTypes(): array {}

/** @return false */
public function allowsNull(): bool {}
Ocramius marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
13 changes: 13 additions & 0 deletions stubs/Php82.phpstub
@@ -0,0 +1,13 @@
<?php
namespace {
class ReflectionClass implements Reflector {
/** @psalm-pure */
public function isReadOnly(): bool {}
Ocramius marked this conversation as resolved.
Show resolved Hide resolved
}

/** @psalm-immutable */
class ReflectionUnionType extends ReflectionType {
/** @return non-empty-list<ReflectionNamedType|ReflectionIntersectionType> */
public function getTypes(): array {}
}
}