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

Improve dead code detection

This commit is contained in:
Matthew Brown 2018-06-16 20:01:33 -04:00
parent 3afd6053fd
commit 3670f066bb
21 changed files with 1549 additions and 872 deletions

View File

@ -104,17 +104,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$this->fq_class_name = $fq_class_name;
$this->storage = $this->file_checker->project_checker->classlike_storage_provider->get($fq_class_name);
if ($this->storage->location) {
$storage_file_path = $this->storage->location->file_path;
$source_file_path = $this->source->getFilePath();
if (!Config::getInstance()->use_case_sensitive_file_names) {
// TODO: Use these variables
$storage_file_path = strtolower($storage_file_path);
$source_file_path = strtolower($source_file_path);
}
}
}
/**

View File

@ -293,7 +293,9 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$context->vars_possibly_in_scope['$' . $function_param->name] = true;
if ($context->collect_references && $function_param->location) {
$context->unreferenced_vars['$' . $function_param->name] = $function_param->location;
$context->unreferenced_vars['$' . $function_param->name] = [
$function_param->location->getHash() => $function_param->location
];
}
if (!$function_param->type_location || !$function_param->location) {

View File

@ -8,6 +8,7 @@ class ScopeChecker
const ACTION_END = 'END';
const ACTION_BREAK = 'BREAK';
const ACTION_CONTINUE = 'CONTINUE';
const ACTION_LEAVE_SWITCH = 'LEAVE_SWITCH';
const ACTION_NONE = 'NONE';
const ACTION_RETURN = 'RETURN';
@ -51,14 +52,14 @@ class ScopeChecker
/**
* @param array<PhpParser\Node> $stmts
* @param bool $continue_is_break when checking inside a switch statement, continue is an alias of break
* @param bool $in_switch when checking inside a switch statement, continue is an alias of break
* @param bool $return_is_exit Exit and Throw statements are treated differently from return if this is false
*
* @return string[] one or more of 'LEAVE', 'CONTINUE', 'BREAK' (or empty if no single action is found)
*/
public static function getFinalControlActions(
array $stmts,
$continue_is_break = false,
$in_switch = false,
$return_is_exit = true
) {
if (empty($stmts)) {
@ -82,23 +83,29 @@ class ScopeChecker
}
if ($stmt instanceof PhpParser\Node\Stmt\Continue_) {
if ($continue_is_break
if ($in_switch
&& (!$stmt->num || !$stmt->num instanceof PhpParser\Node\Scalar\LNumber || $stmt->num->value < 2)
) {
return [self::ACTION_BREAK];
return [self::ACTION_LEAVE_SWITCH];
}
return [self::ACTION_CONTINUE];
}
if ($stmt instanceof PhpParser\Node\Stmt\Break_) {
if ($in_switch
&& (!$stmt->num || !$stmt->num instanceof PhpParser\Node\Scalar\LNumber || $stmt->num->value < 2)
) {
return [self::ACTION_LEAVE_SWITCH];
}
return [self::ACTION_BREAK];
}
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
$if_statement_actions = self::getFinalControlActions($stmt->stmts, $continue_is_break);
$if_statement_actions = self::getFinalControlActions($stmt->stmts, $in_switch);
$else_statement_actions = $stmt->else
? self::getFinalControlActions($stmt->else->stmts, $continue_is_break)
? self::getFinalControlActions($stmt->else->stmts, $in_switch)
: [];
$all_same = count($if_statement_actions) === 1
@ -109,7 +116,7 @@ class ScopeChecker
if ($stmt->elseifs) {
foreach ($stmt->elseifs as $elseif) {
$elseif_control_actions = self::getFinalControlActions($elseif->stmts, $continue_is_break);
$elseif_control_actions = self::getFinalControlActions($elseif->stmts, $in_switch);
$all_same = $all_same && $elseif_control_actions == $if_statement_actions;
@ -142,10 +149,12 @@ class ScopeChecker
$case_actions = self::getFinalControlActions($case->stmts, true);
if (array_intersect([self::ACTION_BREAK, self::ACTION_CONTINUE], $case_actions)) {
// clear out any default breaking notions
$has_non_breaking_default = false;
if (array_intersect([
self::ACTION_LEAVE_SWITCH,
self::ACTION_BREAK,
self::ACTION_CONTINUE
], $case_actions)
) {
continue 2;
}
@ -188,13 +197,13 @@ class ScopeChecker
}
if ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
$try_statement_actions = self::getFinalControlActions($stmt->stmts, $continue_is_break);
$try_statement_actions = self::getFinalControlActions($stmt->stmts, $in_switch);
if ($stmt->catches) {
$all_same = count($try_statement_actions) === 1;
foreach ($stmt->catches as $catch) {
$catch_actions = self::getFinalControlActions($catch->stmts, $continue_is_break);
$catch_actions = self::getFinalControlActions($catch->stmts, $in_switch);
$all_same = $all_same && $try_statement_actions == $catch_actions;
@ -212,7 +221,7 @@ class ScopeChecker
if ($stmt->finally->stmts) {
$finally_statement_actions = self::getFinalControlActions(
$stmt->finally->stmts,
$continue_is_break
$in_switch
);
if (!in_array(self::ACTION_NONE, $finally_statement_actions, true)) {

View File

@ -367,7 +367,7 @@ class ForeachChecker
$location = new CodeLocation($statements_checker, $stmt->keyVar);
if ($context->collect_references && !isset($foreach_context->byref_constraints[$key_var_id])) {
$foreach_context->unreferenced_vars[$key_var_id] = $location;
$foreach_context->unreferenced_vars[$key_var_id] = [$location->getHash() => $location];
}
if (!$statements_checker->hasVariable($key_var_id)) {
@ -378,8 +378,8 @@ class ForeachChecker
);
}
if ($stmt->byRef) {
$statements_checker->registerVariableUse($location);
if ($stmt->byRef && $context->collect_references) {
$statements_checker->registerVariableUses([$location->getHash() => $location]);
}
}

View File

@ -329,16 +329,28 @@ class IfChecker
// which vars of the if we can safely change
$pre_assignment_else_redefined_vars = $temp_else_context->getRedefinedVars($context->vars_in_scope, true);
$old_unreferenced_vars = $context->unreferenced_vars;
$newly_unreferenced_locations = [];
// this captures statements in the if conditional
if ($context->collect_references) {
foreach ($if_context->unreferenced_vars as $var_id => $location) {
if (!isset($old_unreferenced_vars[$var_id])
|| $old_unreferenced_vars[$var_id] !== $location
) {
$newly_unreferenced_locations[$var_id][] = $location;
foreach ($if_context->unreferenced_vars as $var_id => $locations) {
if (!isset($context->unreferenced_vars[$var_id])) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
} else {
$new_locations = array_diff_key(
$locations,
$context->unreferenced_vars[$var_id]
);
if ($new_locations) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
}
}
}
}
@ -357,16 +369,6 @@ class IfChecker
return false;
}
if ($context->collect_references && $if_scope->final_actions !== [ScopeChecker::ACTION_END]) {
foreach ($if_context->unreferenced_vars as $var_id => $location) {
if (!isset($old_unreferenced_vars[$var_id])
|| $old_unreferenced_vars[$var_id] !== $location
) {
$newly_unreferenced_locations[$var_id][] = $location;
}
}
}
// check the elseifs
foreach ($stmt->elseifs as $elseif) {
$elseif_context = clone $original_context;
@ -386,16 +388,6 @@ class IfChecker
) === false) {
return false;
}
if ($context->collect_references && $if_scope->final_actions !== [ScopeChecker::ACTION_END]) {
foreach ($elseif_context->unreferenced_vars as $var_id => $location) {
if (!isset($old_unreferenced_vars[$var_id])
|| $old_unreferenced_vars[$var_id] !== $location
) {
$newly_unreferenced_locations[$var_id][] = $location;
}
}
}
}
// check the else
@ -419,16 +411,6 @@ class IfChecker
return false;
}
if ($context->collect_references && $if_scope->final_actions !== [ScopeChecker::ACTION_END]) {
foreach ($else_context->unreferenced_vars as $var_id => $location) {
if (!isset($old_unreferenced_vars[$var_id])
|| $old_unreferenced_vars[$var_id] !== $location
) {
$newly_unreferenced_locations[$var_id][] = $location;
}
}
}
if ($loop_scope) {
$loop_scope->final_actions = array_unique(
array_merge(
@ -495,7 +477,7 @@ class IfChecker
if ($if_scope->possibly_redefined_vars) {
foreach ($if_scope->possibly_redefined_vars as $var_id => $type) {
if ($context->hasVariable($var_id)
if (isset($context->vars_in_scope[$var_id])
&& !$type->failed_reconciliation
&& !isset($if_scope->updated_vars[$var_id])
) {
@ -515,18 +497,24 @@ class IfChecker
}
if ($context->collect_references) {
foreach ($newly_unreferenced_locations as $var_id => $locations) {
foreach ($if_scope->new_unreferenced_vars as $var_id => $locations) {
if (($stmt->else
&& (isset($if_scope->assigned_var_ids[$var_id]) || isset($if_scope->new_vars[$var_id])))
|| !isset($context->vars_in_scope[$var_id])
) {
$context->unreferenced_vars[$var_id] = array_shift($locations);
}
foreach ($locations as $location) {
$statements_checker->registerVariableUse($location);
$context->unreferenced_vars[$var_id] = $locations;
} elseif (isset($if_scope->possibly_assigned_var_ids[$var_id])) {
if (!isset($context->unreferenced_vars[$var_id])) {
$context->unreferenced_vars[$var_id] = $locations;
} else {
$context->unreferenced_vars[$var_id] += $locations;
}
} else {
$statements_checker->registerVariableUses($locations);
}
}
$context->possibly_assigned_var_ids += $if_scope->possibly_assigned_var_ids;
}
return null;
@ -553,7 +541,7 @@ class IfChecker
array $pre_assignment_else_redefined_vars,
LoopScope $loop_scope = null
) {
$final_actions = ScopeChecker::getFinalControlActions($stmt->stmts);
$final_actions = ScopeChecker::getFinalControlActions($stmt->stmts, $outer_context->inside_case);
$has_ending_statements = $final_actions === [ScopeChecker::ACTION_END];
@ -562,6 +550,7 @@ class IfChecker
$has_break_statement = $final_actions === [ScopeChecker::ACTION_BREAK];
$has_continue_statement = $final_actions === [ScopeChecker::ACTION_CONTINUE];
$has_leave_switch_statement = $final_actions === [ScopeChecker::ACTION_LEAVE_SWITCH];
if (!$has_ending_statements) {
$if_context->parent_context = $outer_context;
@ -768,15 +757,9 @@ class IfChecker
$outer_context->vars_possibly_in_scope
);
$possibly_assigned_var_ids = array_diff_key(
$if_context->possibly_assigned_var_ids,
$outer_context->possibly_assigned_var_ids
);
if ($loop_scope) {
if (!$has_continue_statement && !$has_break_statement) {
$if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope;
$if_scope->possibly_assigned_var_ids = $possibly_assigned_var_ids;
}
$loop_scope->vars_possibly_in_scope = array_merge(
@ -786,6 +769,31 @@ class IfChecker
} elseif (!$has_leaving_statements) {
$if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope;
}
if ($if_context->collect_references && (!$has_leaving_statements || $has_leave_switch_statement)) {
foreach ($if_context->unreferenced_vars as $var_id => $locations) {
if (!isset($outer_context->unreferenced_vars[$var_id])) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
} else {
$new_locations = array_diff_key(
$locations,
$outer_context->unreferenced_vars[$var_id]
);
if ($new_locations) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
}
}
}
}
}
}
@ -867,9 +875,15 @@ class IfChecker
$conditional_assigned_var_ids = $elseif_context->assigned_var_ids;
$elseif_context->assigned_var_ids = array_merge($pre_assigned_var_ids, $conditional_assigned_var_ids);
$elseif_context->assigned_var_ids = array_merge(
$pre_assigned_var_ids,
$conditional_assigned_var_ids
);
$new_assigned_var_ids = array_diff_key($conditional_assigned_var_ids, $pre_assigned_var_ids);
$new_assigned_var_ids = array_diff_key(
$conditional_assigned_var_ids,
$pre_assigned_var_ids
);
$new_referenced_var_ids = array_diff_key($new_referenced_var_ids, $new_assigned_var_ids);
@ -993,6 +1007,8 @@ class IfChecker
$pre_stmts_assigned_var_ids = $elseif_context->assigned_var_ids;
$elseif_context->assigned_var_ids = [];
$pre_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids;
$elseif_context->possibly_assigned_var_ids = [];
if ($statements_checker->analyze(
$elseif->stmts,
@ -1005,7 +1021,12 @@ class IfChecker
/** @var array<string, bool> */
$new_stmts_assigned_var_ids = $elseif_context->assigned_var_ids;
$elseif_context->assigned_var_ids = $pre_stmts_assigned_var_ids;
$elseif_context->assigned_var_ids = $pre_stmts_assigned_var_ids + $new_stmts_assigned_var_ids;
/** @var array<string, bool> */
$new_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids;
$elseif_context->possibly_assigned_var_ids =
$pre_stmts_possibly_assigned_var_ids + $new_stmts_possibly_assigned_var_ids;
if ($elseif_context->byref_constraints !== null) {
foreach ($elseif_context->byref_constraints as $var_id => $byref_constraint) {
@ -1034,7 +1055,7 @@ class IfChecker
}
}
$final_actions = ScopeChecker::getFinalControlActions($elseif->stmts);
$final_actions = ScopeChecker::getFinalControlActions($elseif->stmts, $outer_context->inside_case);
// has a return/throw at end
$has_ending_statements = $final_actions === [ScopeChecker::ACTION_END];
$has_leaving_statements = $has_ending_statements
@ -1042,6 +1063,7 @@ class IfChecker
$has_break_statement = $final_actions === [ScopeChecker::ACTION_BREAK];
$has_continue_statement = $final_actions === [ScopeChecker::ACTION_CONTINUE];
$has_leave_switch_statement = $final_actions === [ScopeChecker::ACTION_LEAVE_SWITCH];
$if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions);
@ -1197,10 +1219,7 @@ class IfChecker
$outer_context->vars_possibly_in_scope
);
$possibly_assigned_var_ids = array_diff_key(
$elseif_context->possibly_assigned_var_ids,
$outer_context->possibly_assigned_var_ids
);
$possibly_assigned_var_ids = $new_stmts_possibly_assigned_var_ids;
if ($has_leaving_statements && $loop_scope) {
if (!$has_continue_statement && !$has_break_statement) {
@ -1228,6 +1247,31 @@ class IfChecker
$if_scope->possibly_assigned_var_ids
);
}
if ($outer_context->collect_references && (!$has_leaving_statements || $has_leave_switch_statement)) {
foreach ($elseif_context->unreferenced_vars as $var_id => $locations) {
if (!isset($outer_context->unreferenced_vars[$var_id])) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
} else {
$new_locations = array_diff_key(
$locations,
$outer_context->unreferenced_vars[$var_id]
);
if ($new_locations) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
}
}
}
}
}
if ($outer_context->collect_references) {
@ -1318,6 +1362,9 @@ class IfChecker
$pre_stmts_assigned_var_ids = $else_context->assigned_var_ids;
$else_context->assigned_var_ids = [];
$pre_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids;
$else_context->possibly_assigned_var_ids = [];
if ($else) {
if ($statements_checker->analyze(
$else->stmts,
@ -1333,6 +1380,10 @@ class IfChecker
$new_assigned_var_ids = $else_context->assigned_var_ids;
$else_context->assigned_var_ids = $pre_stmts_assigned_var_ids;
/** @var array<string, bool> */
$new_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids;
$else_context->possibly_assigned_var_ids = $pre_possibly_assigned_var_ids + $new_possibly_assigned_var_ids;
if ($else && $else_context->byref_constraints !== null) {
$project_checker = $statements_checker->getFileChecker()->project_checker;
@ -1369,7 +1420,9 @@ class IfChecker
);
}
$final_actions = $else ? ScopeChecker::getFinalControlActions($else->stmts) : [ScopeChecker::ACTION_NONE];
$final_actions = $else
? ScopeChecker::getFinalControlActions($else->stmts, $outer_context->inside_case)
: [ScopeChecker::ACTION_NONE];
// has a return/throw at end
$has_ending_statements = $final_actions === [ScopeChecker::ACTION_END];
$has_leaving_statements = $has_ending_statements
@ -1377,6 +1430,7 @@ class IfChecker
$has_break_statement = $final_actions === [ScopeChecker::ACTION_BREAK];
$has_continue_statement = $final_actions === [ScopeChecker::ACTION_CONTINUE];
$has_leave_switch_statement = $final_actions === [ScopeChecker::ACTION_LEAVE_SWITCH];
$if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions);
@ -1454,10 +1508,7 @@ class IfChecker
$outer_context->vars_possibly_in_scope
);
$possibly_assigned_var_ids = array_diff_key(
$else_context->possibly_assigned_var_ids,
$outer_context->possibly_assigned_var_ids
);
$possibly_assigned_var_ids = $new_possibly_assigned_var_ids;
if ($has_leaving_statements && $loop_scope) {
if (!$has_continue_statement && !$has_break_statement) {
@ -1487,6 +1538,31 @@ class IfChecker
$if_scope->possibly_assigned_var_ids
);
}
if ($outer_context->collect_references && (!$has_leaving_statements || $has_leave_switch_statement)) {
foreach ($else_context->unreferenced_vars as $var_id => $locations) {
if (!isset($outer_context->unreferenced_vars[$var_id])) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
} else {
$new_locations = array_diff_key(
$locations,
$outer_context->unreferenced_vars[$var_id]
);
if ($new_locations) {
if (isset($if_scope->new_unreferenced_vars[$var_id])) {
$if_scope->new_unreferenced_vars[$var_id] += $locations;
} else {
$if_scope->new_unreferenced_vars[$var_id] = $locations;
}
}
}
}
}
}
if ($project_checker->infer_types_from_usage) {

View File

@ -87,6 +87,8 @@ class LoopChecker
$inner_context = clone $loop_scope->loop_context;
$inner_context->parent_context = $loop_scope->loop_context;
$old_referenced_var_ids = $inner_context->referenced_var_ids;
$inner_context->referenced_var_ids = [];
if (!$is_do) {
foreach ($pre_conditions as $pre_condition) {
@ -116,6 +118,9 @@ class LoopChecker
}
}
$new_referenced_var_ids = $inner_context->referenced_var_ids;
$inner_context->referenced_var_ids = $old_referenced_var_ids + $inner_context->referenced_var_ids;
$loop_scope->loop_parent_context->vars_possibly_in_scope = array_merge(
$inner_context->vars_possibly_in_scope,
$loop_scope->loop_parent_context->vars_possibly_in_scope
@ -150,6 +155,9 @@ class LoopChecker
$inner_context = clone $loop_scope->loop_context;
$inner_context->parent_context = $loop_scope->loop_context;
$old_referenced_var_ids = $inner_context->referenced_var_ids;
$inner_context->referenced_var_ids = [];
$asserted_var_ids = array_unique($asserted_var_ids);
$inner_context->protected_var_ids = $loop_scope->protected_var_ids;
@ -166,6 +174,12 @@ class LoopChecker
}
}
/**
* @var array<string, bool>
*/
$new_referenced_var_ids = $inner_context->referenced_var_ids;
$inner_context->referenced_var_ids = $old_referenced_var_ids + $inner_context->referenced_var_ids;
$recorded_issues = IssueBuffer::clearRecordingLevel();
IssueBuffer::stopRecording();
@ -231,16 +245,31 @@ class LoopChecker
}
if ($inner_context->collect_references) {
foreach ($inner_context->unreferenced_vars as $var_id => $location) {
if (isset($loop_scope->loop_parent_context->vars_in_scope[$var_id])
&& (!isset($loop_scope->loop_parent_context->unreferenced_vars[$var_id])
|| $loop_scope->loop_parent_context->unreferenced_vars[$var_id] !== $location)
) {
$statements_checker->registerVariableUse($location);
foreach ($loop_scope->possibly_unreferenced_vars as $var_id => $locations) {
if (isset($inner_context->unreferenced_vars[$var_id])) {
$inner_context->unreferenced_vars[$var_id] += $locations;
} else {
$inner_context->unreferenced_vars[$var_id] = $locations;
}
}
}
/*if ($inner_context->collect_references) {
foreach ($inner_context->unreferenced_vars as $var_id => $locations) {
if (isset($loop_scope->loop_parent_context->vars_in_scope[$var_id])
&& !isset($new_referenced_var_ids[$var_id])
) {
if (!isset($loop_scope->loop_parent_context->unreferenced_vars[$var_id])) {
$loop_scope->loop_parent_context->unreferenced_vars[$var_id] = $locations;
} else {
$loop_scope->loop_parent_context->unreferenced_vars[$var_id] += $locations;
}
} else {
$statements_checker->registerVariableUses($locations);
}
}
}*/
// remove vars that were defined in the foreach
foreach ($vars_to_remove as $var_id) {
unset($inner_context->vars_in_scope[$var_id]);
@ -395,11 +424,31 @@ class LoopChecker
);
if ($inner_context->collect_references) {
foreach ($inner_context->unreferenced_vars as $var_id => $location) {
foreach ($loop_scope->possibly_unreferenced_vars as $var_id => $locations) {
if (isset($inner_context->unreferenced_vars[$var_id])) {
$inner_context->unreferenced_vars[$var_id] += $locations;
} else {
$inner_context->unreferenced_vars[$var_id] = $locations;
}
}
foreach ($inner_context->unreferenced_vars as $var_id => $locations) {
if (!isset($new_referenced_var_ids[$var_id]) || $has_break_statement) {
if (!isset($loop_scope->loop_context->unreferenced_vars[$var_id])) {
$loop_scope->loop_context->unreferenced_vars[$var_id] = $locations;
} else {
$loop_scope->loop_context->unreferenced_vars[$var_id] += $locations;
}
} else {
$statements_checker->registerVariableUses($locations);
}
}
foreach ($loop_scope->unreferenced_vars as $var_id => $locations) {
if (!isset($loop_scope->loop_context->unreferenced_vars[$var_id])) {
$loop_scope->loop_context->unreferenced_vars[$var_id] = $location;
} elseif ($loop_scope->loop_context->unreferenced_vars[$var_id] !== $location) {
$statements_checker->registerVariableUse($location);
$loop_scope->loop_context->unreferenced_vars[$var_id] = $locations;
} else {
$loop_scope->loop_context->unreferenced_vars[$var_id] += $locations;
}
}
}

View File

@ -70,7 +70,7 @@ class SwitchChecker
$last_case_exit_type = 'return_throw';
} elseif ($case_actions === [ScopeChecker::ACTION_CONTINUE]) {
$last_case_exit_type = 'continue';
} elseif (in_array(ScopeChecker::ACTION_BREAK, $case_actions, true)) {
} elseif (in_array(ScopeChecker::ACTION_LEAVE_SWITCH, $case_actions, true)) {
$last_case_exit_type = 'break';
}
}
@ -84,6 +84,10 @@ class SwitchChecker
$project_checker = $statements_checker->getFileChecker()->project_checker;
$new_unreferenced_vars = [];
$new_assigned_var_ids = null;
$new_possibly_assigned_var_ids = [];
for ($i = 0, $l = count($stmt->cases); $i < $l; $i++) {
$case = $stmt->cases[$i];
@ -322,8 +326,23 @@ class SwitchChecker
);
}
$pre_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids;
$case_context->possibly_assigned_var_ids = [];
$pre_assigned_var_ids = $case_context->assigned_var_ids;
$case_context->assigned_var_ids = [];
$statements_checker->analyze($case_stmts, $case_context, $loop_scope);
/** @var array<string, bool> */
$new_case_assigned_var_ids = $case_context->assigned_var_ids;
$case_context->assigned_var_ids = $pre_assigned_var_ids + $new_case_assigned_var_ids;
/** @var array<string, bool> */
$new_case_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids;
$case_context->possibly_assigned_var_ids =
$pre_possibly_assigned_var_ids + $new_case_possibly_assigned_var_ids;
$context->referenced_var_ids = array_merge(
$context->referenced_var_ids,
$case_context->referenced_var_ids
@ -427,21 +446,40 @@ class SwitchChecker
);
}
}
}
if ($context->collect_references) {
foreach ($case_context->unreferenced_vars as $var_id => $location) {
if (isset($context->unreferenced_vars[$var_id])
&& $context->unreferenced_vars[$var_id] !== $location
) {
$context->hasVariable($var_id, $statements_checker);
if ($context->collect_references) {
$new_possibly_assigned_var_ids =
$new_possibly_assigned_var_ids + $new_case_possibly_assigned_var_ids;
if ($new_assigned_var_ids === null) {
$new_assigned_var_ids = $new_case_assigned_var_ids;
} else {
$new_assigned_var_ids = array_intersect_key($new_assigned_var_ids, $new_case_assigned_var_ids);
}
foreach ($case_context->unreferenced_vars as $var_id => $locations) {
if (!isset($original_context->unreferenced_vars[$var_id])) {
if (isset($new_unreferenced_vars[$var_id])) {
$new_unreferenced_vars[$var_id] += $locations;
} else {
$new_unreferenced_vars[$var_id] = $locations;
}
} else {
$new_locations = array_diff_key(
$locations,
$original_context->unreferenced_vars[$var_id]
);
if ($new_locations) {
if (isset($new_unreferenced_vars[$var_id])) {
$new_unreferenced_vars[$var_id] += $locations;
} else {
$new_unreferenced_vars[$var_id] = $locations;
}
}
}
}
}
$context->unreferenced_vars = array_merge(
$context->unreferenced_vars,
$case_context->unreferenced_vars
);
}
}
@ -501,6 +539,24 @@ class SwitchChecker
}
}
if ($context->collect_references) {
foreach ($new_unreferenced_vars as $var_id => $locations) {
if (($all_options_matched && isset($new_assigned_var_ids[$var_id]))
|| !isset($context->vars_in_scope[$var_id])
) {
$context->unreferenced_vars[$var_id] = $locations;
} elseif (isset($new_possibly_assigned_var_ids[$var_id])) {
if (!isset($context->unreferenced_vars[$var_id])) {
$context->unreferenced_vars[$var_id] = $locations;
} else {
$context->unreferenced_vars[$var_id] += $locations;
}
} else {
$statements_checker->registerVariableUses($locations);
}
}
}
$context->vars_possibly_in_scope = array_merge($context->vars_possibly_in_scope, $new_vars_possibly_in_scope);
return null;

View File

@ -99,11 +99,11 @@ class TryChecker
)
);
foreach ($context->unreferenced_vars as $var_id => $location) {
foreach ($context->unreferenced_vars as $var_id => $locations) {
if (isset($old_unreferenced_vars[$var_id])
&& $old_unreferenced_vars[$var_id] !== $location
&& $old_unreferenced_vars[$var_id] !== $locations
) {
$reassigned_vars[$var_id] = $location;
$reassigned_vars[$var_id] = $locations;
}
}
}
@ -221,7 +221,7 @@ class TryChecker
$location,
$try_context->branch_point
);
$catch_context->unreferenced_vars[$catch_var_id] = $location;
$catch_context->unreferenced_vars[$catch_var_id] = [$location->getHash() => $location];
}
// this registers the variable to avoid unfair deadcode issues
@ -259,16 +259,16 @@ class TryChecker
)
);
foreach ($catch_context->unreferenced_vars as $var_id => $location) {
foreach ($catch_context->unreferenced_vars as $var_id => $locations) {
if (!isset($old_unreferenced_vars[$var_id])
&& (isset($context->unreferenced_vars[$var_id])
|| isset($newly_assigned_var_ids[$var_id]))
) {
$statements_checker->registerVariableUse($location);
$statements_checker->registerVariableUses($locations);
} elseif (isset($old_unreferenced_vars[$var_id])
&& $old_unreferenced_vars[$var_id] !== $location
&& $old_unreferenced_vars[$var_id] !== $locations
) {
$statements_checker->registerVariableUse($location);
$statements_checker->registerVariableUses($locations);
}
}
}
@ -304,9 +304,11 @@ class TryChecker
}
if ($context->collect_references) {
foreach ($old_unreferenced_vars as $var_id => $location) {
if (isset($context->unreferenced_vars[$var_id]) && $context->unreferenced_vars[$var_id] !== $location) {
$statements_checker->registerVariableUse($location);
foreach ($old_unreferenced_vars as $var_id => $locations) {
if (isset($context->unreferenced_vars[$var_id])
&& $context->unreferenced_vars[$var_id] !== $locations
) {
$statements_checker->registerVariableUses($locations);
}
}
}

View File

@ -244,7 +244,7 @@ class AssignmentChecker
$location = new CodeLocation($statements_checker, $assign_var);
if ($context->collect_references) {
$context->unreferenced_vars[$var_id] = $location;
$context->unreferenced_vars[$var_id] = [$location->getHash() => $location];
}
if (!$statements_checker->hasVariable($var_id)) {
@ -261,7 +261,7 @@ class AssignmentChecker
}
if (isset($context->byref_constraints[$var_id])) {
$statements_checker->registerVariableUse($location);
$statements_checker->registerVariableUses([$location->getHash() => $location]);
}
} elseif ($assign_var instanceof PhpParser\Node\Expr\List_
|| $assign_var instanceof PhpParser\Node\Expr\Array_
@ -338,12 +338,16 @@ class AssignmentChecker
if ($list_var_id) {
$context->vars_possibly_in_scope[$list_var_id] = true;
$context->assigned_var_ids[$list_var_id] = true;
$context->possibly_assigned_var_ids[$list_var_id] = true;
$already_in_scope = isset($context->vars_in_scope[$var_id]);
if (strpos($list_var_id, '-') === false && strpos($list_var_id, '[') === false) {
$location = new CodeLocation($statements_checker, $assign_var);
$location = new CodeLocation($statements_checker, $var);
if ($context->collect_references) {
$context->unreferenced_vars[$list_var_id] = $location;
$context->unreferenced_vars[$list_var_id] = [$location->getHash() => $location];
}
if (!$statements_checker->hasVariable($list_var_id)) {
@ -360,7 +364,7 @@ class AssignmentChecker
}
if (isset($context->byref_constraints[$list_var_id])) {
$statements_checker->registerVariableUse($location);
$statements_checker->registerVariableUses([$location->getHash() => $location]);
}
}
@ -383,7 +387,7 @@ class AssignmentChecker
}
}
if ($context->hasVariable($list_var_id)) {
if ($already_in_scope) {
// removes dependennt vars from $context
$context->removeDescendents(
$list_var_id,

View File

@ -526,7 +526,7 @@ class CallChecker
null
);
$statements_checker->registerVariableUse($location);
$statements_checker->registerVariableUses([$location->getHash() => $location]);
}
} else {
$context->removeVarFromConflictingClauses(
@ -811,6 +811,7 @@ class CallChecker
} else {
if ($existing_generic_params_to_strings) {
$empty_generic_params = [];
$param_type->replaceTemplateTypesWithStandins(
$existing_generic_params_to_strings,
$empty_generic_params,

View File

@ -255,7 +255,7 @@ class VariableFetchChecker
}
}
$statements_checker->registerVariableUse($first_appearance);
$statements_checker->registerVariableUses([$first_appearance->getHash() => $first_appearance]);
}
} else {
$stmt->inferredType = clone $context->vars_in_scope[$var_name];

View File

@ -572,7 +572,7 @@ class ExpressionChecker
$statements_checker->registerVariable($var_id, $location, null);
if ($context->collect_references) {
$context->unreferenced_vars[$var_id] = $location;
$context->unreferenced_vars[$var_id] = [$location->getHash() => $location];
}
$context->hasVariable($var_id, $statements_checker);

View File

@ -72,6 +72,11 @@ class StatementsChecker extends SourceChecker implements StatementsSource
*/
private $unused_var_locations = [];
/**
* @var array<string, bool>
*/
private $used_var_locations = [];
/**
* @param StatementsSource $source
*/
@ -310,6 +315,16 @@ class StatementsChecker extends SourceChecker implements StatementsSource
}
}
}
if ($context->collect_references && (!$context->inside_case || $stmt->num)) {
foreach ($context->unreferenced_vars as $var_id => $locations) {
if (isset($loop_scope->unreferenced_vars[$var_id])) {
$loop_scope->unreferenced_vars[$var_id] += $locations;
} else {
$loop_scope->unreferenced_vars[$var_id] = $locations;
}
}
}
}
$has_returned = true;
@ -358,6 +373,16 @@ class StatementsChecker extends SourceChecker implements StatementsSource
$loop_scope->possibly_redefined_loop_vars[$var] = $type;
}
}
if ($context->collect_references && (!$context->inside_case || $stmt->num)) {
foreach ($context->unreferenced_vars as $var_id => $locations) {
if (isset($loop_scope->possibly_unreferenced_vars[$var_id])) {
$loop_scope->possibly_unreferenced_vars[$var_id] += $locations;
} else {
$loop_scope->possibly_unreferenced_vars[$var_id] = $locations;
}
}
}
}
$has_returned = true;
@ -653,8 +678,8 @@ class StatementsChecker extends SourceChecker implements StatementsSource
$source = $this->getSource();
$function_storage = $source instanceof FunctionLikeChecker ? $source->getFunctionLikeStorage($this) : null;
foreach ($this->unused_var_locations as list($var_id, $original_location)) {
if ($var_id === '$_') {
foreach ($this->unused_var_locations as $hash => list($var_id, $original_location)) {
if ($var_id === '$_' || isset($this->used_var_locations[$hash])) {
continue;
}
@ -701,7 +726,7 @@ class StatementsChecker extends SourceChecker implements StatementsSource
$location = new CodeLocation($this, $stmt);
if ($context->collect_references) {
$context->unreferenced_vars[$var_id] = $location;
$context->unreferenced_vars[$var_id] = [$location->getHash() => $location];
}
$this->registerVariable(
@ -1174,15 +1199,19 @@ class StatementsChecker extends SourceChecker implements StatementsSource
*/
public function registerVariableAssignment($var_id, CodeLocation $location)
{
$this->unused_var_locations[spl_object_hash($location)] = [$var_id, $location];
$this->unused_var_locations[$location->getHash()] = [$var_id, $location];
}
/**
* @param array<string, CodeLocation> $locations
* @return void
*/
public function registerVariableUse(CodeLocation $location)
public function registerVariableUses(array $locations)
{
unset($this->unused_var_locations[spl_object_hash($location)]);
foreach ($locations as $hash => $_) {
unset($this->unused_var_locations[$hash]);
$this->used_var_locations[$hash] = true;
}
}
/**

View File

@ -353,4 +353,12 @@ class CodeLocation
return [$this->preview_start, $this->preview_end];
}
/**
* @return string
*/
public function getHash()
{
return (string) $this->file_start;
}
}

View File

@ -80,13 +80,6 @@ class Config
*/
public $cache_directory;
/**
* Whether or not to care about casing of file names
*
* @var bool
*/
public $use_case_sensitive_file_names = false;
/**
* Path to the autoader
*

View File

@ -157,7 +157,7 @@ class Context
/**
* A list of variables that have never been referenced
*
* @var array<string, CodeLocation>
* @var array<string, array<string, CodeLocation>>
*/
public $unreferenced_vars = [];
@ -673,7 +673,7 @@ class Context
if ($this->collect_references && $statements_checker) {
if (isset($this->unreferenced_vars[$var_name])) {
$statements_checker->registerVariableUse($this->unreferenced_vars[$var_name]);
$statements_checker->registerVariableUses($this->unreferenced_vars[$var_name]);
}
unset($this->unreferenced_vars[$var_name]);

View File

@ -1,6 +1,7 @@
<?php
namespace Psalm\Scope;
use Psalm\CodeLocation;
use Psalm\Clause;
use Psalm\Type;
@ -81,4 +82,9 @@ class IfScope
* @var string[]
*/
public $final_actions = [];
/**
* @var array<string, array<string, CodeLocation>>
*/
public $new_unreferenced_vars = [];
}

View File

@ -1,6 +1,7 @@
<?php
namespace Psalm\Scope;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Type;
@ -41,6 +42,16 @@ class LoopScope
*/
public $protected_var_ids = [];
/**
* @var array<string, array<string, CodeLocation>>
*/
public $unreferenced_vars = [];
/**
* @var array<string, array<string, CodeLocation>>
*/
public $possibly_unreferenced_vars = [];
/**
* @var string[]
*/

View File

@ -294,8 +294,6 @@ class ParseTree
throw new TypeParseTreeException('Unexpected token ' . $type_token);
}
$added_null = false;
$current_parent = $current_leaf->parent;
if ($current_parent instanceof ParseTree\CallableWithReturnTypeTree) {

View File

@ -113,91 +113,6 @@ class UnusedCodeTest extends TestCase
public function providerFileCheckerValidCodeParse()
{
return [
'arrayOffset' => [
'<?php
/** @return void */
function foo() {
$a = 0;
$arr = ["hello"];
echo $arr[$a];
}',
],
'unset' => [
'<?php
/** @return void */
function foo() {
$a = 0;
$arr = ["hello"];
unset($arr[$a]);
}',
],
'usedVariables' => [
'<?php
/** @return string */
function foo() {
$a = 5;
$b = [];
$c[] = "hello";
$d = "Foo";
$e = "arg";
$f = new $d($e);
return $a . implode(",", $b) . $c[0] . get_class($f);
}',
'error_levels' => [
'PossiblyUndefinedVariable',
'MixedArrayAccess',
'MixedOperand',
'MixedAssignment',
],
],
'ifInFunctionWithReference' => [
'<?php
/** @return string */
function foo() {
$a = 5;
if (rand(0, 1)) {
$b = "hello";
} else {
$b = "goodbye";
}
return $a . $b;
}',
],
'byrefInForeachLoop' => [
'<?php
function foo(): void {
$a = [1, 2, 3];
foreach ($a as &$b) {
$b = $b + 1;
}
}',
],
'definedInSecondBranchOfCondition' => [
'<?php
function foo(): void {
if (rand(0, 1) && $a = rand(0, 1)) {
echo $a;
}
}',
],
'booleanOr' => [
'<?php
function foo(int $a, int $b): bool {
return $a || $b;
}',
],
'paramUsedInIf' => [
'<?php
function foo(string $a): void {
if (rand(0, 1)) {
echo $a;
}
}',
],
'magicCall' => [
'<?php
class A {
@ -262,313 +177,6 @@ class UnusedCodeTest extends TestCase
(new A)->foo();',
],
'dummyByRefVar' => [
'<?php
function foo(string &$a = null, string $b = null): void {
if ($a) {
echo $a;
}
if ($b) {
echo $b;
}
}
function bar(): void {
foo($dummy_byref_var, "hello");
}
bar();',
],
'foreachReassigned' => [
'<?php
function foo(): void {
$a = false;
foreach ([1, 2, 3] as $b) {
$a = true;
echo $b;
}
echo $a;
}',
],
'doWhileReassigned' => [
'<?php
function foo(): void {
$a = 5;
do {
echo $a;
$a = $a - rand(-3, 3);
} while ($a > 3);
}',
],
'whileTypeChangedInIfAndContinueWithReference' => [
'<?php
function foo(): void {
$a = false;
while (rand(0, 1)) {
if (rand(0, 1)) {
$a = true;
continue;
}
$a = false;
}
echo $a;
}',
],
'whileReassignedInIfAndContinueWithReferenceAfter' => [
'<?php
function foo(): void {
$a = 5;
while (rand(0, 1)) {
if (rand(0, 1)) {
$a = 7;
continue;
}
$a = 3;
}
echo $a;
}',
],
'whileReassignedInIfAndContinueWithReferenceBeforeAndAfter' => [
'<?php
function foo(): void {
$a = 5;
if ($a) {}
while (rand(0, 1)) {
if (rand(0, 1)) {
$a = 7;
continue;
}
$a = 3;
}
echo $a;
}',
],
'whileReassigned' => [
'<?php
function foo(): void {
$a = false;
while(rand(0, 1)) {
$a = true;
}
echo $a;
}',
],
'ifVarReassignedInBranch' => [
'<?php
function foo(): void {
$a = true;
if (rand(0, 1)) {
$a = false;
}
if ($a) {
echo "cool";
}
}',
],
'elseVarReassignedInBranchAndReference' => [
'<?php
function foo(): void {
$a = false;
if (rand(0, 1)) {
// do nothing
} else {
$a = true;
//echo $a;
}
if ($a) {
echo "cool";
}
}',
],
'switchVarReassignedInBranch' => [
'<?php
function foo(): void {
$a = false;
switch (rand(0, 2)) {
case 0:
$a = true;
}
if ($a) {
echo "cool";
}
}',
],
'switchVarReassignedInBranchWithDefault' => [
'<?php
function foo(): void {
$a = false;
switch (rand(0, 2)) {
case 0:
$a = true;
break;
default:
$a = false;
}
if ($a) {
echo "cool";
}
}',
],
'throwWithMessageCall' => [
'<?php
function dangerous(): void {
throw new \Exception("bad");
}
function callDangerous(): void {
try {
dangerous();
} catch (Exception $e) {
echo $e->getMessage();
}
}',
],
'throwWithMessageCallAndAssignmentAndReference' => [
'<?php
function dangerous(): string {
if (rand(0, 1)) {
throw new \Exception("bad");
}
return "hello";
}
function callDangerous(): void {
$s = null;
try {
$s = dangerous();
} catch (Exception $e) {
echo $e->getMessage();
}
if ($s) {}
}',
],
'throwWithMessageCallAndAssignmentInCatchAndReference' => [
'<?php
function dangerous(): string {
if (rand(0, 1)) {
throw new \Exception("bad");
}
return "hello";
}
function callDangerous(): void {
$s = null;
try {
dangerous();
} catch (Exception $e) {
echo $e->getMessage();
$s = "hello";
}
if ($s) {}
}',
],
'throwWithMessageCallAndAssignmentInTryAndCatchAndReference' => [
'<?php
function dangerous(): string {
if (rand(0, 1)) {
throw new \Exception("bad");
}
return "hello";
}
function callDangerous(): void {
$s = null;
try {
$s = dangerous();
} catch (Exception $e) {
echo $e->getMessage();
$s = "hello";
}
if ($s) {}
}',
],
'throwWithMessageCallAndNestedAssignmentInTryAndCatchAndReference' => [
'<?php
function dangerous(): string {
if (rand(0, 1)) {
throw new \Exception("bad");
}
return "hello";
}
function callDangerous(): void {
$s = null;
if (rand(0, 1)) {
$s = "hello";
} else {
try {
$t = dangerous();
} catch (Exception $e) {
echo $e->getMessage();
$t = "hello";
}
if ($t) {
$s = $t;
}
}
if ($s) {}
}',
],
'ifInReturnBlock' => [
'<?php
function foo(): void {
$i = false;
foreach ([1, 2, 3] as $a) {
if (rand(0, 1)) {
$i = true;
}
echo $a;
}
if ($i) {}
}',
],
'unknownMethodCallWithVar' => [
'<?php
/** @psalm-suppress MixedMethodCall */
function passesByRef(object $a): void {
$a->passedByRef($b);
}',
],
'constructorIsUsed' => [
'<?php
class A {
@ -627,63 +235,6 @@ class UnusedCodeTest extends TestCase
new A([1, 2, 3]);',
],
'usedMethodCallVariable' => [
'<?php
function reindex(array $arr, string $methodName): array {
$ret = [];
foreach ($arr as $element) {
$ret[$element->$methodName()] = true;
}
return $ret;
}',
'error_levels' => [
'MixedAssignment',
'MixedMethodCall',
'MixedArrayOffset',
],
],
'globalVariableUsage' => [
'<?php
$a = "hello";
function example() : void {
global $a;
echo $a;
}
example();',
],
'staticVar' => [
'<?php
function use_static() : void {
static $token;
if (!$token) {
$token = rand(1, 10);
}
echo "token is $token\n";
}',
],
'tryCatchWithUseInIf' => [
'<?php
function example_string() : string {
if (rand(0, 1) > 0) {
return "value";
}
throw new Exception("fail");
}
function main() : void {
try {
$s = example_string();
if (!$s) {
echo "Failed to get string\n";
}
} catch (Exception $e) {
$s = "fallback";
}
printf("s is %s\n", $s);
}',
],
'unusedParamWithUnderscore' => [
'<?php
function foo(int $_) : void {}
@ -742,275 +293,6 @@ class UnusedCodeTest extends TestCase
public function providerFileCheckerInvalidCodeParse()
{
return [
'function' => [
'<?php
/** @return int */
function foo() {
$a = 5;
$b = [];
return $a;
}',
'error_message' => 'UnusedVariable',
],
'ifInFunctionWithoutReference' => [
'<?php
/** @return int */
function foo() {
$a = 5;
if (rand(0, 1)) {
$b = "hello";
} else {
$b = "goodbye";
}
return $a;
}',
'error_message' => 'UnusedVariable',
],
'varInNestedAssignmentWithoutReference' => [
'<?php
function foo(): void {
if (rand(0, 1)) {
$a = "foo";
}
}',
'error_message' => 'UnusedVariable',
],
'varInSecondNestedAssignmentWithoutReference' => [
'<?php
function foo(): void {
if (rand(0, 1)) {
$a = "foo";
echo $a;
}
if (rand(0, 1)) {
$a = "foo";
}
}',
'error_message' => 'UnusedVariable',
],
'varReassignedInBothBranchesOfIf' => [
'<?php
function foo(): void {
$a = "foo";
if (rand(0, 1)) {
$a = "bar";
} else {
$a = "bat";
}
echo $a;
}',
'error_message' => 'UnusedVariable',
],
'varReassignedInNestedBranchesOfIf' => [
'<?php
function foo(): void {
$a = "foo";
if (rand(0, 1)) {
if (rand(0, 1)) {
$a = "bar";
} else {
$a = "bat";
}
} else {
$a = "bang";
}
echo $a;
}',
'error_message' => 'UnusedVariable',
],
'ifVarReassignedInBranch' => [
'<?php
function foo(): void {
$a = true;
if (rand(0, 1)) {
$a = false;
}
}',
'error_message' => 'UnusedVariable',
],
'elseVarReassignedInBranchAndNoReference' => [
'<?php
function foo(): void {
$a = true;
if (rand(0, 1)) {
// do nothing
} else {
$a = false;
}
}',
'error_message' => 'UnusedVariable',
],
'switchVarReassignedInBranch' => [
'<?php
function foo(): void {
$a = false;
switch (rand(0, 2)) {
case 0:
$a = true;
}
}',
'error_message' => 'UnusedVariable',
],
'switchVarReassignedInBranchWithDefault' => [
'<?php
function foo(): void {
$a = false;
switch (rand(0, 2)) {
case 0:
$a = true;
break;
default:
$a = false;
}
}',
'error_message' => 'UnusedVariable',
],
'unusedListVar' => [
'<?php
function foo(): void {
list($a, $b) = explode(" ", "hello world");
echo $a;
}',
'error_message' => 'UnusedVariable',
],
'unusedPreForVar' => [
'<?php
function foo(): void {
$i = 0;
for ($i = 0; $i < 10; $i++) {
echo $i;
}
}',
'error_message' => 'UnusedVariable',
],
'unusedIfInReturnBlock' => [
'<?php
function foo(): void {
$i = rand(0, 1);
foreach ([1, 2, 3] as $a) {
if ($a % 2) {
$i = 7;
return;
}
}
if ($i) {}
}',
'error_message' => 'UnusedVariable',
],
'unusedIfVarInBranch' => [
'<?php
function foo(): void {
if (rand(0, 1)) {
} elseif (rand(0, 1)) {
if (rand(0, 1)) {
$a = "foo";
} else {
$a = "bar";
echo $a;
}
}
}',
'error_message' => 'UnusedVariable',
],
'throwWithMessageCallAndAssignmentAndNoReference' => [
'<?php
function dangerous(): string {
if (rand(0, 1)) {
throw new \Exception("bad");
}
return "hello";
}
function callDangerous(): void {
$s = null;
try {
$s = dangerous();
} catch (Exception $e) {
echo $e->getMessage();
}
}',
'error_message' => 'UnusedVariable',
],
'whileTypeChangedInIfWithoutReference' => [
'<?php
function foo(): void {
$a = false;
while (rand(0, 1)) {
if (rand(0, 1)) {
$a = true;
}
}
}',
'error_message' => 'UnusedVariable',
],
'whileTypeChangedInIfAndContinueWithoutReference' => [
'<?php
function foo(): void {
$a = false;
while (rand(0, 1)) {
if (rand(0, 1)) {
$a = true;
continue;
}
$a = false;
}
}',
'error_message' => 'UnusedVariable',
],
'whileReassignedInIfAndContinueWithoutReferenceAfter' => [
'<?php
function foo(): void {
$a = 5;
while (rand(0, 1)) {
if (rand(0, 1)) {
$a = 7;
continue;
}
$a = 3;
}
}',
'error_message' => 'UnusedVariable',
],
'whileReassignedInIfAndContinueWithoutReference' => [
'<?php
function foo(): void {
$a = 3;
if ($a) {}
while (rand(0, 1)) {
if (rand(0, 1)) {
$a = 5;
continue;
}
$a = 3;
}
}',
'error_message' => 'UnusedVariable',
],
'unusedClass' => [
'<?php
class A { }',

1162
tests/UnusedVariableTest.php Normal file

File diff suppressed because it is too large Load Diff