Skip to content

Commit

Permalink
Support for @mixin
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 2, 2020
1 parent 28d1c34 commit b02ee14
Show file tree
Hide file tree
Showing 15 changed files with 657 additions and 0 deletions.
6 changes: 6 additions & 0 deletions conf/config.level2.neon
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ rules:
- PHPStan\Rules\PhpDoc\WrongVariableNameInVarTagRule

services:
-
class: PHPStan\Rules\Classes\MixinRule
arguments:
checkClassCaseSensitivity: %checkClassCaseSensitivity%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Functions\CallCallablesRule
arguments:
Expand Down
10 changes: 10 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,16 @@ services:
-
class: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository

-
class: PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension

-
class: PHPStan\Reflection\Mixin\MixinPropertiesClassReflectionExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension

-
class: PHPStan\Reflection\Php\PhpClassReflectionExtension
arguments:
Expand Down
16 changes: 16 additions & 0 deletions src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPStan\PhpDoc\Tag\ImplementsTag;
use PHPStan\PhpDoc\Tag\MethodTag;
use PHPStan\PhpDoc\Tag\MethodTagParameter;
use PHPStan\PhpDoc\Tag\MixinTag;
use PHPStan\PhpDoc\Tag\ParamTag;
use PHPStan\PhpDoc\Tag\PropertyTag;
use PHPStan\PhpDoc\Tag\ReturnTag;
Expand All @@ -16,6 +17,7 @@
use PHPStan\PhpDoc\Tag\UsesTag;
use PHPStan\PhpDoc\Tag\VarTag;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
Expand Down Expand Up @@ -339,6 +341,20 @@ public function resolveThrowsTags(PhpDocNode $phpDocNode, NameScope $nameScope):
return new ThrowsTag(TypeCombinator::union(...$types));
}

/**
* @param PhpDocNode $phpDocNode
* @param NameScope $nameScope
* @return array<MixinTag>
*/
public function resolveMixinTags(PhpDocNode $phpDocNode, NameScope $nameScope): array
{
return array_map(function (MixinTagValueNode $mixinTagValueNode) use ($nameScope): MixinTag {
return new MixinTag(
$this->typeNodeResolver->resolve($mixinTagValueNode->type, $nameScope)
);
}, $phpDocNode->getMixinTagValues());
}

public function resolveDeprecatedTag(PhpDocNode $phpDocNode, NameScope $nameScope): ?\PHPStan\PhpDoc\Tag\DeprecatedTag
{
foreach ($phpDocNode->getDeprecatedTagValues() as $deprecatedTagValue) {
Expand Down
19 changes: 19 additions & 0 deletions src/PhpDoc/ResolvedPhpDocBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\PhpDoc;

use PHPStan\Analyser\NameScope;
use PHPStan\PhpDoc\Tag\MixinTag;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\Type\Generic\TemplateTypeMap;

Expand Down Expand Up @@ -57,6 +58,9 @@ class ResolvedPhpDocBlock
/** @var \PHPStan\PhpDoc\Tag\ThrowsTag|false|null */
private $throwsTag = false;

/** @var array<MixinTag>|false */
private $mixinTags = false;

/** @var \PHPStan\PhpDoc\Tag\DeprecatedTag|false|null */
private $deprecatedTag = false;

Expand Down Expand Up @@ -314,6 +318,21 @@ public function getThrowsTag(): ?\PHPStan\PhpDoc\Tag\ThrowsTag
return $this->throwsTag;
}

/**
* @return array<MixinTag>
*/
public function getMixinTags(): array
{
if ($this->mixinTags === false) {
$this->mixinTags = $this->phpDocNodeResolver->resolveMixinTags(
$this->phpDocNode,
$this->nameScope
);
}

return $this->mixinTags;
}

public function getDeprecatedTag(): ?\PHPStan\PhpDoc\Tag\DeprecatedTag
{
if ($this->deprecatedTag === false) {
Expand Down
34 changes: 34 additions & 0 deletions src/PhpDoc/Tag/MixinTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDoc\Tag;

use PHPStan\Type\Type;

class MixinTag
{

/** @var \PHPStan\Type\Type */
private $type;

public function __construct(Type $type)
{
$this->type = $type;
}

public function getType(): Type
{
return $this->type;
}

/**
* @param mixed[] $properties
* @return self
*/
public static function __set_state(array $properties): self
{
return new self(
$properties['type']
);
}

}
35 changes: 35 additions & 0 deletions src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
use PHPStan\PhpDoc\Tag\ExtendsTag;
use PHPStan\PhpDoc\Tag\ImplementsTag;
use PHPStan\PhpDoc\Tag\MixinTag;
use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\Reflection\Php\PhpClassReflectionExtension;
use PHPStan\Reflection\Php\PhpPropertyReflection;
Expand Down Expand Up @@ -941,4 +942,38 @@ private function isValidAncestorType(Type $type, array $ancestorClasses): bool
return in_array($reflection->getName(), $ancestorClasses, true);
}

/**
* @return array<MixinTag>
*/
public function getMixinTags(): array
{
$resolvedPhpDoc = $this->getResolvedPhpDoc();
if ($resolvedPhpDoc === null) {
return [];
}

return $resolvedPhpDoc->getMixinTags();
}

/**
* @return array<Type>
*/
public function getResolvedMixinTypes(): array
{
$types = [];
foreach ($this->getMixinTags() as $mixinTag) {
if (!$this->isGeneric()) {
$types[] = $mixinTag->getType();
continue;
}

$types[] = TemplateTypeHelper::resolveTemplateTypes(
$mixinTag->getType(),
$this->getActiveTemplateTypeMap()
);
}

return $types;
}

}
51 changes: 51 additions & 0 deletions src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\Mixin;

use PHPStan\Analyser\OutOfClassScope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;

class MixinMethodsClassReflectionExtension implements MethodsClassReflectionExtension
{

public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
return $this->findMethod($classReflection, $methodName) !== null;
}

public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
{
$method = $this->findMethod($classReflection, $methodName);
if ($method === null) {
throw new \PHPStan\ShouldNotHappenException();
}

return $method;
}

private function findMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection
{
$mixinTypes = $classReflection->getResolvedMixinTypes();
foreach ($mixinTypes as $type) {
if (!$type->hasMethod($methodName)->yes()) {
continue;
}

return $type->getMethod($methodName, new OutOfClassScope());
}

foreach ($classReflection->getParents() as $parentClass) {
$method = $this->findMethod($parentClass, $methodName);
if ($method === null) {
continue;
}

return $method;
}

return null;
}

}
51 changes: 51 additions & 0 deletions src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\Mixin;

use PHPStan\Analyser\OutOfClassScope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertiesClassReflectionExtension;
use PHPStan\Reflection\PropertyReflection;

class MixinPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension
{

public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
{
return $this->findProperty($classReflection, $propertyName) !== null;
}

public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
{
$property = $this->findProperty($classReflection, $propertyName);
if ($property === null) {
throw new \PHPStan\ShouldNotHappenException();
}

return $property;
}

private function findProperty(ClassReflection $classReflection, string $propertyName): ?PropertyReflection
{
$mixinTypes = $classReflection->getResolvedMixinTypes();
foreach ($mixinTypes as $type) {
if (!$type->hasProperty($propertyName)->yes()) {
continue;
}

return $type->getProperty($propertyName, new OutOfClassScope());
}

foreach ($classReflection->getParents() as $parentClass) {
$property = $this->findProperty($parentClass, $propertyName);
if ($property === null) {
continue;
}

return $property;
}

return null;
}

}
Loading

0 comments on commit b02ee14

Please sign in to comment.