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

Fix #8560 #8586

Merged
merged 4 commits into from Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/Psalm/Internal/Type/TypeExpander.php
Expand Up @@ -669,8 +669,10 @@ private static function expandNamedObject(
if (!$final && $return_type instanceof TNamedObject) {
/** @psalm-suppress InaccessibleProperty Acting on a clone */
$return_type->is_static = true;
/** @psalm-suppress InaccessibleProperty Acting on a clone */
$return_type->is_static_resolved = true;
}
} elseif ($return_type->is_static
} elseif ($return_type->is_static && !$return_type->is_static_resolved
&& ($static_class_type instanceof TNamedObject
|| $static_class_type instanceof TTemplateParam)
) {
Expand All @@ -687,7 +689,8 @@ private static function expandNamedObject(
$return_type_types[$extra_static_type->getKey()] = clone $extra_static_type;
}
}
$return_type = $return_type->setIntersectionTypes($return_type_types);
$return_type = $return_type->setIntersectionTypes($return_type_types)
->setIsStatic(true, true);
} elseif ($return_type->is_static && is_string($static_class_type) && $final) {
$return_type = clone $return_type;
/** @psalm-suppress InaccessibleProperty Acting on a clone */
Expand Down
11 changes: 9 additions & 2 deletions src/Psalm/Type/Atomic/TNamedObject.php
Expand Up @@ -31,6 +31,11 @@ class TNamedObject extends Atomic
*/
public $is_static = false;

/**
* @var bool
*/
public $is_static_resolved = false;

/**
* Whether or not this type can represent a child of the class named in $value
* @var bool
Expand Down Expand Up @@ -59,13 +64,15 @@ public function __construct(
$this->from_docblock = $from_docblock;
}

public function setIsStatic(bool $is_static): self
public function setIsStatic(bool $is_static, ?bool $is_static_resolved = null): self
{
if ($this->is_static === $is_static) {
$is_static_resolved ??= $this->is_static_resolved;
if ($this->is_static === $is_static && $this->is_static_resolved === $is_static_resolved) {
return $this;
}
$cloned = clone $this;
$cloned->is_static = $is_static;
$cloned->is_static_resolved = $is_static_resolved;
return $cloned;
}

Expand Down
110 changes: 110 additions & 0 deletions tests/ClassTest.php
Expand Up @@ -611,6 +611,116 @@ function intersect(A $a) {
return $b;
}'
],
'preventDoubleStaticResolution1' => [
'code' => '<?php

/**
* @template TTKey
* @template TTValue
*
* @extends ArrayObject<TTKey, TTValue>
*/
class iter extends ArrayObject {
/**
* @return self<TTKey, TTValue>
*/
public function stabilize(): self {
return $this;
}
}

class a {
/**
* @return iter<int, static>
*/
public function ret(): iter {
return new iter([$this]);
}
}
class b extends a {
}

$a = new b;
$a = $a->ret();
$a = $a->stabilize();',
'assertions' => [
'$a===' => 'iter<int, b&static>'
]
],
'preventDoubleStaticResolution2' => [
'code' => '<?php
/**
* @template TTKey
* @template TTValue
*
* @extends ArrayObject<TTKey, TTValue>
*/
class iter extends ArrayObject {
/**
* @return self<TTKey, TTValue>
*/
public function stabilize(): self {
return $this;
}
}

interface a {
/**
* @return iter<int, static>
*/
public function ret(): iter;
}
class b implements a {
public function ret(): iter {
return new iter([$this]);
}
}

/** @var a */
$a = new b;
$a = $a->ret();
$a = $a->stabilize();',
'assertions' => [
'$a===' => 'iter<int, a&static>'
]
],
'preventDoubleStaticResolution3' => [
'code' => '<?php
/**
* @template TTKey
* @template TTValue
*
* @extends ArrayObject<TTKey, TTValue>
*/
class iter extends ArrayObject {
/**
* @return self<TTKey, TTValue>
*/
public function stabilize(): self {
return $this;
}
}

interface a {
/**
* @return iter<int, a&static>
*/
public function ret(): iter;
}
class b implements a {
public function ret(): iter {
return new iter([$this]);
}
}

/** @var a */
$a = new b;
$a = $a->ret();
$a = $a->stabilize();',
'assertions' => [
'$a===' => 'iter<int, a&static>'
]
],
'allowTraversableImplementationAlongWithIteratorAggregate' => [
'code' => '<?php
/**
Expand Down