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

Support object for ReflectionClass #566

Merged
merged 7 commits into from
Jul 1, 2021

Conversation

VincentLanglet
Copy link
Contributor

public function doThing($rightClassOrObject, $wrongClassOrObject): void
{
try {
new \ReflectionClass($rightClassOrObject);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code needs more advanced logic if you have an expression that can contain something else besides an object.

(new ObjectWithoutClassType())->isSuperTypeOf($valueType)->yes() will be true only for object or Foo or Foo|Bar. And that test is missing here.

Foo|string and similar types will fall through this condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before the check

(new ObjectWithoutClassType())->isSuperTypeOf($valueType)->yes()

I have the code

foreach (TypeUtils::getConstantStrings($valueType) as $constantString) {
			if (!$this->reflectionProvider->hasClass($constantString->getValue())) {
				return $methodReflection->getThrowType();
			}
			$valueType = TypeCombinator::remove($valueType, $constantString);
		}

I thought that after the foreach, the $valueType would have been now \DateTime|\DateTimeImmutable since I used TypeCombinator::remove with the constant class-string<\DateTime>.

This is exactly why I had two case:

  • \DateTime|\DateTimeImmutable|class-string<\DateTime> should have been transformed to \DateTime|\DateTimeImmutable after the foreach and then the condition returns true.
  • \DateTime|\DateTimeImmutable|string would have stayed untouched and then an exception is still possible.

What is wrong ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is wrong ?

Case new \ReflectionClass(new Foo()); is not tested. These new added tests would pass without changing src/ I believe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant, why the current tests are failing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReflectionClass(object) is tested and works.

ReflectionClass(\DateTime|\DateTimeImmutable|class-string<\DateTime>) should not throw an exception, but PHPStan is still believing an exception can be thrown.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ondrejmirtes I think it's because

try {
    new \ReflectionClass(\\DateTime::class);
} catch (\Exception $e) {

}

works but

/** @var class-string<\DateTime> $foo */
$foo;
try {
    new \ReflectionClass($foo);
} catch (\Exception $e) {

}

doesn't.

The first one is a

class PHPStan\Type\Constant\ConstantStringType#18453 (2) {
  private string $value =>
  string(8) "DateTime"
  private bool $isClassString =>
  bool(true)
}

The second one is a

class PHPStan\Type\Generic\GenericClassStringType#18511 (1) {
  private PHPStan\Type\Type $type =>
  class PHPStan\Type\ObjectType#18481 (6) {
    private string $className =>
    string(8) "DateTime"
    private ?PHPStan\Type\Type $subtractedType =>
    NULL
    private ?PHPStan\Reflection\ClassReflection $classReflection =>
    NULL
    private ?PHPStan\Type\ObjectType $cachedParent =>
    NULL
    private ?array $cachedInterfaces =>
    NULL
    private array $currentAncestors =>
    array(0) {
    }
  }
}

and TypeUtils::getConstantStrings($valueType) is only returning the first one.

All the ThrowTypeExtension I provided have the same issue.

Any idea how to fix this ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea how to fix this ?

With different logic. Maybe use TypeUtils::flattenTypes() and iterate over them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ondrejmirtes It's fixed now :)

I even cought a useless @throws in the psalm codebase.

@ondrejmirtes ondrejmirtes merged commit 3cebee9 into phpstan:master Jul 1, 2021
@ondrejmirtes
Copy link
Member

Thank you!

@VincentLanglet VincentLanglet deleted the fixExtension branch July 1, 2021 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants