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 master from 5.x #10618

Merged
merged 23 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6e2effa
`key_exists()` is an alias for `array_key_exists()`
weirdan Jan 27, 2024
c1e22dd
Allow properties on intersections with enum interfaces
weirdan Jan 27, 2024
9372adb
`readgzfile()` is impure
weirdan Jan 27, 2024
67a91e0
Do not validate callable arguments in lenient contexts
weirdan Jan 27, 2024
361ba65
Use flags key instead of type for ClassMethod
edsrzf Jan 12, 2024
f1a206f
Remove usages of deprecated getLine
edsrzf Jan 9, 2024
4b2cd0f
[LSP] Add issue type in description
weirdan Jan 28, 2024
45f3218
Merge pull request #10607 from weirdan/lsp-issue-type-in-description
weirdan Jan 28, 2024
98d98be
Re-work CheckTrivialExprVisitor
edsrzf Jan 10, 2024
79b67f8
Merge pull request #10612 from edsrzf/rework-trivial-expr
weirdan Jan 29, 2024
872cf58
Merge pull request #10601 from weirdan/do-not-validate-callable-argum…
weirdan Jan 29, 2024
8354ff3
Merge pull request #10600 from weirdan/readgzfile-is-impure
weirdan Jan 29, 2024
fd1294e
Merge pull request #10599 from weirdan/allow-properties-on-intersecti…
weirdan Jan 29, 2024
f045730
Merge pull request #10598 from weirdan/key_exists-is-an-alias-for-arr…
weirdan Jan 29, 2024
98756ba
Fix language server running with `opcache.save_comments=0`
weirdan Jan 30, 2024
e02276a
Merge pull request #10614 from weirdan/fix-json-mapping-with-opcache.…
weirdan Jan 30, 2024
ca9a12d
Report `MissingConstructor` for natively typed mixed properties
weirdan Jan 30, 2024
baa8660
Merge pull request #10615 from weirdan/10589-fix-missing-MissingConst…
weirdan Jan 30, 2024
6d32d2f
Allow importing typedefs from enums
weirdan Jan 30, 2024
35e6eff
Merge pull request #10617 from weirdan/10416-import-typedefs-from-enums
weirdan Jan 30, 2024
7a199c6
Merge branch '5.x' into update-master
weirdan Jan 30, 2024
04ba935
Merge pull request #10605 from edsrzf/php-parser-tweaks
weirdan Jan 30, 2024
e87fd86
Merge branch '5.x' into update-master
weirdan Jan 30, 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
1 change: 1 addition & 0 deletions dictionaries/ImpureFunctionsList.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
'ob_end_clean' => true,
'ob_get_clean' => true,
'readfile' => true,
'readgzfile' => true,
'printf' => true,
'var_dump' => true,
'phpinfo' => true,
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/CodeLocation.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public function __construct(
$this->preview_start = $this->docblock_start ?: $this->file_start;

/** @psalm-suppress ImpureMethodCall Actually mutation-free just not marked */
$this->raw_line_number = $stmt->getLine();
$this->raw_line_number = $stmt->getStartLine();

$this->docblock_line_number = $comment_line;
}
Expand Down
9 changes: 6 additions & 3 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -1091,8 +1091,11 @@ private function checkPropertyInitialization(
$uninitialized_variables[] = '$this->' . $property_name;
$uninitialized_properties[$property_class_name . '::$' . $property_name] = $property;

if ($property->type && !$property->type->isMixed()) {
$uninitialized_typed_properties[$property_class_name . '::$' . $property_name] = $property;
if ($property->type) {
// Complain about all natively typed properties and all non-mixed docblock typed properties
if (!$property->type->from_docblock || !$property->type->isMixed()) {
$uninitialized_typed_properties[$property_class_name . '::$' . $property_name] = $property;
}
}
}

Expand Down Expand Up @@ -1192,7 +1195,7 @@ static function (FunctionLikeParameter $param): PhpParser\Node\Arg {
$fake_stmt = new VirtualClassMethod(
new VirtualIdentifier('__construct'),
[
'type' => PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC,
'flags' => PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC,
'params' => $fake_constructor_params,
'stmts' => $fake_constructor_stmts,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2042,7 +2042,9 @@ private static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt

private static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool
{
return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'array_key_exists';
return $stmt->name instanceof PhpParser\Node\Name
&& (strtolower($stmt->name->getFirst()) === 'array_key_exists'
|| strtolower($stmt->name->getFirst()) === 'key_exists');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,7 @@ private static function verifyExplicitParam(
} else {
if (!$param_type->hasString()
&& !$param_type->hasArray()
&& $context->check_functions
&& CallAnalyzer::checkFunctionExists(
$statements_analyzer,
$function_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,8 @@ private static function handleNonExistentClass(

$override_property_visibility = $interface_storage->override_property_visibility;

$intersects_with_enum = false;

foreach ($intersection_types as $intersection_type) {
if ($intersection_type instanceof TNamedObject
&& $codebase->classExists($intersection_type->value)
Expand All @@ -1141,12 +1143,19 @@ private static function handleNonExistentClass(
$class_exists = true;
return;
}
if ($intersection_type instanceof TNamedObject
&& (in_array($intersection_type->value, ['UnitEnum', 'BackedEnum'], true)
|| in_array('UnitEnum', $codebase->getParentInterfaces($intersection_type->value)))
) {
$intersects_with_enum = true;
}
}

if (!$class_exists &&
//interfaces can't have properties. Except when they do... In PHP Core, they can
!in_array($fq_class_name, ['UnitEnum', 'BackedEnum'], true) &&
!in_array('UnitEnum', $codebase->getParentInterfaces($fq_class_name))
!in_array('UnitEnum', $codebase->getParentInterfaces($fq_class_name)) &&
!$intersects_with_enum
) {
if (IssueBuffer::accepts(
new NoInterfaceProperties(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function findUnusedAssignment(
$traverser->addVisitor($visitor);
$traverser->traverse([$rhs_exp]);

$rhs_exp_trivial = (count($visitor->getNonTrivialExpr()) === 0);
$rhs_exp_trivial = !$visitor->hasNonTrivialExpr();

if ($rhs_exp_trivial) {
$treat_as_expr = false;
Expand Down
9 changes: 9 additions & 0 deletions src/Psalm/Internal/Fork/PsalmRestarter.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ protected function requiresRestart($default): bool
}
}

// opcache.save_comments is required for json mapper (used in language server) to work
if ($opcache_loaded && in_array(ini_get('opcache.save_comments'), ['0', 'false', 0, false])) {
return true;
}

return $default || $this->required;
}

Expand Down Expand Up @@ -163,6 +168,10 @@ protected function restart($command): void
}
}

if ($opcache_loaded) {
$additional_options[] = '-dopcache.save_comments=1';
}

array_splice(
$command,
1,
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/LanguageServer/LanguageServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ public function emitVersionedIssues(array $files, ?int $version = null): void
$diagnostics = array_map(
function (IssueData $issue_data): Diagnostic {
//$check_name = $issue->check_name;
$description = $issue_data->message;
$description = '[' . $issue_data->type . '] ' . $issue_data->message;
$severity = $issue_data->severity;

$start_line = max($issue_data->line_from, 1);
Expand Down
14 changes: 4 additions & 10 deletions src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
*/
final class CheckTrivialExprVisitor extends PhpParser\NodeVisitorAbstract
{
/**
* @var array<int, PhpParser\Node\Expr>
*/
private array $non_trivial_expr = [];
private bool $has_non_trivial_expr = false;

private function checkNonTrivialExpr(PhpParser\Node\Expr $node): bool
{
Expand Down Expand Up @@ -57,7 +54,7 @@ public function enterNode(PhpParser\Node $node): ?int
if ($node instanceof PhpParser\Node\Expr) {
// Check for Non-Trivial Expression first
if ($this->checkNonTrivialExpr($node)) {
$this->non_trivial_expr[] = $node;
$this->has_non_trivial_expr = true;
return PhpParser\NodeTraverser::STOP_TRAVERSAL;
}

Expand All @@ -72,11 +69,8 @@ public function enterNode(PhpParser\Node $node): ?int
return null;
}

/**
* @return array<int, PhpParser\Node\Expr>
*/
public function getNonTrivialExpr(): array
public function hasNonTrivialExpr(): bool
{
return $this->non_trivial_expr;
return $this->has_non_trivial_expr;
}
}
2 changes: 1 addition & 1 deletion src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ public function leaveNode(PhpParser\Node $node)
}

throw new UnexpectedValueException(
'There should be function storages for line ' . $this->file_path . ':' . $node->getLine(),
'There should be function storages for line ' . $this->file_path . ':' . $node->getStartLine(),
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Type/TypeExpander.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ public static function expandAtomic(
$declaring_fq_classlike_name = $self_class;
}

if (!($evaluate_class_constants && $codebase->classOrInterfaceExists($declaring_fq_classlike_name))) {
if (!($evaluate_class_constants && $codebase->classOrInterfaceOrEnumExists($declaring_fq_classlike_name))) {
return [$return_type];
}

Expand Down
27 changes: 27 additions & 0 deletions tests/EnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,33 @@ enum Bar: int
'ignored_issues' => [],
'php_version' => '8.1',
],
'allowPropertiesOnIntersectionsWithEnumInterfaces' => [
'code' => <<<'PHP'
<?php
interface I {}

interface UE extends UnitEnum {}
interface BE extends BackedEnum {}

function f(I $i): void {
if ($i instanceof BackedEnum) {
echo $i->name;
}
if ($i instanceof UnitEnum) {
echo $i->name;
}
if ($i instanceof UE) {
echo $i->name;
}
if ($i instanceof BE) {
echo $i->name;
}
}
PHP,
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.1',
],
];
}

Expand Down
8 changes: 8 additions & 0 deletions tests/FunctionCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,14 @@ function in_array($a, $b) {
}
}',
],
'callableArgumentWithFunctionExists' => [
'code' => <<<'PHP'
<?php
if (function_exists('foo')) {
register_shutdown_function('foo');
}
PHP,
],
'pregMatch' => [
'code' => '<?php
function takesInt(int $i) : void {}
Expand Down
9 changes: 9 additions & 0 deletions tests/PropertyTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3829,6 +3829,15 @@ class A {
',
'error_message' => 'UndefinedPropertyAssignment',
],
'nativeMixedPropertyWithNoConstructor' => [
'code' => <<< 'PHP'
<?php
class A {
public mixed $foo;
}
PHP,
'error_message' => 'MissingConstructor',
],
];
}
}
19 changes: 19 additions & 0 deletions tests/TypeAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,25 @@ public function doesNotWork($_doesNotWork): void {
}
}',
],
'importFromEnum' => [
'code' => <<<'PHP'
<?php
/** @psalm-type _Foo = array{foo: string} */
enum E {}
/**
* @psalm-import-type _Foo from E
*/
class C {
/** @param _Foo $foo */
public function f(array $foo): void {
echo $foo['foo'];
}
}
PHP,
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.1',
],
];
}

Expand Down
13 changes: 13 additions & 0 deletions tests/TypeReconciliation/ArrayKeyExistsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,19 @@ public function isCriticalError(int|string $key): bool {
'ignored_issues' => [],
'php_version' => '8.0',
],
'keyExistsAsAliasForArrayKeyExists' => [
'code' => <<<'PHP'
<?php
/**
* @param array<string, string> $arr
*/
function foo(array $arr): void {
if (key_exists("a", $arr)) {
echo $arr["a"];
}
}
PHP,
],
];
}

Expand Down