Skip to content

Commit bcc13a0

Browse files
authored
Merge branch 'main' into field-type-deprecated-translated-category
2 parents 9deecd9 + 59b9de0 commit bcc13a0

39 files changed

+1107
-133
lines changed

.github/workflows/php.yml

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
- "8.1"
1818
- "8.2"
1919
- "8.3"
20+
- "8.4"
2021
steps:
2122
- name: "Checkout"
2223
uses: "actions/checkout@v4"
@@ -41,12 +42,14 @@ jobs:
4142
matrix:
4243
experimental: [false]
4344
php-version:
44-
- "8.1"
45-
- "8.2"
4645
- "8.3"
4746
drupal:
48-
- "^10"
47+
- "~11.0.0"
48+
- "~11.1.0"
4949
include:
50+
- php-version: "8.1"
51+
drupal: "^10.4"
52+
experimental: false
5053
- php-version: "8.3"
5154
drupal: "11.x-dev"
5255
experimental: true
@@ -78,11 +81,14 @@ jobs:
7881
matrix:
7982
experimental: [false]
8083
php-version:
81-
- "8.1"
82-
- "8.2"
84+
- "8.3"
8385
drupal:
84-
- "^10"
86+
- "~11.0.0"
87+
- "~11.1.0"
8588
include:
89+
- php-version: "8.1"
90+
drupal: "^10.4"
91+
experimental: false
8692
- php-version: "8.3"
8793
drupal: "11.x-dev"
8894
experimental: true
@@ -151,11 +157,14 @@ jobs:
151157
matrix:
152158
experimental: [false]
153159
php-version:
154-
- "8.1"
155-
- "8.2"
160+
- "8.3"
156161
drupal:
157-
- "^10"
162+
- "~11.0.0"
163+
- "~11.1.0"
158164
include:
165+
- php-version: "8.1"
166+
drupal: "^10.4"
167+
experimental: false
159168
- php-version: "8.3"
160169
drupal: "11.x-dev"
161170
experimental: true

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ those namespaces so that PHPStan can properly discover symbols in your Drupal co
1010

1111
> [!NOTE]
1212
> With Drupal 11.2, Drupal core is now using PHPStan 2.0. The 1.x branch of phpstan-drupal will be supported until
13-
> 11.1 loses security support when 11.3 is released.
13+
> Drupal 10 loses security support when Drupal 12 is released.
1414
1515
## Sponsors
1616

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
},
4848
"classmap": [
4949
"tests/src/Type/data",
50-
"tests/src/Rules/data"
50+
"tests/src/Rules/data",
51+
"tests/src/Generics/data"
5152
]
5253
},
5354
"extra": {

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ parameters:
1212
- tests/src
1313
excludePaths:
1414
- tests/src/data/*.php
15+
- tests/src/Generics/data/*.php
1516
- tests/src/Type/data/*.php
1617
- tests/src/Rules/data/*.php
1718
- tests/src/DeprecatedScope/data/*.php

src/Type/InspectorTypeExtension.php

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use PHPStan\Type\Accessory\NonEmptyArrayType;
1818
use PHPStan\Type\ArrayType;
1919
use PHPStan\Type\CallableType;
20-
use PHPStan\Type\ClosureType;
2120
use PHPStan\Type\Constant\ConstantStringType;
2221
use PHPStan\Type\FloatType;
2322
use PHPStan\Type\IntegerRangeType;
@@ -104,14 +103,37 @@ private function specifyAssertAll(MethodReflection $staticMethodReflection, Stat
104103
$callable = $node->getArgs()[0]->value;
105104
$callableInfo = $scope->getType($callable);
106105

107-
if (!$callableInfo instanceof ClosureType) {
106+
if (!$callableInfo->isCallable()->yes()) {
107+
return new SpecifiedTypes();
108+
}
109+
110+
$traversable = $node->getArgs()[1]->value;
111+
$traversableInfo = $scope->getType($traversable);
112+
113+
// If it is already not mixed (narrowed by other code, like
114+
// '::assertAllArray()'), we could not provide any additional
115+
// information. We can only narrow this method to 'array<mixed, mixed>'.
116+
if (!$traversableInfo->equals(new MixedType())) {
117+
return new SpecifiedTypes();
118+
}
119+
120+
// In a negation context, we cannot precisely narrow types because we do
121+
// not know the exact logic of the callable function. This means we
122+
// cannot safely return 'mixed~iterable' since the value might still be
123+
// a valid iterable.
124+
//
125+
// For example, a negation context with an 'is_string(...)' callable
126+
// does not necessarily mean that the value cannot be an
127+
// 'iterable<int>'. In such cases, it is safer to skip type narrowing
128+
// altogether to prevent introducing new bugs into the code.
129+
if ($context->false()) {
108130
return new SpecifiedTypes();
109131
}
110132

111133
return $this->typeSpecifier->create(
112134
$node->getArgs()[1]->value,
113-
new IterableType(new MixedType(true), $callableInfo->getReturnType()),
114-
TypeSpecifierContext::createTruthy(),
135+
new IterableType(new MixedType(), new MixedType()),
136+
$context,
115137
$scope,
116138
);
117139
}
@@ -123,8 +145,8 @@ private function specifyAssertAllStrings(MethodReflection $staticMethodReflectio
123145
{
124146
return $this->typeSpecifier->create(
125147
$node->getArgs()[0]->value,
126-
new IterableType(new MixedType(true), new StringType()),
127-
TypeSpecifierContext::createTruthy(),
148+
new IterableType(new MixedType(), new StringType()),
149+
$context,
128150
$scope,
129151
);
130152
}
@@ -136,12 +158,12 @@ private function specifyAssertAllStringable(MethodReflection $staticMethodReflec
136158
{
137159
// Drupal considers string as part of "stringable" as well.
138160
$stringable = TypeCombinator::union(new ObjectType(Stringable::class), new StringType());
139-
$newType = new IterableType(new MixedType(true), $stringable);
161+
$newType = new IterableType(new MixedType(), $stringable);
140162

141163
return $this->typeSpecifier->create(
142164
$node->getArgs()[0]->value,
143165
$newType,
144-
TypeSpecifierContext::createTruthy(),
166+
$context,
145167
$scope,
146168
);
147169
}
@@ -151,13 +173,13 @@ private function specifyAssertAllStringable(MethodReflection $staticMethodReflec
151173
*/
152174
private function specifyAssertAllArrays(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
153175
{
154-
$arrayType = new ArrayType(new MixedType(true), new MixedType(true));
155-
$newType = new IterableType(new MixedType(true), $arrayType);
176+
$arrayType = new ArrayType(new MixedType(), new MixedType());
177+
$newType = new IterableType(new MixedType(), $arrayType);
156178

157179
return $this->typeSpecifier->create(
158180
$node->getArgs()[0]->value,
159181
$newType,
160-
TypeSpecifierContext::createTruthy(),
182+
$context,
161183
$scope,
162184
);
163185
}
@@ -171,13 +193,13 @@ private function specifyAssertStrictArray(MethodReflection $staticMethodReflecti
171193
// In Drupal, 'strict arrays' are defined as arrays whose indexes
172194
// consist of integers that are equal to or greater than 0.
173195
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
174-
new MixedType(true),
196+
new MixedType(),
175197
);
176198

177199
return $this->typeSpecifier->create(
178200
$node->getArgs()[0]->value,
179201
$newType,
180-
TypeSpecifierContext::createTruthy(),
202+
$context,
181203
$scope,
182204
);
183205
}
@@ -188,17 +210,17 @@ private function specifyAssertStrictArray(MethodReflection $staticMethodReflecti
188210
private function specifyAssertAllStrictArrays(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
189211
{
190212
$newType = new IterableType(
191-
new MixedType(true),
213+
new MixedType(),
192214
new ArrayType(
193215
IntegerRangeType::createAllGreaterThanOrEqualTo(0),
194-
new MixedType(true),
216+
new MixedType(),
195217
),
196218
);
197219

198220
return $this->typeSpecifier->create(
199221
$node->getArgs()[0]->value,
200222
$newType,
201-
TypeSpecifierContext::createTruthy(),
223+
$context,
202224
$scope,
203225
);
204226
}
@@ -229,17 +251,20 @@ private function specifyAssertAllHaveKey(MethodReflection $staticMethodReflectio
229251
}
230252
}
231253

232-
$keyTypes = [];
254+
// @see ArrayKeyExistsFunctionTypeSpecifyingExtension.
255+
$possibleTypes = [
256+
new ArrayType(new MixedType(), new MixedType())
257+
];
233258
foreach ($keys as $key) {
234-
$keyTypes[] = new HasOffsetType(new ConstantStringType($key));
259+
$possibleTypes[] = new HasOffsetType(new ConstantStringType($key));
235260
}
236261

237-
$newArrayType = new ArrayType(
238-
new MixedType(true),
239-
new ArrayType(TypeCombinator::intersect(new MixedType(), ...$keyTypes), new MixedType(true)),
262+
$newType = new IterableType(
263+
new MixedType(),
264+
TypeCombinator::intersect(...$possibleTypes),
240265
);
241266

242-
return $this->typeSpecifier->create($traversableArg, $newArrayType, TypeSpecifierContext::createTruthy(), $scope);
267+
return $this->typeSpecifier->create($traversableArg, $newType, $context, $scope);
243268
}
244269

245270
/**
@@ -249,8 +274,8 @@ private function specifyAssertAllIntegers(MethodReflection $staticMethodReflecti
249274
{
250275
return $this->typeSpecifier->create(
251276
$node->getArgs()[0]->value,
252-
new IterableType(new MixedType(true), new IntegerType()),
253-
TypeSpecifierContext::createTruthy(),
277+
new IterableType(new MixedType(), new IntegerType()),
278+
$context,
254279
$scope,
255280
);
256281
}
@@ -262,8 +287,8 @@ private function specifyAssertAllFloat(MethodReflection $staticMethodReflection,
262287
{
263288
return $this->typeSpecifier->create(
264289
$node->getArgs()[0]->value,
265-
new IterableType(new MixedType(true), new FloatType()),
266-
TypeSpecifierContext::createTruthy(),
290+
new IterableType(new MixedType(), new FloatType()),
291+
$context,
267292
$scope,
268293
);
269294
}
@@ -275,8 +300,8 @@ private function specifyAssertAllCallable(MethodReflection $staticMethodReflecti
275300
{
276301
return $this->typeSpecifier->create(
277302
$node->getArgs()[0]->value,
278-
new IterableType(new MixedType(true), new CallableType()),
279-
TypeSpecifierContext::createTruthy(),
303+
new IterableType(new MixedType(), new CallableType()),
304+
$context,
280305
$scope,
281306
);
282307
}
@@ -295,12 +320,12 @@ private function specifyAssertAllNotEmpty(MethodReflection $staticMethodReflecti
295320
new FloatType(),
296321
new ResourceType(),
297322
];
298-
$newType = new IterableType(new MixedType(true), new UnionType($non_empty_types));
323+
$newType = new IterableType(new MixedType(), new UnionType($non_empty_types));
299324

300325
return $this->typeSpecifier->create(
301326
$node->getArgs()[0]->value,
302327
$newType,
303-
TypeSpecifierContext::createTruthy(),
328+
$context,
304329
$scope,
305330
);
306331
}
@@ -312,8 +337,8 @@ private function specifyAssertAllNumeric(MethodReflection $staticMethodReflectio
312337
{
313338
return $this->typeSpecifier->create(
314339
$node->getArgs()[0]->value,
315-
new IterableType(new MixedType(true), new UnionType([new IntegerType(), new FloatType()])),
316-
TypeSpecifierContext::createTruthy(),
340+
new IterableType(new MixedType(), new UnionType([new IntegerType(), new FloatType()])),
341+
$context,
317342
$scope,
318343
);
319344
}
@@ -325,8 +350,8 @@ private function specifyAssertAllMatch(MethodReflection $staticMethodReflection,
325350
{
326351
return $this->typeSpecifier->create(
327352
$node->getArgs()[1]->value,
328-
new IterableType(new MixedType(true), new StringType()),
329-
TypeSpecifierContext::createTruthy(),
353+
new IterableType(new MixedType(), new StringType()),
354+
$context,
330355
$scope,
331356
);
332357
}
@@ -340,8 +365,8 @@ private function specifyAssertAllRegularExpressionMatch(MethodReflection $static
340365
$node->getArgs()[1]->value,
341366
// Drupal treats any non-string input in traversable as invalid
342367
// value, so it is possible to narrow type here.
343-
new IterableType(new MixedType(true), new StringType()),
344-
TypeSpecifierContext::createTruthy(),
368+
new IterableType(new MixedType(), new StringType()),
369+
$context,
345370
$scope,
346371
);
347372
}
@@ -373,8 +398,8 @@ private function specifyAssertAllObjects(MethodReflection $staticMethodReflectio
373398

374399
return $this->typeSpecifier->create(
375400
$node->getArgs()[0]->value,
376-
new IterableType(new MixedType(true), TypeCombinator::union(...$objectTypes)),
377-
TypeSpecifierContext::createTruthy(),
401+
new IterableType(new MixedType(), TypeCombinator::union(...$objectTypes)),
402+
$context,
378403
$scope,
379404
);
380405
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Drupal\Component\Plugin;
4+
5+
interface DependentPluginInterface {
6+
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Drupal\Component\Plugin;
4+
5+
interface DerivativeInspectionInterface {
6+
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Drupal\Component\Plugin;
4+
5+
abstract class PluginBase implements PluginInspectionInterface, DerivativeInspectionInterface {
6+
7+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Drupal\Core\Field;
4+
5+
use Drupal\Core\Entity\EntityInterface;
6+
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
7+
8+
/**
9+
* @template T of EntityInterface
10+
* @extends FieldItemList<EntityReferenceItem<T>>
11+
* @implements EntityReferenceFieldItemListInterface<T>
12+
*/
13+
class EntityReferenceFieldItemList extends FieldItemList implements EntityReferenceFieldItemListInterface {
14+
15+
}

stubs/Drupal/Core/Field/FieldItemList.stub

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ namespace Drupal\Core\Field;
55
use Drupal\Core\TypedData\Plugin\DataType\ItemList;
66

77
/**
8-
* @template T of \Drupal\Core\Field\FieldItemInterface
8+
* @template T of FieldItemInterface
99
* @extends ItemList<T>
1010
* @implements FieldItemListInterface<T>
1111
*/
1212
class FieldItemList extends ItemList implements FieldItemListInterface {
1313

1414
/**
15-
* @return \Drupal\Core\Field\FieldItemInterface
15+
* @return T
1616
*/
17-
protected function createItem(int $offset = 0, ?mixed $value = NULL): \Drupal\Core\Field\FieldItemInterface {
18-
}
17+
protected function createItem(int $offset = 0, mixed $value = NULL): FieldItemInterface {}
1918

2019
}

0 commit comments

Comments
 (0)