Skip to content

Commit c867ba8

Browse files
committed
Fix #59 - Implement intersection types correctly
1 parent aa6aa56 commit c867ba8

File tree

7 files changed

+147
-103
lines changed

7 files changed

+147
-103
lines changed

src/Parser/NodeVisitor.php

Lines changed: 53 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -131,18 +131,7 @@ protected function addFunction(FunctionNode $node, ?string $namespace = null)
131131

132132
$parameter->setVariadic($param->variadic);
133133

134-
$type = $param->type;
135-
$typeStr = $this->typeToString($type);
136-
137-
if (null !== $typeStr) {
138-
$typeArr = [[$typeStr, false]];
139-
140-
if ($param->type instanceof NullableType) {
141-
$typeArr[] = ['null', false];
142-
}
143-
144-
$parameter->setHint($this->resolveHint($typeArr));
145-
}
134+
$this->manageHint($param->type, $parameter);
146135

147136
$function->addParameter($parameter);
148137
}
@@ -167,18 +156,7 @@ protected function addFunction(FunctionNode $node, ?string $namespace = null)
167156
$function->setModifiersFromTags();
168157
$function->setErrors($errors);
169158

170-
$returnType = $node->getReturnType();
171-
$returnTypeStr = $this->typeToString($returnType);
172-
173-
if (null !== $returnTypeStr) {
174-
$returnTypeArr = [[$returnTypeStr, false]];
175-
176-
if ($returnType instanceof NullableType) {
177-
$returnTypeArr[] = ['null', false];
178-
}
179-
180-
$function->setHint($this->resolveHint($returnTypeArr));
181-
}
159+
$this->manageHint($node->getReturnType(), $function);
182160

183161
$this->context->addFunction($function);
184162

@@ -188,7 +166,7 @@ protected function addFunction(FunctionNode $node, ?string $namespace = null)
188166
}
189167

190168
/**
191-
* @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|IntersectionType|null $type Type declaration
169+
* @param \PhpParser\Node\ComplexType|\PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|IntersectionType|null $type Type declaration
192170
*/
193171
protected function typeToString($type): ?string
194172
{
@@ -206,9 +184,13 @@ protected function typeToString($type): ?string
206184
} elseif ($type instanceof IntersectionType) {
207185
$typeString = [];
208186
foreach ($type->types as $type) {
209-
$typeString[] = $type->__toString();
187+
$typeAsStr = $type->__toString();
188+
if ($type instanceof FullyQualified && 0 !== strpos($typeAsStr, '\\')) {
189+
$typeAsStr = '\\' . $typeAsStr;
190+
}
191+
$typeString[] = $typeAsStr;
210192
}
211-
$typeString = implode('&', $typeString);
193+
return implode('&', $typeString);
212194
}
213195

214196
if ($typeString === null) {
@@ -332,18 +314,7 @@ protected function addMethod(ClassMethodNode $node)
332314

333315
$parameter->setVariadic($param->variadic);
334316

335-
$type = $param->type;
336-
$typeStr = $this->typeToString($type);
337-
338-
if (null !== $typeStr) {
339-
$typeArr = [[$typeStr, false]];
340-
341-
if ($param->type instanceof NullableType) {
342-
$typeArr[] = ['null', false];
343-
}
344-
345-
$parameter->setHint($this->resolveHint($typeArr));
346-
}
317+
$this->manageHint($param->type, $parameter);
347318

348319
$method->addParameter($parameter);
349320
}
@@ -371,18 +342,7 @@ protected function addMethod(ClassMethodNode $node)
371342
$method->setModifiersFromTags();
372343
$method->setErrors($errors);
373344

374-
$returnType = $node->getReturnType();
375-
$returnTypeStr = $this->typeToString($returnType);
376-
377-
if (null !== $returnTypeStr) {
378-
$returnTypeArr = [[$returnTypeStr, false]];
379-
380-
if ($returnType instanceof NullableType) {
381-
$returnTypeArr[] = ['null', false];
382-
}
383-
384-
$method->setHint($this->resolveHint($returnTypeArr));
385-
}
345+
$this->manageHint($node->getReturnType(), $method);
386346

387347
if ($this->context->getFilter()->acceptMethod($method)) {
388348
$this->context->getClass()->addMethod($method);
@@ -417,6 +377,14 @@ protected function addTagFromCommentToMethod(
417377
if (is_array($firstTagFound)) {
418378
$hint = $firstTagFound[0];
419379
$hintDescription = $firstTagFound[1] ?? null;
380+
if (is_array($hint) && isset($hint[0]) && stripos($hint[0][0] ?? '', '&') !== false) {// Detect intersection type
381+
$methodOrFunctionOrProperty->setIntersectionType(true);
382+
$intersectionParts = explode('&', $hint[0][0]);
383+
$hint = [];
384+
foreach ($intersectionParts as $part) {
385+
$hint[] = [$part, false];
386+
}
387+
}
420388
$methodOrFunctionOrProperty->setHint(is_array($hint) ? $this->resolveHint($hint) : $hint);
421389
if ($hintDescription !== null) {
422390
if (is_string($hintDescription)) {
@@ -458,36 +426,21 @@ protected function addProperty(PropertyNode $node)
458426
}
459427

460428
/**
461-
* @return array<int,PropertyReflection|string[]>
462-
* @phpstan-return array{PropertyReflection,string[]}
429+
* @param \PhpParser\Node\ComplexType|\PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|IntersectionType|null $type Type declaration
430+
* @param MethodReflection|FunctionReflection|ParameterReflection|PropertyReflection $object
463431
*/
464-
protected function getPropertyReflectionFromParserProperty(PropertyNode $node, PropertyProperty $prop): array
432+
protected function manageHint($type, Reflection $object): void
465433
{
466-
$property = new PropertyReflection($prop->name->toString(), $prop->getLine());
467-
$property->setModifiers($node->flags);
468-
469-
$property->setDefault($prop->default);
470-
471-
$docComment = $node->getDocComment();
472-
$docComment = $docComment === null ? null : $docComment->__toString();
473-
$comment = $this->context->getDocBlockParser()->parse($docComment, $this->context, $property);
474-
$property->setDocComment($docComment);
475-
$property->setShortDesc($comment->getShortDesc());
476-
$property->setLongDesc($comment->getLongDesc());
477-
$property->setSee($this->resolveSee($comment->getTag('see')));
478-
479-
$type = $node->type;
480-
481434
if ($type instanceof IntersectionType) {
482-
$property->setIntersectionType(true);
435+
$object->setIntersectionType(true);
483436

484437
$typeArr = [];
485438
foreach ($type->types as $type) {
486439
$typeStr = $this->typeToString($type);
487440
$typeArr[] = [$typeStr, false];
488441
}
489442

490-
$property->setHint($this->resolveHint($typeArr));
443+
$object->setHint($this->resolveHint($typeArr));
491444
} else {
492445
$typeStr = $this->typeToString($type);
493446

@@ -497,9 +450,31 @@ protected function getPropertyReflectionFromParserProperty(PropertyNode $node, P
497450
if ($type instanceof NullableType) {
498451
$typeArr[] = ['null', false];
499452
}
500-
$property->setHint($this->resolveHint($typeArr));
453+
$object->setHint($this->resolveHint($typeArr));
501454
}
502455
}
456+
}
457+
458+
/**
459+
* @return array<int,PropertyReflection|string[]>
460+
* @phpstan-return array{PropertyReflection,string[]}
461+
*/
462+
protected function getPropertyReflectionFromParserProperty(PropertyNode $node, PropertyProperty $prop): array
463+
{
464+
$property = new PropertyReflection($prop->name->toString(), $prop->getLine());
465+
$property->setModifiers($node->flags);
466+
467+
$property->setDefault($prop->default);
468+
469+
$docComment = $node->getDocComment();
470+
$docComment = $docComment === null ? null : $docComment->__toString();
471+
$comment = $this->context->getDocBlockParser()->parse($docComment, $this->context, $property);
472+
$property->setDocComment($docComment);
473+
$property->setShortDesc($comment->getShortDesc());
474+
$property->setLongDesc($comment->getLongDesc());
475+
$property->setSee($this->resolveSee($comment->getTag('see')));
476+
477+
$this->manageHint($node->type, $property);
503478

504479
if ($errors = $comment->getErrors()) {
505480
$property->setErrors($errors);
@@ -643,6 +618,9 @@ protected function updateMethodParametersFromTags(Reflection $method, array $tag
643618
return $errors;
644619
}
645620

621+
/**
622+
* @phpstan-param $hints array{0: string, 1: bool}
623+
*/
646624
protected function resolveHint(array $hints): array
647625
{
648626
foreach ($hints as $i => $hint) {
@@ -652,6 +630,9 @@ protected function resolveHint(array $hints): array
652630
return $hints;
653631
}
654632

633+
/**
634+
* @phpstan-param $alias array{0: string, 1: bool}
635+
*/
655636
protected function resolveAlias($alias)
656637
{
657638
// not a class

src/Reflection/FunctionReflection.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class FunctionReflection extends Reflection
1919
/** @var array<string,ParameterReflection> */
2020
protected $parameters = [];
2121
protected $byRef;
22+
/** @var bool */
23+
protected $isIntersectionType = false;
2224
protected $project;
2325
/** @var string|null */
2426
protected $file = null;
@@ -43,6 +45,16 @@ public function isByRef()
4345
return $this->byRef;
4446
}
4547

48+
public function setIntersectionType(bool $boolean): void
49+
{
50+
$this->isIntersectionType = $boolean;
51+
}
52+
53+
public function isIntersectionType(): bool
54+
{
55+
return $this->isIntersectionType;
56+
}
57+
4658
/**
4759
* @return Project
4860
*/
@@ -202,6 +214,7 @@ public function toArray()
202214
'tags' => $this->tags,
203215
'modifiers' => $this->modifiers,
204216
'is_by_ref' => $this->byRef,
217+
'is_intersection_type' => $this->isIntersectionType(),
205218
'exceptions' => $this->exceptions,
206219
'errors' => $this->errors,
207220
'parameters' => array_map(
@@ -233,6 +246,10 @@ public static function fromArray(Project $project, array $array)
233246
$method->relativeFilePath = $array['relative_file'] ?? '';// New in 5.5.0
234247
$method->fromCache = true;
235248

249+
if (isset($array['is_intersection_type'])) {// New in 5.5.3
250+
$method->setIntersectionType($array['is_intersection_type']);
251+
}
252+
236253
foreach ($array['parameters'] as $parameter) {
237254
$method->addParameter(ParameterReflection::fromArray($project, $parameter));
238255
}

src/Reflection/MethodReflection.php

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ class MethodReflection extends Reflection
2121
protected $class;
2222
protected $parameters = [];
2323
protected $byRef;
24-
protected $exceptions = [];
24+
/** @var bool */
25+
protected $isIntersectionType = false;
26+
protected $exceptions = [];
2527

2628
public function __toString()
2729
{
@@ -38,6 +40,16 @@ public function isByRef()
3840
return $this->byRef;
3941
}
4042

43+
public function setIntersectionType(bool $boolean): void
44+
{
45+
$this->isIntersectionType = $boolean;
46+
}
47+
48+
public function isIntersectionType(): bool
49+
{
50+
return $this->isIntersectionType;
51+
}
52+
4153
/**
4254
* {@inheritDoc}
4355
*/
@@ -135,6 +147,7 @@ public function toArray()
135147
'see' => $this->see,
136148
'modifiers' => $this->modifiers,
137149
'is_by_ref' => $this->byRef,
150+
'is_intersection_type' => $this->isIntersectionType(),
138151
'exceptions' => $this->exceptions,
139152
'errors' => $this->errors,
140153
'parameters' => array_map(
@@ -151,17 +164,19 @@ static function ($parameter) {
151164
*/
152165
public static function fromArray(Project $project, array $array)
153166
{
154-
$method = new self($array['name'], $array['line']);
155-
$method->shortDesc = $array['short_desc'];
156-
$method->longDesc = $array['long_desc'];
157-
$method->hint = $array['hint'];
158-
$method->hintDesc = $array['hint_desc'];
159-
$method->tags = $array['tags'];
160-
$method->modifiers = $array['modifiers'];
161-
$method->byRef = $array['is_by_ref'];
162-
$method->exceptions = $array['exceptions'];
163-
$method->errors = $array['errors'];
164-
$method->see = $array['see'] ?? [];// New in 5.4.0
167+
$method = new self($array['name'], $array['line']);
168+
$method->shortDesc = $array['short_desc'];
169+
$method->longDesc = $array['long_desc'];
170+
$method->hint = $array['hint'];
171+
$method->hintDesc = $array['hint_desc'];
172+
$method->tags = $array['tags'];
173+
$method->modifiers = $array['modifiers'];
174+
$method->byRef = $array['is_by_ref'];
175+
$method->exceptions = $array['exceptions'];
176+
$method->errors = $array['errors'];
177+
$method->see = $array['see'] ?? [];// New in 5.4.0
178+
$method->isIntersectionType = $array['is_intersection_type'] ?? false;// New in 5.5.3
179+
165180

166181
foreach ($array['parameters'] as $parameter) {
167182
$method->addParameter(ParameterReflection::fromArray($project, $parameter));

src/Reflection/ParameterReflection.php

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class ParameterReflection extends Reflection
2424
protected $byRef;
2525
protected $default;
2626
protected $variadic;
27+
/** @var bool */
28+
protected $isIntersectionType = false;
2729

2830
public function __toString()
2931
{
@@ -38,6 +40,16 @@ public function getClass()
3840
return $this->method->getClass();
3941
}
4042

43+
public function setIntersectionType(bool $boolean): void
44+
{
45+
$this->isIntersectionType = $boolean;
46+
}
47+
48+
public function isIntersectionType(): bool
49+
{
50+
return $this->isIntersectionType;
51+
}
52+
4153
public function setByRef($boolean)
4254
{
4355
$this->byRef = $boolean;
@@ -134,6 +146,7 @@ public function toArray()
134146
'variadic' => $this->variadic,
135147
'is_by_ref' => $this->byRef,
136148
'is_read_only' => $this->isReadOnly(),
149+
'is_intersection_type' => $this->isIntersectionType(),
137150
];
138151
}
139152

@@ -142,16 +155,17 @@ public function toArray()
142155
*/
143156
public static function fromArray(Project $project, array $array)
144157
{
145-
$parameter = new self($array['name'], $array['line']);
146-
$parameter->shortDesc = $array['short_desc'];
147-
$parameter->longDesc = $array['long_desc'];
148-
$parameter->hint = $array['hint'];
149-
$parameter->tags = $array['tags'];
150-
$parameter->modifiers = $array['modifiers'];
151-
$parameter->default = $array['default'];
152-
$parameter->variadic = $array['variadic'];
153-
$parameter->byRef = $array['is_by_ref'];
154-
$parameter->isReadOnly = $array['is_read_only'] ?? false;// New in 5.4.0
158+
$parameter = new self($array['name'], $array['line']);
159+
$parameter->shortDesc = $array['short_desc'];
160+
$parameter->longDesc = $array['long_desc'];
161+
$parameter->hint = $array['hint'];
162+
$parameter->tags = $array['tags'];
163+
$parameter->modifiers = $array['modifiers'];
164+
$parameter->default = $array['default'];
165+
$parameter->variadic = $array['variadic'];
166+
$parameter->byRef = $array['is_by_ref'];
167+
$parameter->isReadOnly = $array['is_read_only'] ?? false;// New in 5.4.0
168+
$parameter->isIntersectionType = $array['is_intersection_type'] ?? false;// New in 5.5.3
155169

156170
return $parameter;
157171
}

0 commit comments

Comments
 (0)