1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Refactor FunctionLikeAnalyzer::analyze

This commit is contained in:
Brown 2019-08-22 10:29:21 -04:00
parent d69e8ddafa
commit 7797bfd71c

View File

@ -375,119 +375,8 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
}
}
$check_stmts = true;
if ($codebase->alter_code) {
foreach ($this->function->params as $param) {
$param_name_node = null;
if ($param->type instanceof PhpParser\Node\Name) {
$param_name_node = $param->type;
} elseif ($param->type instanceof PhpParser\Node\NullableType
&& $param->type->type instanceof PhpParser\Node\Name
) {
$param_name_node = $param->type->type;
}
if ($param_name_node) {
$resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($param_name_node, $this->getAliases());
$parent_fqcln = $this->getParentFQCLN();
if ($resolved_name === 'self' && $context->self) {
$resolved_name = (string) $context->self;
} elseif ($resolved_name === 'parent' && $parent_fqcln) {
$resolved_name = $parent_fqcln;
}
$codebase->classlikes->handleClassLikeReferenceInMigration(
$codebase,
$this,
$param_name_node,
$resolved_name,
$context->calling_method_id
);
}
}
if ($this->function->returnType) {
$return_name_node = null;
if ($this->function->returnType instanceof PhpParser\Node\Name) {
$return_name_node = $this->function->returnType;
} elseif ($this->function->returnType instanceof PhpParser\Node\NullableType
&& $this->function->returnType->type instanceof PhpParser\Node\Name
) {
$return_name_node = $this->function->returnType->type;
}
if ($return_name_node) {
$resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($return_name_node, $this->getAliases());
$parent_fqcln = $this->getParentFQCLN();
if ($resolved_name === 'self' && $context->self) {
$resolved_name = (string) $context->self;
} elseif ($resolved_name === 'parent' && $parent_fqcln) {
$resolved_name = $parent_fqcln;
}
$codebase->classlikes->handleClassLikeReferenceInMigration(
$codebase,
$this,
$return_name_node,
$resolved_name,
$context->calling_method_id
);
}
}
if ($storage->return_type
&& $storage->return_type_location
&& $storage->return_type_location !== $storage->signature_return_type_location
) {
$replace_type = ExpressionAnalyzer::fleshOutType(
$codebase,
$storage->return_type,
$context->self,
$context->self,
$this->getParentFQCLN(),
false
);
$codebase->classlikes->handleDocblockTypeInMigration(
$codebase,
$this,
$replace_type,
$storage->return_type_location,
$context->calling_method_id
);
}
foreach ($params as $function_param) {
if ($function_param->type
&& $function_param->type_location
&& $function_param->type_location !== $function_param->signature_type_location
&& $function_param->type_location->file_path === $this->getFilePath()
) {
$replace_type = ExpressionAnalyzer::fleshOutType(
$codebase,
$function_param->type,
$context->self,
$context->self,
$this->getParentFQCLN(),
false
);
$codebase->classlikes->handleDocblockTypeInMigration(
$codebase,
$this,
$replace_type,
$function_param->type_location,
$context->calling_method_id
);
}
}
$this->alterParams($codebase, $storage, $params, $context);
}
foreach ($codebase->methods_to_rename as $original_method_id => $new_method_name) {
@ -509,6 +398,394 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
}
}
$check_stmts = $this->processParams(
$statements_analyzer,
$storage,
$cased_method_id,
$params,
$context,
$implemented_docblock_param_types,
(bool) $non_null_param_types,
(bool) $template_types
);
if ($storage->pure) {
$context->pure = true;
}
if ($storage->unused_docblock_params) {
foreach ($storage->unused_docblock_params as $param_name => $param_location) {
if (IssueBuffer::accepts(
new InvalidDocblockParamName(
'Incorrect param name $' . $param_name . ' in docblock for ' . $cased_method_id,
$param_location
)
)) {
}
}
}
if (ReturnTypeAnalyzer::checkReturnType(
$this->function,
$project_analyzer,
$this,
$storage,
$context
) === false) {
$check_stmts = false;
}
if (!$check_stmts) {
return false;
}
if ($context->collect_initializations || $context->collect_mutations) {
$statements_analyzer->addSuppressedIssues([
'DocblockTypeContradiction',
'InvalidReturnStatement',
'RedundantCondition',
'RedundantConditionGivenDocblockType',
'TypeDoesNotContainNull',
'TypeDoesNotContainType',
'LoopInvalidation',
]);
if ($context->collect_initializations) {
$statements_analyzer->addSuppressedIssues([
'UndefinedInterfaceMethod',
'UndefinedMethod',
'PossiblyUndefinedMethod',
]);
}
}
$statements_analyzer->analyze($function_stmts, $context, $global_context, true);
$this->examineParamTypes($statements_analyzer, $context, $codebase);
foreach ($storage->params as $offset => $function_param) {
// only complain if there's no type defined by a parent type
if (!$function_param->type
&& $function_param->location
&& !isset($implemented_docblock_param_types[$offset])
) {
if ($this->function instanceof Closure) {
IssueBuffer::accepts(
new MissingClosureParamType(
'Parameter $' . $function_param->name . ' has no provided type',
$function_param->location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
);
} else {
IssueBuffer::accepts(
new MissingParamType(
'Parameter $' . $function_param->name . ' has no provided type',
$function_param->location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
);
}
}
}
if ($storage->signature_return_type && $storage->signature_return_type_location) {
list($start, $end) = $storage->signature_return_type_location->getSelectionBounds();
$codebase->analyzer->addOffsetReference(
$this->getFilePath(),
$start,
$end,
(string) $storage->signature_return_type
);
}
if ($this->function instanceof Closure) {
$this->verifyReturnType(
$statements_analyzer,
$storage->return_type,
$this->source->getFQCLN(),
$storage->return_type_location,
$global_context && $global_context->inside_call
);
$closure_yield_types = [];
$ignore_nullable_issues = false;
$ignore_falsable_issues = false;
$closure_return_types = ReturnTypeCollector::getReturnTypes(
$this->function->stmts,
$closure_yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues,
true
);
if ($closure_return_types) {
$closure_return_type = new Type\Union($closure_return_types);
if (!$storage->return_type
|| $storage->return_type->hasMixed()
|| TypeAnalyzer::isContainedBy(
$codebase,
$closure_return_type,
$storage->return_type
)
) {
if ($this->function->inferredType) {
/** @var Type\Atomic\TFn */
$closure_atomic = $this->function->inferredType->getTypes()['Closure'];
$closure_atomic->return_type = $closure_return_type;
}
}
}
}
if ($context->collect_references
&& !$context->collect_initializations
&& $codebase->find_unused_variables
&& $context->check_variables
) {
$this->checkParamReferences(
$statements_analyzer,
$storage,
$class_storage,
$context
);
}
foreach ($storage->throws as $expected_exception => $_) {
if (isset($storage->throw_locations[$expected_exception])) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$expected_exception,
$storage->throw_locations[$expected_exception],
$statements_analyzer->getSuppressedIssues(),
false,
false,
true,
true
)) {
$input_type = new Type\Union([new TNamedObject($expected_exception)]);
$container_type = new Type\Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]);
if (!TypeAnalyzer::isContainedBy($codebase, $input_type, $container_type)) {
if (IssueBuffer::accepts(
new \Psalm\Issue\InvalidThrow(
'Class supplied for @throws ' . $expected_exception
. ' does not implement Throwable',
$storage->throw_locations[$expected_exception],
$expected_exception
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
foreach ($statements_analyzer->getUncaughtThrows($context) as $possibly_thrown_exception => $codelocations) {
$is_expected = false;
foreach ($storage->throws as $expected_exception => $_) {
if ($expected_exception === $possibly_thrown_exception
|| $codebase->classExtends($possibly_thrown_exception, $expected_exception)
) {
$is_expected = true;
break;
}
}
if (!$is_expected) {
foreach ($codelocations as $codelocation) {
// issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc.
if (IssueBuffer::accepts(
new MissingThrowsDocblock(
$possibly_thrown_exception . ' is thrown but not caught - please either catch'
. ' or add a @throws annotation',
$codelocation
)
)) {
// fall through
}
}
}
}
if ($add_mutations) {
if ($this->return_vars_in_scope !== null) {
$context->vars_in_scope = TypeAnalyzer::combineKeyedTypes(
$context->vars_in_scope,
$this->return_vars_in_scope
);
}
if ($this->return_vars_possibly_in_scope !== null) {
$context->vars_possibly_in_scope = array_merge(
$context->vars_possibly_in_scope,
$this->return_vars_possibly_in_scope
);
}
foreach ($context->vars_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
unset($context->vars_in_scope[$var]);
}
}
foreach ($context->vars_possibly_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
unset($context->vars_possibly_in_scope[$var]);
}
}
if ($hash && $real_method_id && $this instanceof MethodAnalyzer) {
$new_hash = md5($real_method_id . '::' . $context->getScopeSummary());
if ($new_hash === $hash) {
self::$no_effects_hashes[$hash] = true;
}
}
}
return null;
}
private function checkParamReferences(
StatementsAnalyzer $statements_analyzer,
FunctionLikeStorage $storage,
?\Psalm\Storage\ClassLikeStorage $class_storage,
Context $context
) : void {
$codebase = $statements_analyzer->getCodebase();
$unused_params = [];
foreach ($statements_analyzer->getUnusedVarLocations() as list($var_name, $original_location)) {
if (!array_key_exists(substr($var_name, 1), $storage->param_types)) {
continue;
}
if (strpos($var_name, '$_') === 0 || (strpos($var_name, '$unused') === 0 && $var_name !== '$unused')) {
continue;
}
$position = array_search(substr($var_name, 1), array_keys($storage->param_types), true);
if ($position === false) {
throw new \UnexpectedValueException('$position should not be false here');
}
if ($storage->params[$position]->by_ref) {
continue;
}
if (!($storage instanceof MethodStorage)
|| $storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
) {
if ($this->function instanceof Closure) {
if (IssueBuffer::accepts(
new UnusedClosureParam(
'Param ' . $var_name . ' is never referenced in this method',
$original_location
),
$this->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new UnusedParam(
'Param ' . $var_name . ' is never referenced in this method',
$original_location
),
$this->getSuppressedIssues()
)) {
// fall through
}
}
} else {
$fq_class_name = (string)$context->self;
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
$method_name_lc = strtolower($storage->cased_name);
if ($storage->abstract || !isset($class_storage->overridden_method_ids[$method_name_lc])) {
continue;
}
$parent_method_id = end($class_storage->overridden_method_ids[$method_name_lc]);
if ($parent_method_id) {
$parent_method_storage = $codebase->methods->getStorage($parent_method_id);
// if the parent method has a param at that position and isn't abstract
if (!$parent_method_storage->abstract
&& isset($parent_method_storage->params[$position])
) {
continue;
}
}
$unused_params[$position] = $original_location;
}
}
if ($storage instanceof MethodStorage
&& $class_storage
&& $storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE
) {
$method_id_lc = strtolower($this->getMethodId());
foreach ($storage->params as $i => $_) {
if (!isset($unused_params[$i])) {
$codebase->file_reference_provider->addMethodParamUse(
$method_id_lc,
$i,
$method_id_lc
);
/** @var ClassMethod $this->function */
$method_name_lc = strtolower($storage->cased_name);
if (!isset($class_storage->overridden_method_ids[$method_name_lc])) {
continue;
}
foreach ($class_storage->overridden_method_ids[$method_name_lc] as $parent_method_id) {
$codebase->file_reference_provider->addMethodParamUse(
strtolower($parent_method_id),
$i,
$method_id_lc
);
}
}
}
}
}
/**
* @param array<int, \Psalm\Storage\FunctionLikeParameter> $params
* @param array<int, Type\Union> $implemented_docblock_param_types
*/
private function processParams(
StatementsAnalyzer $statements_analyzer,
FunctionLikeStorage $storage,
?string $cased_method_id,
array $params,
Context $context,
array $implemented_docblock_param_types,
bool $non_null_param_types,
bool $has_template_types
) : bool {
$check_stmts = true;
$codebase = $statements_analyzer->getCodebase();
$project_analyzer = $statements_analyzer->getProjectAnalyzer();
foreach ($params as $offset => $function_param) {
$signature_type = $function_param->signature_type;
$signature_type_location = $function_param->signature_type_location;
@ -660,7 +937,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
),
$storage->suppressed_issues
)) {
return false;
// do nothing
}
if ($signature_type->check(
@ -709,7 +986,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
}
}
if ($template_types) {
if ($has_template_types) {
$substituted_type = clone $param_type;
if ($substituted_type->check(
$this->source,
@ -776,347 +1053,128 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
);
}
if ($storage->pure) {
$context->pure = true;
}
return $check_stmts;
}
if ($storage->unused_docblock_params) {
foreach ($storage->unused_docblock_params as $param_name => $param_location) {
if (IssueBuffer::accepts(
new InvalidDocblockParamName(
'Incorrect param name $' . $param_name . ' in docblock for ' . $cased_method_id,
$param_location
)
)) {
}
}
}
/**
* @param \Psalm\Storage\FunctionLikeParameter[] $params
*/
private function alterParams(
Codebase $codebase,
FunctionLikeStorage $storage,
array $params,
Context $context
) : void {
foreach ($this->function->params as $param) {
$param_name_node = null;
if (ReturnTypeAnalyzer::checkReturnType(
$this->function,
$project_analyzer,
$this,
$storage,
$context
) === false) {
$check_stmts = false;
}
if (!$check_stmts) {
return false;
}
if ($context->collect_initializations || $context->collect_mutations) {
$statements_analyzer->addSuppressedIssues([
'DocblockTypeContradiction',
'InvalidReturnStatement',
'RedundantCondition',
'RedundantConditionGivenDocblockType',
'TypeDoesNotContainNull',
'TypeDoesNotContainType',
'LoopInvalidation',
]);
if ($context->collect_initializations) {
$statements_analyzer->addSuppressedIssues([
'UndefinedInterfaceMethod',
'UndefinedMethod',
'PossiblyUndefinedMethod',
]);
}
}
$statements_analyzer->analyze($function_stmts, $context, $global_context, true);
$this->examineParamTypes($statements_analyzer, $context, $codebase);
foreach ($storage->params as $offset => $function_param) {
// only complain if there's no type defined by a parent type
if (!$function_param->type
&& $function_param->location
&& !isset($implemented_docblock_param_types[$offset])
if ($param->type instanceof PhpParser\Node\Name) {
$param_name_node = $param->type;
} elseif ($param->type instanceof PhpParser\Node\NullableType
&& $param->type->type instanceof PhpParser\Node\Name
) {
if ($this->function instanceof Closure) {
IssueBuffer::accepts(
new MissingClosureParamType(
'Parameter $' . $function_param->name . ' has no provided type',
$function_param->location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
);
} else {
IssueBuffer::accepts(
new MissingParamType(
'Parameter $' . $function_param->name . ' has no provided type',
$function_param->location
),
$storage->suppressed_issues + $this->getSuppressedIssues()
);
$param_name_node = $param->type->type;
}
if ($param_name_node) {
$resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($param_name_node, $this->getAliases());
$parent_fqcln = $this->getParentFQCLN();
if ($resolved_name === 'self' && $context->self) {
$resolved_name = (string) $context->self;
} elseif ($resolved_name === 'parent' && $parent_fqcln) {
$resolved_name = $parent_fqcln;
}
$codebase->classlikes->handleClassLikeReferenceInMigration(
$codebase,
$this,
$param_name_node,
$resolved_name,
$context->calling_method_id
);
}
}
if ($storage->signature_return_type && $storage->signature_return_type_location) {
list($start, $end) = $storage->signature_return_type_location->getSelectionBounds();
if ($this->function->returnType) {
$return_name_node = null;
$codebase->analyzer->addOffsetReference(
$this->getFilePath(),
$start,
$end,
(string) $storage->signature_return_type
);
}
if ($this->function->returnType instanceof PhpParser\Node\Name) {
$return_name_node = $this->function->returnType;
} elseif ($this->function->returnType instanceof PhpParser\Node\NullableType
&& $this->function->returnType->type instanceof PhpParser\Node\Name
) {
$return_name_node = $this->function->returnType->type;
}
if ($this->function instanceof Closure) {
$this->verifyReturnType(
$statements_analyzer,
$storage->return_type,
$this->source->getFQCLN(),
$storage->return_type_location,
$global_context && $global_context->inside_call
);
if ($return_name_node) {
$resolved_name = ClassLikeAnalyzer::getFQCLNFromNameObject($return_name_node, $this->getAliases());
$closure_yield_types = [];
$parent_fqcln = $this->getParentFQCLN();
$ignore_nullable_issues = false;
$ignore_falsable_issues = false;
$closure_return_types = ReturnTypeCollector::getReturnTypes(
$this->function->stmts,
$closure_yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues,
true
);
if ($closure_return_types) {
$closure_return_type = new Type\Union($closure_return_types);
if (!$storage->return_type
|| $storage->return_type->hasMixed()
|| TypeAnalyzer::isContainedBy(
$codebase,
$closure_return_type,
$storage->return_type
)
) {
if ($this->function->inferredType) {
/** @var Type\Atomic\TFn */
$closure_atomic = $this->function->inferredType->getTypes()['Closure'];
$closure_atomic->return_type = $closure_return_type;
}
if ($resolved_name === 'self' && $context->self) {
$resolved_name = (string) $context->self;
} elseif ($resolved_name === 'parent' && $parent_fqcln) {
$resolved_name = $parent_fqcln;
}
$codebase->classlikes->handleClassLikeReferenceInMigration(
$codebase,
$this,
$return_name_node,
$resolved_name,
$context->calling_method_id
);
}
}
$unused_params = [];
if ($context->collect_references
&& !$context->collect_initializations
&& $codebase->find_unused_variables
&& $context->check_variables
if ($storage->return_type
&& $storage->return_type_location
&& $storage->return_type_location !== $storage->signature_return_type_location
) {
foreach ($statements_analyzer->getUnusedVarLocations() as list($var_name, $original_location)) {
if (!array_key_exists(substr($var_name, 1), $storage->param_types)) {
continue;
}
$replace_type = ExpressionAnalyzer::fleshOutType(
$codebase,
$storage->return_type,
$context->self,
$context->self,
$this->getParentFQCLN(),
false
);
if (strpos($var_name, '$_') === 0 || (strpos($var_name, '$unused') === 0 && $var_name !== '$unused')) {
continue;
}
$codebase->classlikes->handleDocblockTypeInMigration(
$codebase,
$this,
$replace_type,
$storage->return_type_location,
$context->calling_method_id
);
}
$position = array_search(substr($var_name, 1), array_keys($storage->param_types), true);
if ($position === false) {
throw new \UnexpectedValueException('$position should not be false here');
}
if ($storage->params[$position]->by_ref) {
continue;
}
if (!($storage instanceof MethodStorage)
|| $storage->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
) {
if ($this->function instanceof Closure) {
if (IssueBuffer::accepts(
new UnusedClosureParam(
'Param ' . $var_name . ' is never referenced in this method',
$original_location
),
$this->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new UnusedParam(
'Param ' . $var_name . ' is never referenced in this method',
$original_location
),
$this->getSuppressedIssues()
)) {
// fall through
}
}
} else {
$fq_class_name = (string)$context->self;
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
$method_name_lc = strtolower($storage->cased_name);
if ($storage->abstract || !isset($class_storage->overridden_method_ids[$method_name_lc])) {
continue;
}
$parent_method_id = end($class_storage->overridden_method_ids[$method_name_lc]);
if ($parent_method_id) {
$parent_method_storage = $codebase->methods->getStorage($parent_method_id);
// if the parent method has a param at that position and isn't abstract
if (!$parent_method_storage->abstract
&& isset($parent_method_storage->params[$position])
) {
continue;
}
}
$unused_params[$position] = $original_location;
}
}
if ($storage instanceof MethodStorage
&& $class_storage
&& $storage->visibility !== ClassLikeAnalyzer::VISIBILITY_PRIVATE
foreach ($params as $function_param) {
if ($function_param->type
&& $function_param->type_location
&& $function_param->type_location !== $function_param->signature_type_location
&& $function_param->type_location->file_path === $this->getFilePath()
) {
$method_id_lc = strtolower($this->getMethodId());
$replace_type = ExpressionAnalyzer::fleshOutType(
$codebase,
$function_param->type,
$context->self,
$context->self,
$this->getParentFQCLN(),
false
);
foreach ($storage->params as $i => $_) {
if (!isset($unused_params[$i])) {
$codebase->file_reference_provider->addMethodParamUse(
$method_id_lc,
$i,
$method_id_lc
);
/** @var ClassMethod $this->function */
$method_name_lc = strtolower($storage->cased_name);
if (!isset($class_storage->overridden_method_ids[$method_name_lc])) {
continue;
}
foreach ($class_storage->overridden_method_ids[$method_name_lc] as $parent_method_id) {
$codebase->file_reference_provider->addMethodParamUse(
strtolower($parent_method_id),
$i,
$method_id_lc
);
}
}
}
}
}
foreach ($storage->throws as $expected_exception => $_) {
if (isset($storage->throw_locations[$expected_exception])) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$expected_exception,
$storage->throw_locations[$expected_exception],
$statements_analyzer->getSuppressedIssues(),
false,
false,
true,
true
)) {
$input_type = new Type\Union([new TNamedObject($expected_exception)]);
$container_type = new Type\Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]);
if (!TypeAnalyzer::isContainedBy($codebase, $input_type, $container_type)) {
if (IssueBuffer::accepts(
new \Psalm\Issue\InvalidThrow(
'Class supplied for @throws ' . $expected_exception
. ' does not implement Throwable',
$storage->throw_locations[$expected_exception],
$expected_exception
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
foreach ($statements_analyzer->getUncaughtThrows($context) as $possibly_thrown_exception => $codelocations) {
$is_expected = false;
foreach ($storage->throws as $expected_exception => $_) {
if ($expected_exception === $possibly_thrown_exception
|| $codebase->classExtends($possibly_thrown_exception, $expected_exception)
) {
$is_expected = true;
break;
}
}
if (!$is_expected) {
foreach ($codelocations as $codelocation) {
// issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc.
if (IssueBuffer::accepts(
new MissingThrowsDocblock(
$possibly_thrown_exception . ' is thrown but not caught - please either catch'
. ' or add a @throws annotation',
$codelocation
)
)) {
// fall through
}
}
}
}
if ($add_mutations) {
if ($this->return_vars_in_scope !== null) {
$context->vars_in_scope = TypeAnalyzer::combineKeyedTypes(
$context->vars_in_scope,
$this->return_vars_in_scope
$codebase->classlikes->handleDocblockTypeInMigration(
$codebase,
$this,
$replace_type,
$function_param->type_location,
$context->calling_method_id
);
}
if ($this->return_vars_possibly_in_scope !== null) {
$context->vars_possibly_in_scope = array_merge(
$context->vars_possibly_in_scope,
$this->return_vars_possibly_in_scope
);
}
foreach ($context->vars_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
unset($context->vars_in_scope[$var]);
}
}
foreach ($context->vars_possibly_in_scope as $var => $_) {
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
unset($context->vars_possibly_in_scope[$var]);
}
}
if ($hash && $real_method_id && $this instanceof MethodAnalyzer) {
$new_hash = md5($real_method_id . '::' . $context->getScopeSummary());
if ($new_hash === $hash) {
self::$no_effects_hashes[$hash] = true;
}
}
}
return null;
}
/**