Skip to content

Commit af0a3dd

Browse files
phpstan-botclaude
authored andcommitted
Carry native callable types through ParametersAcceptorSelector for general native type resolution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9eb188b commit af0a3dd

2 files changed

Lines changed: 131 additions & 93 deletions

File tree

src/Analyser/NodeScopeResolver.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@
131131
use PHPStan\Reflection\ParameterReflection;
132132
use PHPStan\Reflection\ParametersAcceptor;
133133
use PHPStan\Reflection\ParametersAcceptorSelector;
134-
use PHPStan\Reflection\PassedByReference;
135-
use PHPStan\Reflection\Php\DummyParameter;
136134
use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection;
137135
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
138136
use PHPStan\Reflection\Php\PhpMethodReflection;

src/Reflection/ParametersAcceptorSelector.php

Lines changed: 131 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ public static function selectFromArgs(
8787
$arrayMapArgs = $args[0]->value->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
8888
if ($arrayMapArgs !== null) {
8989
$callbackParameters = [];
90+
$nativeCallbackParameters = [];
9091
foreach ($arrayMapArgs as $arg) {
9192
$argType = $scope->getType($arg->value);
93+
$nativeArgType = $scope->getNativeType($arg->value);
9294
if ($arg->unpack) {
9395
$constantArrays = $argType->getConstantArrays();
9496
if (count($constantArrays) > 0) {
@@ -99,35 +101,34 @@ public static function selectFromArgs(
99101
}
100102
}
101103
}
104+
$nativeConstantArrays = $nativeArgType->getConstantArrays();
105+
if (count($nativeConstantArrays) > 0) {
106+
foreach ($nativeConstantArrays as $constantArray) {
107+
$valueTypes = $constantArray->getValueTypes();
108+
foreach ($valueTypes as $valueType) {
109+
$nativeCallbackParameters[] = new DummyParameter('item', $scope->getIterableValueType($valueType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
110+
}
111+
}
112+
}
102113
} else {
103114
$callbackParameters[] = new DummyParameter('item', $scope->getIterableValueType($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
115+
$nativeCallbackParameters[] = new DummyParameter('item', $scope->getIterableValueType($nativeArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
104116
}
105117
}
106118

107119
$acceptor = $parametersAcceptors[0];
108120
$parameters = $acceptor->getParameters();
109121
if (isset($parameters[0])) {
110-
$parameters[0] = new NativeParameterReflection(
111-
$parameters[0]->getName(),
112-
$parameters[0]->isOptional(),
113-
new UnionType([
114-
new CallableType($callbackParameters, new MixedType(), false),
115-
new NullType(),
116-
]),
117-
$parameters[0]->passedByReference(),
118-
$parameters[0]->isVariadic(),
119-
$parameters[0]->getDefaultValue(),
120-
);
121-
$parametersAcceptors = [
122-
new FunctionVariant(
123-
$acceptor->getTemplateTypeMap(),
124-
$acceptor->getResolvedTemplateTypeMap(),
125-
$parameters,
126-
$acceptor->isVariadic(),
127-
$acceptor->getReturnType(),
128-
$acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
129-
),
130-
];
122+
$callableType = new UnionType([
123+
new CallableType($callbackParameters, new MixedType(), false),
124+
new NullType(),
125+
]);
126+
$nativeCallableType = new UnionType([
127+
new CallableType($nativeCallbackParameters, new MixedType(), false),
128+
new NullType(),
129+
]);
130+
$parameters[0] = self::createExtendedParameter($parameters[0], $callableType, $nativeCallableType);
131+
$parametersAcceptors = [self::createModifiedAcceptor($acceptor, $parameters)];
131132
}
132133
}
133134

@@ -228,52 +229,58 @@ public static function selectFromArgs(
228229
}
229230

230231
if ((bool) $args[0]->getAttribute(ArrayFilterArgVisitor::ATTRIBUTE_NAME)) {
232+
$arrayFilterParameters = null;
233+
$nativeArrayFilterParameters = null;
231234
if (isset($args[2])) {
232235
$mode = $scope->getType($args[2]->value);
233236
if ($mode instanceof ConstantIntegerType) {
234237
if ($mode->getValue() === ARRAY_FILTER_USE_KEY) {
235238
$arrayFilterParameters = [
236239
new DummyParameter('key', $scope->getIterableKeyType($scope->getType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
237240
];
241+
$nativeArrayFilterParameters = [
242+
new DummyParameter('key', $scope->getIterableKeyType($scope->getNativeType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
243+
];
238244
} elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) {
239245
$arrayFilterParameters = [
240246
new DummyParameter('item', $scope->getIterableValueType($scope->getType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
241247
new DummyParameter('key', $scope->getIterableKeyType($scope->getType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
242248
];
249+
$nativeArrayFilterParameters = [
250+
new DummyParameter('item', $scope->getIterableValueType($scope->getNativeType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
251+
new DummyParameter('key', $scope->getIterableKeyType($scope->getNativeType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
252+
];
243253
}
244254
}
245255
}
246256

247257
$acceptor = $parametersAcceptors[0];
248258
$parameters = $acceptor->getParameters();
249259
if (isset($parameters[1])) {
250-
$parameters[1] = new NativeParameterReflection(
251-
$parameters[1]->getName(),
252-
$parameters[1]->isOptional(),
253-
new UnionType([
254-
new CallableType(
255-
$arrayFilterParameters ?? [
256-
new DummyParameter('item', $scope->getIterableValueType($scope->getType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
257-
],
258-
new BooleanType(),
259-
false,
260-
),
261-
new NullType(),
262-
]),
263-
$parameters[1]->passedByReference(),
264-
$parameters[1]->isVariadic(),
265-
$parameters[1]->getDefaultValue(),
266-
);
267-
$parametersAcceptors = [
268-
new FunctionVariant(
269-
$acceptor->getTemplateTypeMap(),
270-
$acceptor->getResolvedTemplateTypeMap(),
271-
$parameters,
272-
$acceptor->isVariadic(),
273-
$acceptor->getReturnType(),
274-
$acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
260+
$arrayArgType = $scope->getType($args[0]->value);
261+
$nativeArrayArgType = $scope->getNativeType($args[0]->value);
262+
$callableType = new UnionType([
263+
new CallableType(
264+
$arrayFilterParameters ?? [
265+
new DummyParameter('item', $scope->getIterableValueType($arrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
266+
],
267+
new BooleanType(),
268+
false,
275269
),
276-
];
270+
new NullType(),
271+
]);
272+
$nativeCallableType = new UnionType([
273+
new CallableType(
274+
$nativeArrayFilterParameters ?? [
275+
new DummyParameter('item', $scope->getIterableValueType($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
276+
],
277+
new BooleanType(),
278+
false,
279+
),
280+
new NullType(),
281+
]);
282+
$parameters[1] = self::createExtendedParameter($parameters[1], $callableType, $nativeCallableType);
283+
$parametersAcceptors = [self::createModifiedAcceptor($acceptor, $parameters)];
277284
}
278285
}
279286

@@ -307,35 +314,28 @@ public static function selectFromArgs(
307314
}
308315

309316
if ((bool) $args[0]->getAttribute(ArrayWalkArgVisitor::ATTRIBUTE_NAME)) {
317+
$arrayArgType = $scope->getType($args[0]->value);
318+
$nativeArrayArgType = $scope->getNativeType($args[0]->value);
310319
$arrayWalkParameters = [
311-
new DummyParameter('item', $scope->getIterableValueType($scope->getType($args[0]->value)), optional: false, passedByReference: PassedByReference::createReadsArgument(), variadic: false, defaultValue: null),
312-
new DummyParameter('key', $scope->getIterableKeyType($scope->getType($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
320+
new DummyParameter('item', $scope->getIterableValueType($arrayArgType), optional: false, passedByReference: PassedByReference::createReadsArgument(), variadic: false, defaultValue: null),
321+
new DummyParameter('key', $scope->getIterableKeyType($arrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
322+
];
323+
$nativeArrayWalkParameters = [
324+
new DummyParameter('item', $scope->getIterableValueType($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createReadsArgument(), variadic: false, defaultValue: null),
325+
new DummyParameter('key', $scope->getIterableKeyType($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
313326
];
314327
if (isset($args[2])) {
315328
$arrayWalkParameters[] = new DummyParameter('arg', $scope->getType($args[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
329+
$nativeArrayWalkParameters[] = new DummyParameter('arg', $scope->getNativeType($args[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
316330
}
317331

318332
$acceptor = $parametersAcceptors[0];
319333
$parameters = $acceptor->getParameters();
320334
if (isset($parameters[1])) {
321-
$parameters[1] = new NativeParameterReflection(
322-
$parameters[1]->getName(),
323-
$parameters[1]->isOptional(),
324-
new CallableType($arrayWalkParameters, new MixedType(), false),
325-
$parameters[1]->passedByReference(),
326-
$parameters[1]->isVariadic(),
327-
$parameters[1]->getDefaultValue(),
328-
);
329-
$parametersAcceptors = [
330-
new FunctionVariant(
331-
$acceptor->getTemplateTypeMap(),
332-
$acceptor->getResolvedTemplateTypeMap(),
333-
$parameters,
334-
$acceptor->isVariadic(),
335-
$acceptor->getReturnType(),
336-
$acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
337-
),
338-
];
335+
$callableType = new CallableType($arrayWalkParameters, new MixedType(), false);
336+
$nativeCallableType = new CallableType($nativeArrayWalkParameters, new MixedType(), false);
337+
$parameters[1] = self::createExtendedParameter($parameters[1], $callableType, $nativeCallableType);
338+
$parametersAcceptors = [self::createModifiedAcceptor($acceptor, $parameters)];
339339
}
340340
}
341341

@@ -344,31 +344,25 @@ public static function selectFromArgs(
344344
$parameters = $acceptor->getParameters();
345345
if (isset($parameters[1])) {
346346
$argType = $scope->getType($args[0]->value);
347-
$parameters[1] = new NativeParameterReflection(
348-
$parameters[1]->getName(),
349-
$parameters[1]->isOptional(),
350-
new CallableType(
351-
[
352-
new DummyParameter('value', $scope->getIterableValueType($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
353-
new DummyParameter('key', $scope->getIterableKeyType($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
354-
],
355-
new BooleanType(),
356-
false,
357-
),
358-
$parameters[1]->passedByReference(),
359-
$parameters[1]->isVariadic(),
360-
$parameters[1]->getDefaultValue(),
347+
$nativeArgType = $scope->getNativeType($args[0]->value);
348+
$callableType = new CallableType(
349+
[
350+
new DummyParameter('value', $scope->getIterableValueType($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
351+
new DummyParameter('key', $scope->getIterableKeyType($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
352+
],
353+
new BooleanType(),
354+
false,
361355
);
362-
$parametersAcceptors = [
363-
new FunctionVariant(
364-
$acceptor->getTemplateTypeMap(),
365-
$acceptor->getResolvedTemplateTypeMap(),
366-
$parameters,
367-
$acceptor->isVariadic(),
368-
$acceptor->getReturnType(),
369-
$acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
370-
),
371-
];
356+
$nativeCallableType = new CallableType(
357+
[
358+
new DummyParameter('value', $scope->getIterableValueType($nativeArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
359+
new DummyParameter('key', $scope->getIterableKeyType($nativeArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
360+
],
361+
new BooleanType(),
362+
false,
363+
);
364+
$parameters[1] = self::createExtendedParameter($parameters[1], $callableType, $nativeCallableType);
365+
$parametersAcceptors = [self::createModifiedAcceptor($acceptor, $parameters)];
372366
}
373367
}
374368

@@ -1238,4 +1232,50 @@ private static function getCurlOptValueType(int $curlOpt): ?Type
12381232
return null;
12391233
}
12401234

1235+
private static function createExtendedParameter(ParameterReflection $original, Type $type, Type $nativeType): ExtendedDummyParameter
1236+
{
1237+
return new ExtendedDummyParameter(
1238+
$original->getName(),
1239+
$type,
1240+
$original->isOptional(),
1241+
$original->passedByReference(),
1242+
$original->isVariadic(),
1243+
$original->getDefaultValue(),
1244+
$nativeType,
1245+
$type,
1246+
$original instanceof ExtendedParameterReflection ? $original->getOutType() : null,
1247+
$original instanceof ExtendedParameterReflection ? $original->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(),
1248+
$original instanceof ExtendedParameterReflection ? $original->getClosureThisType() : null,
1249+
$original instanceof ExtendedParameterReflection ? $original->getAttributes() : [],
1250+
);
1251+
}
1252+
1253+
/**
1254+
* @param list<ParameterReflection> $parameters
1255+
*/
1256+
private static function createModifiedAcceptor(ParametersAcceptor $acceptor, array $parameters): ParametersAcceptor
1257+
{
1258+
if ($acceptor instanceof ExtendedParametersAcceptor) {
1259+
return new ExtendedFunctionVariant(
1260+
$acceptor->getTemplateTypeMap(),
1261+
$acceptor->getResolvedTemplateTypeMap(),
1262+
array_map(static fn (ParameterReflection $p): ExtendedParameterReflection => $p instanceof ExtendedParameterReflection ? $p : self::wrapParameter($p), $parameters),
1263+
$acceptor->isVariadic(),
1264+
$acceptor->getReturnType(),
1265+
$acceptor->getPhpDocReturnType(),
1266+
$acceptor->getNativeReturnType(),
1267+
$acceptor->getCallSiteVarianceMap(),
1268+
);
1269+
}
1270+
1271+
return new FunctionVariant(
1272+
$acceptor->getTemplateTypeMap(),
1273+
$acceptor->getResolvedTemplateTypeMap(),
1274+
$parameters,
1275+
$acceptor->isVariadic(),
1276+
$acceptor->getReturnType(),
1277+
TemplateTypeVarianceMap::createEmpty(),
1278+
);
1279+
}
1280+
12411281
}

0 commit comments

Comments
 (0)