Skip to content

Commit 28fd10a

Browse files
committed
Enhance type handling in ContractParser and add tests for Constructor-Property type conflicts in Doctrine simulation classes
1 parent e90b311 commit 28fd10a

7 files changed

Lines changed: 98 additions & 1 deletion

File tree

src/Contract/ContractParser.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,8 @@ private static function resolveTargetParamName(
668668
}
669669

670670
/**
671-
* Falls back to property @var docblocks for constructor promoted parameters if un-annotated.
671+
* Falls back to property @var docblocks for constructor parameters if un-annotated,
672+
* ensuring scalar constructor parameters are never overridden by conflicting object property types.
672673
*
673674
* @param \ReflectionMethod $ref
674675
* @param array<string, TypeNode> $types
@@ -687,6 +688,22 @@ private static function applyConstructorPromotionFallback(\ReflectionMethod $ref
687688
if ($propDoc !== false) {
688689
$propType = DocblockExtractor::extractTypeFromPropertyDoc($propDoc, $paramName);
689690
if ($propType !== null) {
691+
if ($p->hasType()) {
692+
$nativeType = $p->getType();
693+
if ($nativeType instanceof \ReflectionNamedType) {
694+
$nativeName = strtolower($nativeType->getName());
695+
$propTypeStr = strtolower((string) $propType);
696+
697+
if (
698+
$nativeType->isBuiltin()
699+
&& ! \in_array($nativeName, ['array', 'iterable', 'mixed'], true)
700+
&& ! \in_array($propTypeStr, [$nativeName, 'mixed'], true)
701+
) {
702+
continue;
703+
}
704+
}
705+
}
706+
690707
$isVariadic = $p->isVariadic();
691708
if ($isVariadic) {
692709
$propType = new ArrayTypeNode($propType);

src/Internal/ErrorFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ private static function isCallSiteError(string $lowerMessage): bool
8383
* Filters out internal TypePHP frames from the raw stack trace.
8484
*
8585
* @param list<array<string, mixed>> $trace
86+
*
8687
* @return list<array<string, mixed>>
8788
*/
8889
private static function filterTrace(
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Doctrine;
6+
7+
/**
8+
* Simulates Doctrine DBAL's AbstractNamedObject
9+
*
10+
* @template N of Name
11+
*/
12+
abstract class AbstractNamedObject
13+
{
14+
/**
15+
* The property is an object of type Name (template N)
16+
*
17+
* @var N
18+
*/
19+
protected Name $name;
20+
21+
/**
22+
* The constructor parameter is a native scalar string
23+
*/
24+
public function __construct(string $name)
25+
{
26+
}
27+
}

tests/Fixtures/Doctrine/Column.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Doctrine;
6+
7+
/**
8+
* Simulates Doctrine DBAL's Column
9+
*
10+
* @extends AbstractNamedObject<UnqualifiedName>
11+
*/
12+
class Column extends AbstractNamedObject
13+
{
14+
public function __construct(string $name)
15+
{
16+
parent::__construct($name);
17+
}
18+
}

tests/Fixtures/Doctrine/Name.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Doctrine;
6+
7+
abstract class Name
8+
{
9+
public function __construct(public string $identifier)
10+
{
11+
}
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Doctrine;
6+
7+
class UnqualifiedName extends Name
8+
{
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use TypePHP\Tests\Fixtures\Doctrine\Column;
6+
7+
describe('Constructor Parameter vs Property Type Conflict (Doctrine DBAL AbstractNamedObject)', function () {
8+
test('does not override native scalar constructor parameter with conflicting object property @var docblock', function () {
9+
$column = new Column('"id"');
10+
11+
expect($column)->toBeInstanceOf(Column::class);
12+
});
13+
});

0 commit comments

Comments
 (0)