diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 702c0718bfc..02d1c13fb68 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -374,14 +374,6 @@
properties[0]]]>
-
-
- $callable
-
-
- TCallable|TClosure|null
-
-
properties[0]]]>
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php
index 76c7073c12e..98d6acc1c84 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php
@@ -898,7 +898,7 @@ public static function analyzeAssignmentRef(
PhpParser\Node\Expr\AssignRef $stmt,
Context $context
): bool {
- ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context, false, null, false, null, true);
+ ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context, false, null, false, true);
$lhs_var_id = ExpressionIdentifier::getExtendedVarId(
$stmt->var,
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php
index 653ffedc9ac..36971a73e95 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php
@@ -195,19 +195,7 @@ public static function analyze(
$toggled_class_exists = true;
}
- $high_order_template_result = null;
- $high_order_callable_info = $param
- ? HighOrderFunctionArgHandler::getCallableArgInfo($context, $arg->value, $statements_analyzer, $param)
- : null;
-
- if ($param && $high_order_callable_info) {
- $high_order_template_result = HighOrderFunctionArgHandler::remapLowerBounds(
- $statements_analyzer,
- $template_result ?? new TemplateResult([], []),
- $high_order_callable_info,
- $param->type ?? Type::getMixed(),
- );
- } elseif (($arg->value instanceof PhpParser\Node\Expr\Closure
+ if (($arg->value instanceof PhpParser\Node\Expr\Closure
|| $arg->value instanceof PhpParser\Node\Expr\ArrowFunction)
&& $param
&& !$arg->value->getDocComment()
@@ -227,15 +215,7 @@ public static function analyze(
$was_inside_call = $context->inside_call;
$context->inside_call = true;
- if (ExpressionAnalyzer::analyze(
- $statements_analyzer,
- $arg->value,
- $context,
- false,
- null,
- false,
- $high_order_template_result,
- ) === false) {
+ if (ExpressionAnalyzer::analyze($statements_analyzer, $arg->value, $context) === false) {
$context->inside_call = $was_inside_call;
return false;
@@ -243,16 +223,6 @@ public static function analyze(
$context->inside_call = $was_inside_call;
- if ($high_order_callable_info && $high_order_template_result) {
- HighOrderFunctionArgHandler::enhanceCallableArgType(
- $context,
- $arg->value,
- $statements_analyzer,
- $high_order_callable_info,
- $high_order_template_result,
- );
- }
-
if (($argument_offset === 0 && $method_id === 'array_filter' && count($args) === 2)
|| ($argument_offset > 0 && $method_id === 'array_map' && count($args) >= 2)
) {
@@ -266,28 +236,6 @@ public static function analyze(
);
}
- $inferred_arg_type = $statements_analyzer->node_data->getType($arg->value);
-
- if (null !== $inferred_arg_type
- && null !== $template_result
- && null !== $param
- && null !== $param->type
- && !$arg->unpack
- ) {
- $codebase = $statements_analyzer->getCodebase();
-
- TemplateStandinTypeReplacer::fillTemplateResult(
- $param->type,
- $template_result,
- $codebase,
- $statements_analyzer,
- $inferred_arg_type,
- $argument_offset,
- $context->self,
- $context->calling_method_id ?: $context->calling_function_id,
- );
- }
-
if ($toggled_class_exists) {
$context->inside_class_exists = false;
}
@@ -468,28 +416,13 @@ private static function handleClosureArg(
if ($replaced_type_part instanceof TCallable
|| $replaced_type_part instanceof TClosure
) {
- if (isset($replaced_type_part->params[$closure_param_offset]->type)) {
- $replaced_param_type = $replaced_type_part->params[$closure_param_offset]->type;
-
- if ($replaced_param_type->hasTemplate()) {
- $replaced_param_type = TypeExpander::expandUnion(
- $codebase,
- $replaced_param_type,
- null,
- null,
- null,
- true,
- false,
- false,
- true,
- true,
- );
- }
-
+ if (isset($replaced_type_part->params[$closure_param_offset]->type)
+ && !$replaced_type_part->params[$closure_param_offset]->type->hasTemplate()
+ ) {
if ($param_storage->type && !$param_type_inferred) {
$type_match_found = UnionTypeComparator::isContainedBy(
$codebase,
- $replaced_param_type,
+ $replaced_type_part->params[$closure_param_offset]->type,
$param_storage->type,
);
@@ -500,7 +433,7 @@ private static function handleClosureArg(
$newly_inferred_type = Type::combineUnionTypes(
$newly_inferred_type,
- $replaced_param_type,
+ $replaced_type_part->params[$closure_param_offset]->type,
$codebase,
);
}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php
index 21bdf3f9ea7..01e90cd2bcf 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php
@@ -83,8 +83,7 @@ class FunctionCallAnalyzer extends CallAnalyzer
public static function analyze(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\FuncCall $stmt,
- Context $context,
- ?TemplateResult $template_result = null
+ Context $context
): bool {
$function_name = $stmt->name;
@@ -167,15 +166,7 @@ public static function analyze(
$set_inside_conditional = true;
}
- if (!$template_result) {
- $template_result = new TemplateResult([], []);
- }
-
if (!$is_first_class_callable) {
- if (isset($function_call_info->function_storage->template_types)) {
- $template_result->template_types += $function_call_info->function_storage->template_types ?: [];
- }
-
ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),
@@ -183,7 +174,6 @@ public static function analyze(
$function_call_info->function_id,
$function_call_info->allow_named_args,
$context,
- $template_result,
);
}
@@ -209,8 +199,6 @@ public static function analyze(
}
}
- $already_inferred_lower_bounds = $template_result->lower_bounds;
-
$template_result = new TemplateResult([], []);
// do this here to allow closure param checks
@@ -235,11 +223,6 @@ public static function analyze(
$function_call_info->function_id,
);
- $template_result->lower_bounds = array_merge(
- $template_result->lower_bounds,
- $already_inferred_lower_bounds,
- );
-
if ($function_name instanceof PhpParser\Node\Name && $function_call_info->function_id) {
$stmt_type = FunctionCallReturnTypeFetcher::fetch(
$statements_analyzer,
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php
deleted file mode 100644
index 3d1a51c4e67..00000000000
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php
+++ /dev/null
@@ -1,325 +0,0 @@
- $_items
- * * param callable(A): B $_ab
- * * return list
- * function map(array $items, callable $ab): array { ... }
- *
- * // list
- * $numbers = [1, 2, 3];
- *
- * $result = map($numbers, id());
- * // $result is list because template T of id() was inferred by previous arg.
- * ```
- */
- public static function remapLowerBounds(
- StatementsAnalyzer $statements_analyzer,
- TemplateResult $inferred_template_result,
- HighOrderFunctionArgInfo $input_function,
- Union $container_function_type
- ): TemplateResult {
- // Try to infer container callable by $inferred_template_result
- $container_type = TemplateInferredTypeReplacer::replace(
- $container_function_type,
- $inferred_template_result,
- $statements_analyzer->getCodebase(),
- );
-
- $input_function_type = $input_function->getFunctionType();
- $input_function_template_result = $input_function->getTemplates();
-
- // Traverse side by side 'container' params and 'input' params.
- // This maps 'input' templates to 'container' templates.
- //
- // Example:
- // 'input' => Closure(C:Bar, D:Bar): array{C:Bar, D:Bar}
- // 'container' => Closure(int, string): array{int, string}
- //
- // $remapped_lower_bounds will be: [
- // 'C' => ['Bar' => [int]],
- // 'D' => ['Bar' => [string]]
- // ].
- foreach ($input_function_type->getAtomicTypes() as $input_atomic) {
- if (!$input_atomic instanceof TClosure && !$input_atomic instanceof TCallable) {
- continue;
- }
-
- foreach ($container_type->getAtomicTypes() as $container_atomic) {
- if (!$container_atomic instanceof TClosure && !$container_atomic instanceof TCallable) {
- continue;
- }
-
- foreach ($input_atomic->params ?? [] as $offset => $input_param) {
- if (!isset($container_atomic->params[$offset])) {
- continue;
- }
-
- TemplateStandinTypeReplacer::fillTemplateResult(
- $input_param->type ?? Type::getMixed(),
- $input_function_template_result,
- $statements_analyzer->getCodebase(),
- $statements_analyzer,
- $container_atomic->params[$offset]->type,
- );
- }
- }
- }
-
- return $input_function_template_result;
- }
-
- public static function enhanceCallableArgType(
- Context $context,
- PhpParser\Node\Expr $arg_expr,
- StatementsAnalyzer $statements_analyzer,
- HighOrderFunctionArgInfo $high_order_callable_info,
- TemplateResult $high_order_template_result
- ): void {
- // Psalm can infer simple callable/closure.
- // But can't infer first-class-callable or high-order function.
- if ($high_order_callable_info->getType() === HighOrderFunctionArgInfo::TYPE_CALLABLE) {
- return;
- }
-
- $fully_inferred_callable_type = TemplateInferredTypeReplacer::replace(
- $high_order_callable_info->getFunctionType(),
- $high_order_template_result,
- $statements_analyzer->getCodebase(),
- );
-
- // Some templates may not have been replaced.
- // They expansion makes error message better.
- $expanded = TypeExpander::expandUnion(
- $statements_analyzer->getCodebase(),
- $fully_inferred_callable_type,
- $context->self,
- $context->self,
- $context->parent,
- true,
- true,
- false,
- false,
- true,
- );
-
- $statements_analyzer->node_data->setType($arg_expr, $expanded);
- }
-
- public static function getCallableArgInfo(
- Context $context,
- PhpParser\Node\Expr $input_arg_expr,
- StatementsAnalyzer $statements_analyzer,
- FunctionLikeParameter $container_param
- ): ?HighOrderFunctionArgInfo {
- if (!self::isSupported($container_param)) {
- return null;
- }
-
- $codebase = $statements_analyzer->getCodebase();
-
- try {
- if ($input_arg_expr instanceof PhpParser\Node\Expr\FuncCall) {
- $function_id = strtolower((string) $input_arg_expr->name->getAttribute('resolvedName'));
-
- if (empty($function_id)) {
- return null;
- }
-
- $dynamic_storage = !$input_arg_expr->isFirstClassCallable()
- ? $codebase->functions->dynamic_storage_provider->getFunctionStorage(
- $input_arg_expr,
- $statements_analyzer,
- $function_id,
- $context,
- new CodeLocation($statements_analyzer, $input_arg_expr),
- )
- : null;
-
- return new HighOrderFunctionArgInfo(
- $input_arg_expr->isFirstClassCallable()
- ? HighOrderFunctionArgInfo::TYPE_FIRST_CLASS_CALLABLE
- : HighOrderFunctionArgInfo::TYPE_CALLABLE,
- $dynamic_storage ?? $codebase->functions->getStorage($statements_analyzer, $function_id),
- );
- }
-
- if ($input_arg_expr instanceof PhpParser\Node\Expr\MethodCall &&
- $input_arg_expr->var instanceof PhpParser\Node\Expr\Variable &&
- $input_arg_expr->name instanceof PhpParser\Node\Identifier &&
- is_string($input_arg_expr->var->name) &&
- isset($context->vars_in_scope['$' . $input_arg_expr->var->name])
- ) {
- $lhs_type = $context->vars_in_scope['$' . $input_arg_expr->var->name]->getSingleAtomic();
-
- if (!$lhs_type instanceof Type\Atomic\TNamedObject) {
- return null;
- }
-
- $method_id = new MethodIdentifier(
- $lhs_type->value,
- strtolower((string)$input_arg_expr->name),
- );
-
- return new HighOrderFunctionArgInfo(
- $input_arg_expr->isFirstClassCallable()
- ? HighOrderFunctionArgInfo::TYPE_FIRST_CLASS_CALLABLE
- : HighOrderFunctionArgInfo::TYPE_CALLABLE,
- $codebase->methods->getStorage($method_id),
- );
- }
-
- if ($input_arg_expr instanceof PhpParser\Node\Expr\StaticCall &&
- $input_arg_expr->name instanceof PhpParser\Node\Identifier
- ) {
- $method_id = new MethodIdentifier(
- (string)$input_arg_expr->class->getAttribute('resolvedName'),
- strtolower($input_arg_expr->name->toString()),
- );
-
- return new HighOrderFunctionArgInfo(
- $input_arg_expr->isFirstClassCallable()
- ? HighOrderFunctionArgInfo::TYPE_FIRST_CLASS_CALLABLE
- : HighOrderFunctionArgInfo::TYPE_CALLABLE,
- $codebase->methods->getStorage($method_id),
- );
- }
-
- if ($input_arg_expr instanceof PhpParser\Node\Scalar\String_) {
- return self::fromLiteralString(Type::getString($input_arg_expr->value), $statements_analyzer);
- }
-
- if ($input_arg_expr instanceof PhpParser\Node\Expr\ConstFetch) {
- $constant = $context->constants[$input_arg_expr->name->toString()] ?? null;
-
- return null !== $constant
- ? self::fromLiteralString($constant, $statements_analyzer)
- : null;
- }
-
- if ($input_arg_expr instanceof PhpParser\Node\Expr\ClassConstFetch &&
- $input_arg_expr->name instanceof PhpParser\Node\Identifier
- ) {
- $storage = $codebase->classlikes
- ->getStorageFor((string)$input_arg_expr->class->getAttribute('resolvedName'));
-
- $constant = null !== $storage
- ? $storage->constants[$input_arg_expr->name->toString()] ?? null
- : null;
-
- return null !== $constant && null !== $constant->type
- ? self::fromLiteralString($constant->type, $statements_analyzer)
- : null;
- }
-
- if ($input_arg_expr instanceof PhpParser\Node\Expr\New_ &&
- $input_arg_expr->class instanceof PhpParser\Node\Name
- ) {
- $class_storage = $codebase->classlikes
- ->getStorageFor((string) $input_arg_expr->class->getAttribute('resolvedName'));
-
- $invoke_storage = $class_storage && isset($class_storage->methods['__invoke'])
- ? $class_storage->methods['__invoke']
- : null;
-
- if (!$invoke_storage) {
- return null;
- }
-
- return new HighOrderFunctionArgInfo(
- HighOrderFunctionArgInfo::TYPE_CLASS_CALLABLE,
- $invoke_storage,
- $class_storage,
- );
- }
- } catch (UnexpectedValueException $e) {
- return null;
- }
-
- return null;
- }
-
- private static function isSupported(FunctionLikeParameter $container_param): bool
- {
- if (!$container_param->type || !$container_param->type->hasCallableType()) {
- return false;
- }
-
- foreach ($container_param->type->getAtomicTypes() as $a) {
- if (($a instanceof TClosure || $a instanceof TCallable) && !$a->params) {
- return false;
- }
-
- if ($a instanceof Type\Atomic\TCallableArray ||
- $a instanceof Type\Atomic\TCallableString ||
- $a instanceof Type\Atomic\TCallableKeyedArray
- ) {
- return false;
- }
- }
-
- return true;
- }
-
- private static function fromLiteralString(
- Union $constant,
- StatementsAnalyzer $statements_analyzer
- ): ?HighOrderFunctionArgInfo {
- $literal = $constant->isSingle() ? $constant->getSingleAtomic() : null;
-
- if (!$literal instanceof Type\Atomic\TLiteralString || empty($literal->value)) {
- return null;
- }
-
- $codebase = $statements_analyzer->getCodebase();
-
- return new HighOrderFunctionArgInfo(
- HighOrderFunctionArgInfo::TYPE_STRING_CALLABLE,
- strpos($literal->value, '::') !== false
- ? $codebase->methods->getStorage(MethodIdentifier::wrap($literal->value))
- : $codebase->functions->getStorage($statements_analyzer, strtolower($literal->value)),
- );
- }
-}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgInfo.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgInfo.php
deleted file mode 100644
index 526e6ee1141..00000000000
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgInfo.php
+++ /dev/null
@@ -1,90 +0,0 @@
-type = $type;
- $this->function_storage = $function_storage;
- $this->class_storage = $class_storage;
- }
-
- public function getTemplates(): TemplateResult
- {
- $templates = $this->class_storage
- ? array_merge(
- $this->function_storage->template_types ?? [],
- $this->class_storage->template_types ?? [],
- )
- : $this->function_storage->template_types ?? [];
-
- return new TemplateResult($templates, []);
- }
-
- public function getType(): string
- {
- return $this->type;
- }
-
- public function getFunctionType(): Union
- {
- switch ($this->type) {
- case self::TYPE_FIRST_CLASS_CALLABLE:
- return new Union([
- new TClosure(
- 'Closure',
- $this->function_storage->params,
- $this->function_storage->return_type,
- $this->function_storage->pure,
- ),
- ]);
-
- case self::TYPE_STRING_CALLABLE:
- case self::TYPE_CLASS_CALLABLE:
- return new Union([
- new TCallable(
- 'callable',
- $this->function_storage->params,
- $this->function_storage->return_type,
- $this->function_storage->pure,
- ),
- ]);
-
- default:
- return $this->function_storage->return_type ?? Type::getMixed();
- }
- }
-}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php
index 4e6d3188211..12c5197f3e8 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php
@@ -78,8 +78,7 @@ public static function analyze(
?Atomic $static_type,
bool $is_intersection,
?string $lhs_var_id,
- AtomicMethodCallAnalysisResult $result,
- ?TemplateResult $inferred_template_result = null
+ AtomicMethodCallAnalysisResult $result
): void {
if ($lhs_type_part instanceof TTemplateParam
&& !$lhs_type_part->as->isMixed()
@@ -113,7 +112,6 @@ public static function analyze(
$context,
$lhs_type_part->callable,
$result,
- $inferred_template_result,
);
return;
}
@@ -491,7 +489,6 @@ public static function analyze(
$lhs_var_id,
$method_id,
$result,
- $inferred_template_result,
);
$statements_analyzer->node_data = $old_node_data;
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php
index 980043f1ce7..5b3b8345ba6 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php
@@ -71,8 +71,7 @@ public static function analyze(
?Atomic $static_type,
?string $lhs_var_id,
MethodIdentifier $method_id,
- AtomicMethodCallAnalysisResult $result,
- ?TemplateResult $inferred_template_result = null
+ AtomicMethodCallAnalysisResult $result
): Union {
$config = $codebase->config;
@@ -225,13 +224,6 @@ public static function analyze(
$template_result = new TemplateResult([], $class_template_params ?: []);
$template_result->lower_bounds += $method_template_params;
- if ($inferred_template_result) {
- $template_result->lower_bounds += $inferred_template_result->lower_bounds;
- }
- if ($method_storage && $method_storage->template_types) {
- $template_result->template_types += $method_storage->template_types;
- }
-
if ($codebase->store_node_types
&& !$stmt->isFirstClassCallable()
&& !$context->collect_initializations
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php
index 437f65d7510..59908857f4b 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php
@@ -47,8 +47,7 @@ public static function analyze(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\MethodCall $stmt,
Context $context,
- bool $real_method_call = true,
- ?TemplateResult $template_result = null
+ bool $real_method_call = true
): bool {
$was_inside_call = $context->inside_call;
@@ -208,7 +207,6 @@ public static function analyze(
false,
$lhs_var_id,
$result,
- $template_result,
);
if (isset($context->vars_in_scope[$lhs_var_id])
&& ($possible_new_class_type = $context->vars_in_scope[$lhs_var_id]) instanceof Union
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php
index 45476ca22b6..ceffa4fd60e 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php
@@ -72,8 +72,7 @@ class NewAnalyzer extends CallAnalyzer
public static function analyze(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\New_ $stmt,
- Context $context,
- TemplateResult $template_result = null
+ Context $context
): bool {
$fq_class_name = null;
@@ -255,7 +254,6 @@ public static function analyze(
$fq_class_name,
$from_static,
$can_extend,
- $template_result,
);
} else {
ArgumentsAnalyzer::analyze(
@@ -291,8 +289,7 @@ private static function analyzeNamedConstructor(
Context $context,
string $fq_class_name,
bool $from_static,
- bool $can_extend,
- TemplateResult $template_result = null
+ bool $can_extend
): void {
$storage = $codebase->classlike_storage_provider->get($fq_class_name);
@@ -393,7 +390,7 @@ private static function analyzeNamedConstructor(
);
}
- $template_result ??= new TemplateResult([], []);
+ $template_result = new TemplateResult([], []);
if (self::checkMethodArgs(
$method_id,
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php
index fa7cc498184..9f675543611 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php
@@ -41,8 +41,7 @@ class StaticCallAnalyzer extends CallAnalyzer
public static function analyze(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\StaticCall $stmt,
- Context $context,
- ?TemplateResult $template_result = null
+ Context $context
): bool {
$method_id = null;
@@ -213,7 +212,6 @@ public static function analyze(
$moved_call,
$has_mock,
$has_existing_method,
- $template_result,
);
}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php
index b0839345418..e7f5b64d77e 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php
@@ -76,8 +76,7 @@ public static function analyze(
bool $ignore_nullable_issues,
bool &$moved_call,
bool &$has_mock,
- bool &$has_existing_method,
- ?TemplateResult $inferred_template_result = null
+ bool &$has_existing_method
): void {
$intersection_types = [];
@@ -213,7 +212,6 @@ public static function analyze(
$fq_class_name,
$moved_call,
$has_existing_method,
- $inferred_template_result,
);
} else {
if ($stmt->name instanceof PhpParser\Node\Expr) {
@@ -311,8 +309,7 @@ private static function handleNamedCall(
array $intersection_types,
string $fq_class_name,
bool &$moved_call,
- bool &$has_existing_method,
- ?TemplateResult $inferred_template_result = null
+ bool &$has_existing_method
): bool {
$codebase = $statements_analyzer->getCodebase();
@@ -889,7 +886,6 @@ private static function handleNamedCall(
$cased_method_id,
$class_storage,
$moved_call,
- $inferred_template_result,
);
return true;
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php
index 607f521881a..dcd03eb45b5 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php
@@ -64,8 +64,7 @@ public static function analyze(
MethodIdentifier $method_id,
string $cased_method_id,
ClassLikeStorage $class_storage,
- bool &$moved_call,
- ?TemplateResult $inferred_template_result = null
+ bool &$moved_call
): void {
$fq_class_name = $method_id->fq_class_name;
$method_name_lc = $method_id->method_name;
@@ -186,10 +185,6 @@ public static function analyze(
$template_result = new TemplateResult([], $found_generic_params ?: []);
- if ($inferred_template_result) {
- $template_result->lower_bounds += $inferred_template_result->lower_bounds;
- }
-
if (CallAnalyzer::checkMethodArgs(
$method_id,
$args,
diff --git a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php
index 7cdb1b54a05..6fd4448a5db 100644
--- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php
@@ -47,7 +47,6 @@
use Psalm\Internal\DataFlow\DataFlowNode;
use Psalm\Internal\DataFlow\TaintSink;
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
-use Psalm\Internal\Type\TemplateResult;
use Psalm\Issue\ForbiddenCode;
use Psalm\Issue\UnrecognizedExpression;
use Psalm\Issue\UnsupportedReferenceUsage;
@@ -78,7 +77,6 @@ public static function analyze(
bool $array_assignment = false,
?Context $global_context = null,
bool $from_stmt = false,
- ?TemplateResult $template_result = null,
bool $assigned_to_reference = false
): bool {
if (self::dispatchBeforeExpressionAnalysis($stmt, $context, $statements_analyzer) === false) {
@@ -94,7 +92,6 @@ public static function analyze(
$array_assignment,
$global_context,
$from_stmt,
- $template_result,
$assigned_to_reference,
) === false) {
return false;
@@ -149,7 +146,6 @@ private static function handleExpression(
bool $array_assignment,
?Context $global_context,
bool $from_stmt,
- ?TemplateResult $template_result = null,
bool $assigned_to_reference = false
): bool {
if ($stmt instanceof PhpParser\Node\Expr\Variable) {
@@ -174,11 +170,11 @@ private static function handleExpression(
}
if ($stmt instanceof PhpParser\Node\Expr\MethodCall) {
- return MethodCallAnalyzer::analyze($statements_analyzer, $stmt, $context, true, $template_result);
+ return MethodCallAnalyzer::analyze($statements_analyzer, $stmt, $context);
}
if ($stmt instanceof PhpParser\Node\Expr\StaticCall) {
- return StaticCallAnalyzer::analyze($statements_analyzer, $stmt, $context, $template_result);
+ return StaticCallAnalyzer::analyze($statements_analyzer, $stmt, $context);
}
if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) {
@@ -271,7 +267,7 @@ private static function handleExpression(
}
if ($stmt instanceof PhpParser\Node\Expr\New_) {
- return NewAnalyzer::analyze($statements_analyzer, $stmt, $context, $template_result);
+ return NewAnalyzer::analyze($statements_analyzer, $stmt, $context);
}
if ($stmt instanceof PhpParser\Node\Expr\Array_) {
@@ -287,7 +283,6 @@ private static function handleExpression(
$statements_analyzer,
$stmt,
$context,
- $template_result,
);
}
diff --git a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php
index 2999480dd36..b3898842d85 100644
--- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php
+++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php
@@ -10,9 +10,6 @@
use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Provider\NodeDataProvider;
-use Psalm\Internal\Type\TemplateInferredTypeReplacer;
-use Psalm\Internal\Type\TemplateResult;
-use Psalm\Internal\Type\TemplateStandinTypeReplacer;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Type;
use Psalm\Type\Atomic;
@@ -26,7 +23,6 @@
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TTemplateParam;
-use Psalm\Type\Union;
use UnexpectedValueException;
use function array_slice;
@@ -401,35 +397,6 @@ public static function getCallableFromAtomic(
if ($codebase->methods->methodExists($invoke_id)) {
$declaring_method_id = $codebase->methods->getDeclaringMethodId($invoke_id);
- $template_result = null;
-
- if ($input_type_part instanceof Atomic\TGenericObject) {
- $invokable_storage = $codebase->methods->getClassLikeStorageForMethod(
- $declaring_method_id ?? $invoke_id,
- );
- $type_params = [];
-
- foreach ($invokable_storage->template_types ?? [] as $template => $for_class) {
- foreach ($for_class as $type) {
- $type_params[] = new Type\Union([
- new TTemplateParam($template, $type, $input_type_part->value),
- ]);
- }
- }
-
- if (!empty($type_params)) {
- $input_with_templates = new Atomic\TGenericObject($input_type_part->value, $type_params);
- $template_result = new TemplateResult($invokable_storage->template_types ?? [], []);
-
- TemplateStandinTypeReplacer::fillTemplateResult(
- new Type\Union([$input_with_templates]),
- $template_result,
- $codebase,
- null,
- new Type\Union([$input_type_part]),
- );
- }
- }
if ($declaring_method_id) {
$method_storage = $codebase->methods->getStorage($declaring_method_id);
@@ -445,22 +412,12 @@ public static function getCallableFromAtomic(
);
}
- $callable = new TCallable(
+ return new TCallable(
'callable',
$method_storage->params,
$converted_return_type,
$method_storage->pure,
);
-
- if ($template_result) {
- $callable = TemplateInferredTypeReplacer::replace(
- new Union([$callable]),
- $template_result,
- $codebase,
- )->getSingleAtomic();
- }
-
- return $callable;
}
}
}
diff --git a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php
index 9fa51c420cd..65482e45692 100644
--- a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php
+++ b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php
@@ -232,17 +232,6 @@ private static function handleAtomicStandin(
);
}
- if ($atomic_type instanceof TTemplateParam
- && isset($template_result->lower_bounds[$atomic_type->param_name][$atomic_type->defining_class])
- ) {
- $most_specific_type = self::getMostSpecificTypeFromBounds(
- $template_result->lower_bounds[$atomic_type->param_name][$atomic_type->defining_class],
- $codebase,
- );
-
- return array_values($most_specific_type->getAtomicTypes());
- }
-
if ($atomic_type instanceof TTemplateParamClass
&& isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])
) {
@@ -271,14 +260,11 @@ private static function handleAtomicStandin(
$include_first = true;
- if (isset($template_result->lower_bounds[$atomic_type->array_param_name][$atomic_type->defining_class])
+ if (isset($template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class])
&& !empty($template_result->lower_bounds[$atomic_type->offset_param_name])
) {
$array_template_type
- = self::getMostSpecificTypeFromBounds(
- $template_result->lower_bounds[$atomic_type->array_param_name][$atomic_type->defining_class],
- $codebase,
- );
+ = $template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class];
$offset_template_type
= self::getMostSpecificTypeFromBounds(
array_values($template_result->lower_bounds[$atomic_type->offset_param_name])[0],
@@ -334,12 +320,7 @@ private static function handleAtomicStandin(
$include_first = true;
$template_type = null;
- if (isset($template_result->lower_bounds[$atomic_type->param_name][$atomic_type->defining_class])) {
- $template_type = self::getMostSpecificTypeFromBounds(
- $template_result->lower_bounds[$atomic_type->param_name][$atomic_type->defining_class],
- $codebase,
- );
- } elseif (isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])) {
+ if (isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])) {
$template_type = $template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class];
}
diff --git a/tests/CallableTest.php b/tests/CallableTest.php
index 5815222b1ad..22849d8fb34 100644
--- a/tests/CallableTest.php
+++ b/tests/CallableTest.php
@@ -109,902 +109,6 @@ public function __invoke(Closure ...$_fn): int
'ignored_issues' => [],
'php_version' => '7.4',
],
- 'inferArgFromClassContextInGenericContext' => [
- 'code' => '
- */
- public function map(Closure $ab): ArrayList
- {
- throw new RuntimeException("???");
- }
- }
-
- /**
- * @template T
- * @param ArrayList $list
- * @return ArrayList
- */
- function asTupled(ArrayList $list): ArrayList
- {
- return $list->map(function ($_a) {
- return [$_a];
- });
- }
- /** @var ArrayList $a */
- $a = new ArrayList();
- $b = asTupled($a);',
- 'assertions' => [
- '$b' => 'ArrayList',
- ],
- ],
- 'inferArgByPreviousMethodArg' => [
- 'code' => ' $list
- * @param callable(A): B $first
- * @param callable(B): C $second
- * @return list
- */
- public function map(array $list, callable $first, callable $second): array
- {
- throw new RuntimeException("never");
- }
- }
- $result = (new ArrayList())->map([1, 2, 3], fn($i) => ["num" => $i], fn($i) => ["object" => $i]);',
- 'assertions' => [
- '$result' => 'list',
- ],
- ],
- 'inferArgByPreviousFunctionArg' => [
- 'code' => ' $_collection
- * @param callable(A): B $_ab
- * @return list
- */
- function map(iterable $_collection, callable $_ab) { return []; }
-
- /** @template T */
- final class Foo
- {
- /** @return Foo */
- public function toInt() { throw new RuntimeException("???"); }
- }
-
- /** @var list> */
- $items = [];
-
- $inferred = map($items, function ($i) {
- return $i->toInt();
- });',
- 'assertions' => [
- '$inferred' => 'list>',
- ],
- ],
- 'inferTemplateForExplicitlyTypedArgByPreviousFunctionArg' => [
- 'code' => ' $_collection
- * @param callable(A): B $_ab
- * @return list
- */
- function map(iterable $_collection, callable $_ab) { return []; }
-
- /** @template T */
- final class Foo
- {
- /** @return Foo */
- public function toInt() { throw new RuntimeException("???"); }
- }
-
- /** @var list> */
- $items = [];
-
- $inferred = map($items, function (Foo $i) {
- return $i->toInt();
- });',
- 'assertions' => [
- '$inferred' => 'list>',
- ],
- ],
- 'doNotInferTemplateForExplicitlyTypedWithPhpdocArgByPreviousFunctionArg' => [
- 'code' => ' $_collection
- * @param callable(A): B $_ab
- * @return list
- */
- function map(iterable $_collection, callable $_ab) { return []; }
-
- /** @template T */
- final class Foo { }
-
- /** @var list> */
- $items = [];
-
- $inferred = map($items,
- /** @param Foo $i */
- function ($i) {
- return $i;
- }
- );',
- 'assertions' => [
- '$inferred' => 'list',
- ],
- ],
- 'inferTemplateOfHighOrderFunctionArgByPreviousArg' => [
- 'code' => '
- */
- function getList() { throw new RuntimeException("???"); }
-
- /**
- * @template T
- * @return Closure(T): T
- */
- function id() { throw new RuntimeException("???"); }
-
- /**
- * @template A
- * @template B
- *
- * @param list $_items
- * @param callable(A): B $_ab
- * @return list
- */
- function map(array $_items, callable $_ab) { throw new RuntimeException("???"); }
-
- $result = map(getList(), id());
- ',
- 'assertions' => [
- '$result' => 'list',
- ],
- ],
- 'inferTemplateOfHighOrderFunctionArgByPreviousArgInClassContext' => [
- 'code' => '
- */
- public function map(callable $ab) { throw new RuntimeException("???"); }
- }
-
- /**
- * @return ArrayList
- */
- function getList() { throw new RuntimeException("???"); }
-
- /**
- * @template T
- * @return Closure(T): T
- */
- function id() { throw new RuntimeException("???"); }
-
- $result = getList()->map(id());
- ',
- 'assertions' => [
- '$result' => 'ArrayList',
- ],
- ],
- 'inferTemplateOfHighOrderFunctionFromMethodArgByPreviousArg' => [
- 'code' => '): T
- */
- public function flatten() { throw new RuntimeException("???"); }
- }
- /**
- * @return list>
- */
- function getList() { throw new RuntimeException("???"); }
- /**
- * @template T
- * @return Closure(list): T
- */
- function flatten() { throw new RuntimeException("???"); }
- /**
- * @template A
- * @template B
- *
- * @param list $_a
- * @param callable(A): B $_ab
- * @return list
- */
- function map(array $_a, callable $_ab) { throw new RuntimeException("???"); }
-
- $ops = new Ops;
- $result = map(getList(), $ops->flatten());
- ',
- 'assertions' => [
- '$result' => 'list',
- ],
- ],
- 'inferTemplateOfHighOrderFunctionFromStaticMethodArgByPreviousArg' => [
- 'code' => '): T
- */
- public static function flatten() { throw new RuntimeException("???"); }
- }
- /**
- * @return list>
- */
- function getList() { throw new RuntimeException("???"); }
- /**
- * @template T
- * @return Closure(list): T
- */
- function flatten() { throw new RuntimeException("???"); }
- /**
- * @template A
- * @template B
- *
- * @param list $_a
- * @param callable(A): B $_ab
- * @return list
- */
- function map(array $_a, callable $_ab) { throw new RuntimeException("???"); }
-
- $result = map(getList(), StaticOps::flatten());
- ',
- 'assertions' => [
- '$result' => 'list',
- ],
- ],
- 'inferInvokableClassCallable' => [
- 'code' => 'ab = Closure::fromCallable($ab);
- }
-
- /**
- * @template K
- * @param array $a
- * @return array
- */
- public function __invoke(array $a): array
- {
- $b = [];
-
- foreach ($a as $k => $v) {
- $b[$k] = ($this->ab)($v);
- }
-
- return $b;
- }
- }
- /**
- * @template A
- * @template B
- * @param A $a
- * @param callable(A): B $ab
- * @return B
- */
- function pipe(mixed $a, callable $ab): mixed
- {
- return $ab($a);
- }
- /**
- * @return array
- */
- function getDict(): array
- {
- return ["fst" => 1, "snd" => 2, "thr" => 3];
- }
- $result = pipe(getDict(), new MapOperator(fn($i) => ["num" => $i]));
- ',
- 'assertions' => [
- '$result' => 'array',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.0',
- ],
- 'inferConstCallableLikeFirstClassCallable' => [
- 'code' => '): list
- */
- function map(callable $callback): Closure
- {
- return fn(array $list) => array_map($callback, $list);
- }
- /**
- * @template A
- * @template B
- * @param A $a
- * @param callable(A): B $ab
- * @return B
- */
- function pipe1(mixed $a, callable $ab): mixed
- {
- return $ab($a);
- }
- /**
- * @template A
- * @template B
- * @template C
- * @param A $a
- * @param callable(A): B $ab
- * @param callable(B): C $bc
- * @return C
- */
- function pipe2(mixed $a, callable $ab, callable $bc): mixed
- {
- return $bc($ab($a));
- }
- }
-
- namespace App {
- use Functions\Module;
- use function Functions\map;
- use function Functions\pipe1;
- use function Functions\pipe2;
- use const Functions\classId;
- use const Functions\id;
-
- $class_const_id = pipe1([42], Module::id);
- $class_const_composition = pipe1([42], map(Module::id));
- $class_const_sequential = pipe2([42], map(fn($i) => ["num" => $i]), Module::id);
-
- $class_const_alias_id = pipe1([42], classId);
- $class_const_alias_composition = pipe1([42], map(classId));
- $class_const_alias_sequential = pipe2([42], map(fn($i) => ["num" => $i]), classId);
-
- $const_id = pipe1([42], id);
- $const_composition = pipe1([42], map(id));
- $const_sequential = pipe2([42], map(fn($i) => ["num" => $i]), id);
-
- $string_id = pipe1([42], "Functions\id");
- $string_composition = pipe1([42], map("Functions\id"));
- $string_sequential = pipe2([42], map(fn($i) => ["num" => $i]), "Functions\id");
-
- $class_string_id = pipe1([42], "Functions\Module::id");
- $class_string_composition = pipe1([42], map("Functions\Module::id"));
- $class_string_sequential = pipe2([42], map(fn($i) => ["num" => $i]), "Functions\Module::id");
- }
- ',
- 'assertions' => [
- '$class_const_id===' => 'list{42}',
- '$class_const_composition===' => 'list<42>',
- '$class_const_sequential===' => 'list',
- '$class_const_alias_id===' => 'list{42}',
- '$class_const_alias_composition===' => 'list<42>',
- '$class_const_alias_sequential===' => 'list',
- '$const_id===' => 'list{42}',
- '$const_composition===' => 'list<42>',
- '$const_sequential===' => 'list',
- '$string_id===' => 'list{42}',
- '$string_composition===' => 'list<42>',
- '$string_sequential===' => 'list',
- '$class_string_id===' => 'list{42}',
- '$class_string_composition===' => 'list<42>',
- '$class_string_sequential===' => 'list',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.0',
- ],
- 'inferPipelineWithPartiallyAppliedFunctions' => [
- 'code' => '): list
- */
- function filter(callable $_predicate): Closure { throw new RuntimeException("???"); }
- /**
- * @template A
- * @template B
- *
- * @param callable(A): B $_ab
- * @return Closure(list): list
- */
- function map(callable $_ab): Closure { throw new RuntimeException("???"); }
- /**
- * @template T
- * @return (Closure(list): (non-empty-list | null))
- */
- function asNonEmptyList(): Closure { throw new RuntimeException("???"); }
- /**
- * @template T
- * @return Closure(T): T
- */
- function id(): Closure { throw new RuntimeException("???"); }
-
- /**
- * @template A
- * @template B
- * @template C
- * @template D
- * @template E
- * @template F
- *
- * @param A $arg
- * @param callable(A): B $ab
- * @param callable(B): C $bc
- * @param callable(C): D $cd
- * @param callable(D): E $de
- * @param callable(E): F $ef
- * @return F
- */
- function pipe4(mixed $arg, callable $ab, callable $bc, callable $cd, callable $de, callable $ef): mixed
- {
- return $ef($de($cd($bc($ab($arg)))));
- }
-
- /**
- * @template TFoo of string
- * @template TBar of bool
- */
- final class Item
- {
- /**
- * @param TFoo $foo
- * @param TBar $bar
- */
- public function __construct(
- public string $foo,
- public bool $bar,
- ) { }
- }
-
- /**
- * @return list-
- */
- function getList(): array { return []; }
-
- $result = pipe4(
- getList(),
- filter(fn($i) => $i->bar),
- filter(fn(Item $i) => $i->foo !== "bar"),
- map(fn($i) => new Item("test: " . $i->foo, $i->bar)),
- asNonEmptyList(),
- id(),
- );',
- 'assertions' => [
- '$result' => 'non-empty-list
- >|null',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.0',
- ],
- 'inferPipelineWithPartiallyAppliedFunctionsAndFirstClassCallable' => [
- 'code' => '): list
- */
- function map(callable $callback): Closure
- {
- return fn($array) => array_map($callback, $array);
- }
-
- /**
- * @return list
- */
- function getNums(): array
- {
- return [];
- }
-
- /**
- * @template T of float|int
- */
- final class ObjectNum
- {
- /**
- * @psalm-param T $value
- */
- public function __construct(
- public readonly float|int $value,
- ) {}
- }
-
- /**
- * @return list>
- */
- function getObjectNums(): array
- {
- return [];
- }
-
- $id = pipe(getNums(), id(...));
- $wrapped_id = pipe(getNums(), map(id(...)));
- $id_nested = pipe(getObjectNums(), map(id(...)));
- $id_nested_simple = pipe(getObjectNums(), id(...));
- ',
- 'assertions' => [
- '$id' => 'list',
- '$wrapped_id' => 'list',
- '$id_nested' => 'list>',
- '$id_nested_simple' => 'list>',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.1',
- ],
- 'inferFirstClassCallableWithGenericObject' => [
- 'code' => ' $container
- * @return A
- */
- function unwrap(Container $container)
- {
- return $container->value;
- }
- $result = pipe(
- new Container(42),
- unwrap(...),
- );
- ',
- 'assertions' => [
- '$result===' => '42',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.1',
- ],
- 'inferFirstClassCallableOnMethodCall' => [
- 'code' => 'a), $processB($this->b)];
- }
- }
-
- /**
- * @template A
- * @param A $value
- * @return A
- */
- function id(mixed $value): mixed
- {
- return $value;
- }
-
- function intToString(int $value): string
- {
- return (string) $value;
- }
-
- /**
- * @template A
- * @param A $value
- * @return list{A}
- */
- function singleToList(mixed $value): array
- {
- return [$value];
- }
-
- $processor = new Processor(a: 1, b: 2);
-
- $test_id = $processor->process(id(...), id(...));
- $test_complex = $processor->process(intToString(...), singleToList(...));
- ',
- 'assertions' => [
- '$test_id' => 'list{int, int}',
- '$test_complex' => 'list{string, list{int}}',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.1',
- ],
- 'inferFirstClassCallableOnMethodCallWithMultipleParams' => [
- 'code' => 'a, $this->b, $this->c);
- }
- }
-
- /**
- * @template A
- * @template B
- * @template C
- * @param A $value1
- * @param B $value2
- * @param C $value3
- * @return list{A, B, C}
- */
- function tripleId(mixed $value1, mixed $value2, mixed $value3): array
- {
- return [$value1, $value2, $value3];
- }
-
- $processor = new Processor(a: 1, b: 2, c: 3);
-
- $test = $processor->process(tripleId(...));
- ',
- 'assertions' => [
- '$test' => 'list{int, int, int}',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.1',
- ],
- 'inferFirstClassCallableOnMethodCallWithTemplatedAndNonTemplatedParams' => [
- 'code' => 'param1, $this->param2);
- }
- }
-
- /**
- * @template T of int|float
- * @param T $param2
- * @return array{param1: int, param2: T}
- */
- function appHandler1(int $param1, int|float $param2): array
- {
- return ["param1" => $param1, "param2" => $param2];
- }
-
- /**
- * @template T of int|float
- * @param T $param1
- * @return array{param1: T, param2: int}
- */
- function appHandler2(int|float $param1, int $param2): array
- {
- return ["param1" => $param1, "param2" => $param2];
- }
-
- /**
- * @return array{param1: int, param2: int}
- */
- function appHandler3(int $param1, int $param2): array
- {
- return ["param1" => $param1, "param2" => $param2];
- }
-
- $app = new App(param1: 42, param2: 42);
-
- $result1 = $app->run(appHandler1(...));
- $result2 = $app->run(appHandler2(...));
- $result3 = $app->run(appHandler3(...));
- ',
- 'assertions' => [
- '$result1===' => 'array{param1: int, param2: 42}',
- '$result2===' => 'array{param1: 42, param2: int}',
- '$result3===' => 'array{param1: int, param2: int}',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.1',
- ],
- 'inferTypeWhenClosureParamIsOmitted' => [
- 'code' => '): list
- */
- function iterate(callable $callback): Closure
- {
- return function(array $list) use ($callback) {
- foreach ($list as $item) {
- $callback($item);
- }
- return $list;
- };
- }
- $result1 = pipe(
- [1, 2, 3],
- iterate(fn($i) => print_r($i)),
- );
- $result2 = pipe(
- [1, 2, 3],
- iterate(fn() => print_r("noop")),
- );',
- 'assertions' => [
- '$result1===' => 'list<1|2|3>',
- '$result2===' => 'list<1|2|3>',
- ],
- 'ignored_issues' => [],
- 'php_version' => '8.1',
- ],
'varReturnType' => [
'code' => ' 'InvalidArgument',
],
- 'mismatchParamTypeFromDocblock' => [
- 'code' => '
- */
- public function map(Closure $effect): ArrayList
- {
- throw new RuntimeException("???");
- }
- }
-
- /**
- * @template T
- * @template B
- *
- * @param ArrayList $list
- * @return ArrayList
- */
- function genericContext(ArrayList $list): ArrayList
- {
- return $list->map(
- /** @param B $_a */
- function ($_a) {
- return [$_a];
- }
- );
- }',
- 'error_message' => 'InvalidArgument',
- ],
- 'invalidFirstClassCallableCannotBeInferred' => [
- 'code' => 'param1);
- }
- }
-
- /**
- * @template P1 of int|float
- * @param P1 $param1
- * @return array{param1: P1}
- */
- function appHandler(mixed $param1): array
- {
- return ["param1" => $param1];
- }
-
- $result = (new App(param1: [42]))->run(appHandler(...));
- ',
- 'error_message' => 'InvalidArgument',
- 'ignored_issues' => [],
- 'php_version' => '8.1',
- ],
'variadicClosureAssignability' => [
'code' => 'analyzeFile($file_path, new Context());
}
-
- public function testFunctionDynamicStorageProviderHook(): void
- {
- require_once __DIR__ . '/Plugin/StoragePlugin.php';
-
- $this->project_analyzer = $this->getProjectAnalyzerWithConfig(
- TestConfig::loadFromXML(
- dirname(__DIR__, 2) . DIRECTORY_SEPARATOR,
- '
-
-
-
-
-
-
-
- ',
- ),
- );
-
- $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer);
-
- $file_path = getcwd() . '/src/somefile.php';
-
- $this->addFile(
- $file_path,
- ' $_list
- */
- function acceptsList(array $_list): void { }
-
- /** @var list $list */
- $list = [1, 2, 3];
-
- $tuples = custom_array_map(
- fn($a) => $a + 1,
- fn($a) => ["num" => $a],
- $list
- );
-
- acceptsList($tuples);',
- );
-
- $this->analyzeFile($file_path, new Context());
- }
}
diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php
index 3858da04d8f..82cde3e1f44 100644
--- a/tests/Template/ClassTemplateTest.php
+++ b/tests/Template/ClassTemplateTest.php
@@ -2357,16 +2357,13 @@ public function map(callable $callback) {
}
}
- /** @param ArrayCollection> $ints */
+ /** @param ArrayCollection $ints */
function takesInts(ArrayCollection $ints) :void {}
/** @param ArrayCollection $ints */
function takesIntsOrStrings(ArrayCollection $ints) :void {}
- /** @return list */
- function getList() :array {return [];}
-
- takesInts((new ArrayCollection(getList()))->map("strlen"));
+ takesInts((new ArrayCollection(["a", "b"]))->map("strlen"));
/** @return ($s is "string" ? string : int) */
function foo(string $s) {
@@ -2376,7 +2373,7 @@ function foo(string $s) {
return 5;
}
- takesIntsOrStrings((new ArrayCollection(getList()))->map("foo"));
+ takesIntsOrStrings((new ArrayCollection(["a", "b"]))->map("foo"));
/**
* @template T