diff --git a/config.xsd b/config.xsd
index 5c176821e24..0f3e88916c8 100644
--- a/config.xsd
+++ b/config.xsd
@@ -427,6 +427,7 @@
+
diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md
index 55a18b8fa61..f3df22adb45 100644
--- a/docs/running_psalm/error_levels.md
+++ b/docs/running_psalm/error_levels.md
@@ -244,6 +244,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
- [RedundantConditionGivenDocblockType](issues/RedundantConditionGivenDocblockType.md)
- [RedundantFunctionCallGivenDocblockType](issues/RedundantFunctionCallGivenDocblockType.md)
- [ReferenceConstraintViolation](issues/ReferenceConstraintViolation.md)
+- [RiskyTruthyFalsyComparison](issues/RiskyTruthyFalsyComparison.md)
- [UndefinedTrace](issues/UndefinedTrace.md)
- [UnresolvableInclude](issues/UnresolvableInclude.md)
- [UnsafeInstantiation](issues/UnsafeInstantiation.md)
diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md
index ac8135c7142..179f9bf7b53 100644
--- a/docs/running_psalm/issues.md
+++ b/docs/running_psalm/issues.md
@@ -229,6 +229,7 @@
- [ReferenceReusedFromConfusingScope](issues/ReferenceReusedFromConfusingScope.md)
- [ReservedWord](issues/ReservedWord.md)
- [RiskyCast](issues/RiskyCast.md)
+ - [RiskyTruthyFalsyComparison](issues/RiskyTruthyFalsyComparison.md)
- [StringIncrement](issues/StringIncrement.md)
- [TaintedCallable](issues/TaintedCallable.md)
- [TaintedCookie](issues/TaintedCookie.md)
diff --git a/docs/running_psalm/issues/RiskyTruthyFalsyComparison.md b/docs/running_psalm/issues/RiskyTruthyFalsyComparison.md
new file mode 100644
index 00000000000..8d60969633e
--- /dev/null
+++ b/docs/running_psalm/issues/RiskyTruthyFalsyComparison.md
@@ -0,0 +1,29 @@
+# RiskyTruthyFalsyComparison
+
+Emitted when comparing a value with multiple types that can both contain truthy and falsy values.
+
+```php
+
-
+
+
+
+ mapper]]>
+
+
+
+
+ $items
+
+
+
+
+ $returnType
+ attrGroups]]>
+ byRef]]>
+ expr]]>
+ params]]>
+ returnType]]>
+ static]]>
+
+
+
+
+
+
+
+ $returnType
+ attrGroups]]>
+ byRef]]>
+ params]]>
+ returnType]]>
+ static]]>
+ stmts]]>
+ uses]]>
+
+
+
+
+ $items
+
+
+
+
+ $parts
+
+
+
+
+ $conds
+
+
+
+
+ $parts
+ $parts
+ $parts
+
+
+
+
+ $stmts
+
+
+
+
+ $stmts
+
+
+
+
+ $returnType
+ attrGroups]]>
+ byRef]]>
+ flags]]>
+ params]]>
+ returnType]]>
+ stmts]]>
+
+
+
+
+ attrGroups]]>
+ extends]]>
+ flags]]>
+ implements]]>
+ stmts]]>
+
+
+
+
+ $stmts
+
+
+
+
+ $stmts
+
+
+
+
+ $stmts
+
+
+
+
+ $stmts
+
+
+
+
+ cond]]>
+ init]]>
+ loop]]>
+ stmts]]>
+
+
+
+
+ byRef]]>
+ keyVar]]>
+ stmts]]>
+
+
+
+
+ $returnType
+ attrGroups]]>
+ byRef]]>
+ params]]>
+ returnType]]>
+ stmts]]>
+
+
+
+
+ else]]>
+ elseifs]]>
+ stmts]]>
+
+
+
+
+ attrGroups]]>
+ extends]]>
+ stmts]]>
+
+
+
+
+ $stmts
+
+
+
+
+ attrGroups]]>
+ stmts]]>
+
+
+
+
+ $stmts
+
+
+
+
+ $stmts
+
+
+
+
+ static::getDefaultDescription()
+ static::getDefaultDescription()
+ static::getDefaultDescription()
+ static::getDefaultName()
+ static::getDefaultName()
+ static::getDefaultName()
+
+
+ $name
+
+
tags['variablesfrom'][0]]]>
@@ -12,6 +193,25 @@
$matches[1]
+
+
+ cased_name]]>
+
+
+
+
+ !$appearing_method_id
+
+
+
+
+ docblock_line_number]]>
+ docblock_line_number]]>
+ docblock_start]]>
+ docblock_start_line_number]]>
+ text]]>
+
+
$const_name
@@ -19,11 +219,83 @@
$symbol_name
$symbol_parts[1]
+
+ !$function_name
+ namespace]]>
+ namespace]]>
+ namespace]]>
+ namespace_first_stmt_start]]>
+ uses_end]]>
+ $file_path
+ insertText]]>
+ symbol, '()')]]>
+ symbol, '()')]]>
+ symbol, '()')]]>
+ symbol, '()')]]>
+ symbol, '::')]]>
+ symbol, '::')]]>
+ symbol, '\\')]]>
+
+
+
+
+
+
+
+
+
+
+
+
+ !$composer_json
+ !$config_path
+ !$file_path
+
+ $cwd
+ $dir
+ function_id]]>
+
+ $issue_handler_children
+ $parent_issue_type
+ composer_class_loader->findFile($pluginClassName)]]>
+ autoloader]]>
+ localName, $offset)]]>
+ name, $offset - strlen($file_contents))]]>
+
+
+
+
+ $suggested_dir
+ file_path, 'stub')]]>
+ file_path, 'vendor')]]>
+
+
+ !$directory_path
+ !$file_path
+ !$glob_directory_path
+ !$glob_file_path
+ directory]]>
+ file]]>
+ referencedClass]]>
+ referencedConstant]]>
+ referencedFunction]]>
+ referencedMethod]]>
+ referencedProperty]]>
+ referencedVariable]]>
+ glob($parts[0], GLOB_NOSORT)
+ glob($parts[0], GLOB_ONLYDIR | GLOB_NOSORT)
+
+
+
+
+
+
+
@@ -32,6 +304,17 @@
$matches[3]
+
+
+ $creating_conditional_id
+ $creating_conditional_id
+
+
+
+
+ name->name ?? null !== "name"]]>
+
+
$comments[0]
@@ -39,11 +322,147 @@
props[0]]]>
$uninitialized_variables[0]
+
+ !$declaring_property_class
+ !$fq_class_name
+ self]]>
+ self]]>
+ self]]>
+ self]]>
+ template_extended_params]]>
+ template_types]]>
+ $class_template_params
+ initialized_class]]>
+ $parent_fq_class_name
+ getStmts()]]>
+ getStmts()]]>
+ template_extended_params]]>
+ template_types]]>
+ classlike_storage_provider->get($original_fq_classlike_name),
+ strtolower($stmt->name->name),
+ $this_object_type,
+ )]]>
+
$property_name
+
+ !$appearing_property_class
+ self]]>
+ !$declaring_property_class
+ self]]>
+ template_types]]>
+ $resolved_name
+ template_covariants]]>
+ template_extended_params]]>
+ template_types]]>
+ template_types]]>
+
+
+
+
+ self]]>
+ self]]>
+ self]]>
+
+
+
+
+ !$original_type
+ description]]>
+ var_id]]>
+ !$var_type_tokens
+ $brackets
+ $template_type_map
+ $type_aliases
+ line_number]]>
+ type_end]]>
+ type_start]]>
+
+
+
+
+ $namespace_name
+ $namespace_name
+ root_file_name]]>
+ root_file_path]]>
+
+
+
+
+ $namespace
+ $namespace
+ getNamespace()]]>
+
+
+
+
+ getStmts()]]>
+ $class_template_params
+ self]]>
+ self]]>
+ $fq_class_name
+ $self_fq_class_name
+
+
+
+
+ calling_method_id]]>
+ cased_name]]>
+ cased_name]]>
+
+ template_types]]>
+ template_types]]>
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ self]]>
+ self]]>
+ self]]>
+ self]]>
+ self]]>
+ $context_self
+ $hash
+ $namespace
+ $parent_fqcln
+ $parent_fqcln
+ cased_name]]>
+ template_types]]>
+ $template_types
+ function->getStmts()]]>
+ source->getTemplateTypeMap()]]>
+ storage->template_types]]>
+
+
+
+
+
+ !$calling_method_id
+ self]]>
+ $appearing_method_class
+ $appearing_method_class
+ self]]>
+ $context_self
+
+
+
+
+ template_types]]>
+ cased_name]]>
+ cased_name]]>
+ cased_name]]>
+ template_extended_params]]>
+ template_extended_params]]>
+ template_extended_params]]>
+ defining_fqcln]]>
+
@@ -53,22 +472,86 @@
$php_minor_version
$source_parts[1]
+
+ self]]>
+
+ $potential_file_path
+
+
+
+
+
+ branch_point]]>
+
cond]]>
+
+ branch_point]]>
+
if (AtomicTypeComparator::isContainedBy(
if (AtomicTypeComparator::isContainedBy(
+
+ var_id]]>
+ var_id]]>
+ $calling_type_params
+ branch_point]]>
+ template_types]]>
+ getTemplateTypeMap()]]>
+ line_number]]>
+ type_end]]>
+ type_start]]>
+ $var_id
+ $var_id
+
+
+
+
+ negatable_if_types]]>
+ getTemplateTypeMap()]]>
+
+
+
+
+ getTemplateTypeMap()]]>
+ getTemplateTypeMap()]]>
+
+
+
+
+ getTemplateTypeMap()]]>
+
+
+
+
+ branch_point]]>
+ branch_point]]>
+ branch_point]]>
+ assigned_var_ids]]>
+ new_vars]]>
+ redefined_vars]]>
+ getTemplateTypeMap()]]>
+
assigned_var_ids += $switch_scope->new_assigned_var_ids]]>
+
+ !$switch_var_id
+ new_assigned_var_ids]]>
+ new_vars_in_scope]]>
+ possibly_redefined_vars]]>
+ possibly_redefined_vars]]>
+ redefined_vars]]>
+ $switch_var_id
+
@@ -76,6 +559,29 @@
leftover_statements[0]]]>
traverse([$switch_condition])[0]]]>
+
+ branch_point]]>
+ $nested_or_options
+ $switch_var_id
+ $switch_var_id
+ $switch_var_id
+ $type_statements
+
+
+
+
+ branch_point]]>
+
+
+
+
+ branch_point]]>
+
+
+
+
+ $var_id
+
@@ -108,23 +614,204 @@
getArgs()[0]]]>
getArgs()[0]]]>
+
+ !$var_name
+ !$var_type
+ ')]]>
+
+ $array_root
+ $count_equality_position
+ $count_equality_position
+ $count_equality_position
+ $count_inequality_position
+ $count_inequality_position
+ $count_inequality_position
+ $false_position
+ $false_position
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name
+ $first_var_name_in_array_argument
+ $get_debug_type_position
+ $get_debug_type_position
+ $getclass_position
+ $getclass_position
+ $gettype_position
+ $gettype_position
+ $if_false_assertions
+ $if_true_assertions
+ $inferior_value_position
+ $other_var_name
+ $superior_value_position
+ $this_class_name
+ $this_class_name
+ $this_class_name
+ $true_position
+ $true_position
+ $typed_value_position
+ $typed_value_position
+ $var_id
+ $var_id
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name
+ $var_name_left
+ $var_name_right
+ $var_type
+ $var_type
+ $var_type
+ self::hasReconcilableNonEmptyCountEqualityCheck($conditional)
+
+
+
+
+ !$parent_var_id
+ $object_id
+ $parent_var_id
+ $parent_var_id
+ $root_var_id
+ $root_var_id
+ $root_var_id
+ $root_var_id
+ $root_var_id
+ $var_id
+ $var_var_id
+
+
+
+
+ self]]>
+ !$var_id
+ $appearing_property_class
+ $class_template_params
+ $class_template_params
+ calling_method_id]]>
+ calling_method_id]]>
+ self]]>
+ self]]>
+ self]]>
+ $declaring_property_class
+ getter_method]]>
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_property_id
+ $var_property_id
+ calling_method_id, '::__clone')]]>
+ calling_method_id, '::__construct')]]>
+ calling_method_id, '::__unserialize')]]>
+ calling_method_id, '::unserialize')]]>
+
$new_property_name
+
+ calling_method_id]]>
+ $var_id
+ $var_id
+
+
+ var_id]]>
+ ')]]>
+ ')]]>
+
+ $assign_value_id
+ calling_method_id]]>
+ $extended_var_id
+ $extended_var_id
+ $extended_var_id
+ $extended_var_id
+ $extended_var_id
+ $list_var_id
+ $list_var_id
+ $list_var_id
+ $prop_name
+ $root_var_id
+ line_number]]>
+ type_end]]>
+ type_start]]>
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+
vars_in_scope[$lhs_var_id] = &$context->vars_in_scope[$rhs_var_id]]]>
+
+
+ getTemplateTypeMap()]]>
+
+
$invalid_left_messages[0]
$invalid_right_messages[0]
+
+
+ branch_point]]>
+ $var_id
+
+
verifyType
@@ -134,6 +821,54 @@
$parts[1]
+
+ !$container_class
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $class_generic_params
+ calling_function_id]]>
+ calling_function_id]]>
+ calling_method_id]]>
+ $self_fq_class_name
+ $static_fq_class_name
+ $var_id
+ value, '::')]]>
+ value, '::')]]>
+
+
+
+
+ self]]>
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ $cased_method_id
+ calling_method_id]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ sinks]]>
+ $function_params
+ $function_params
+ $function_params
+ template_types]]>
+ $method_id
+ $method_id
+ $method_id
+ $method_id
+ $var_id
+ $var_id
+ $var_id
+ getFQCLN())]]>
+
@@ -142,19 +877,117 @@
$args[1]
$method_name
+
+ !$container_class
+ calling_method_id]]>
+ $var_id
+
+
+
+
+ !$template_types
+ !$template_types
+ template_types]]>
+ $method_name
+ $overridden_template_types
+ template_extended_params]]>
+ template_types]]>
+
+
+ $function_name
+ $function_name
+
+
+ getArgs()[0]->value]]>
+
getArgs()[0]]]>
$parts[1]
+
+ function_id]]>
+ function_id]]>
+ function_id]]>
+ function_id]]>
+ function_id]]>
+ function_id]]>
+ getTemplateTypeMap()]]>
+ value, '::')]]>
+
$method
+
+ self]]>
+ self]]>
+ self]]>
+ template_types]]>
+ template_types]]>
+
+
+
+
+ mixin_declaring_fqcln]]>
+ mixin_declaring_fqcln]]>
+ template_types]]>
+ template_types]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ self]]>
+ $lhs_var_id
+ $mixin_class_template_params
+
+
+
+
+ $class_template_params
+ calling_method_id]]>
+ calling_method_id]]>
+ $lhs_var_id
+ template_types]]>
+ template_types]]>
+
+
+
+
+ $caller_identifier
+
+
+
+
+ this_property_mutations]]>
+
+
+
+
+ specialization_key]]>
+ $var_id
+
+
+
+
+ self]]>
+ self]]>
+ $appearing_method_name
+
+
+
+
+ $found_generic_params
+ $found_generic_params
+ $found_generic_params
+ $found_generic_params
+ $found_generic_params
+ $found_generic_params
+ $intersection_method_id
+ $intersection_method_id
+
@@ -165,23 +998,115 @@
non_existent_interface_method_ids[0]]]>
non_existent_magic_method_ids[0]]]>
+
+ getFQCLN()]]>
+ $lhs_var_id
+ $lhs_var_id
+ $lhs_var_id
+
+
+
+
+ getFQCLN()]]>
+ $path_to_file
+ $var_id
+ ')]]>
+
+
+
+
+
+ calling_method_id]]>
+ self]]>
+ $fq_class_name
+ $fq_class_name
+ getFullyQualifiedFunctionMethodOrNamespaceName()]]>
+ template_extended_params]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+
+
+
+
+ parent_class]]>
+ $child_fq_class_name
+ calling_method_id]]>
+ self]]>
+ self]]>
+
+
+
+
+ self]]>
+ !$fq_class_name
+ mixin_declaring_fqcln]]>
+ parent_class]]>
+ parent_class]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ self]]>
+
$new_method_name
+
+ self]]>
+ self]]>
+ self]]>
+ self]]>
+ $found_generic_params
+ $found_generic_params
+ template_extended_params]]>
+
items[0]]]>
items[1]]]>
+
+ !$arg_var_id
+ $arg_var_id
+ $assertion_var_id
+ template_extended_params]]>
+ self]]>
+ self]]>
+ self]]>
+ template_types]]>
+ template_types]]>
+
$new_const_name
$new_const_name
+
+ self]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ self]]>
+ self]]>
+ self]]>
+ self]]>
+
+
+
+
+ !$lhs_var_name
+ !$object_id
+ !$object_id
+ !$this_class_name
+ $object_id
+ $property_root
+ $resolved_name
+ $resolved_name
+ $root_var_id
+ $this_class_name
+
@@ -189,31 +1114,143 @@
$stmt_type
$stmt_type
+
+ $dim_var_id
+ $dim_var_id
+ $extended_var_id
+ $extended_var_id
+ $keyed_array_var_id
+ $keyed_array_var_id
+ $keyed_array_var_id
+ $keyed_array_var_id
+
$stmt_type
+
+ self]]>
+ self]]>
+ $declaring_property_class
+ $declaring_property_class
+ template_types]]>
+ template_types]]>
+ $var_id
+ $var_id
+ $var_property_id
+ $var_property_id
+
$invalid_fetch_types[0]
+
+ !$prop_name
+ calling_method_id]]>
+ calling_method_id]]>
+ $declaring_property_class
+ $stmt_var_id
+ $var_id
+ $var_id
+
$new_property_name
+
+ !$prop_name
+ calling_method_id]]>
+ calling_method_id]]>
+ calling_method_id]]>
+ self]]>
+ $string_type
+ $var_id
+ $var_id
+
+
+
+
+ $branch_point
+ $branch_point
+
+
+
+
+ $var_id
+
+
+
+
+ !$evaled_path
+ !$var_id
+ $include_path
+ $left_string
+ $path_to_file
+ $right_string
+ $var_id
+
+
+
+
+ self]]>
+
+
+
+
+ !$switch_var_id
+ $switch_var_id
+
+
+
+
+ $fq_classlike_name
+
+
+
+
+ branch_point]]>
+ getTemplateTypeMap()]]>
+ getTemplateTypeMap()]]>
+
type_params[2]]]>
+
+ var_id]]>
+ $class_template_params
+ declaring_yield_fqcn]]>
+ self]]>
+ line_number]]>
+ type_end]]>
+ type_start]]>
+
$method_name
+
+ calling_function_id]]>
+ calling_method_id]]>
+ var_id]]>
+ calling_function_id]]>
+ self]]>
+ $found_generic_params
+ line_number]]>
+ type_end]]>
+ type_start]]>
+
+
+
+
+ $root_var_id
+ $var_id
+
@@ -231,21 +1268,84 @@
expr->getArgs()[0]]]>
+
+ $branch_point
+ $new_issues
+ getNamespace()]]>
+ $possible_traced_variable_names
+ fake_this_class]]>
+ vars_to_initialize]]>
+
+
+
+
+ UndefinedFunction
+ UndefinedFunction
+
+
+
+
+ !$root_path
+
+
+
+ error_baseline]]>
+ !$paths_to_check
+ !$root_path
+
+
+ $baseline_file_path
+ $cache_directory
+ threads]]>
+ $find_references_to
+ empty($baselineFile)
+
+
+
+
+
+ !$root_path
+ $paths_to_check
+
$identifier_name
+
+ !$last_arg
+ !$last_arg
+ !$last_arg
+ !$root_path
+
+
+
+
+
+ !$config_file
+ !$end_psalm_open_tag
+ !$path_to_check
+ error_baseline]]>
+ $f_paths
+ $path_to_config
+ $stdin = fgets(STDIN)
+ getPHPVersionFromComposerJson()]]>
+ getPhpVersionFromConfig()]]>
+
+
$trait
+
+
+
@@ -255,11 +1355,92 @@
$source_const_name
$stub
+
+ !$calling_fq_class_name
+ !$insert_pos
+ !$insert_pos
+ !$insert_pos
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_fq_class_name
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $file_path
+ $file_path
+ $file_path
+ $file_path
+ $file_path
+ $migrated_source_fqcln
+ $migrated_source_fqcln
+
+
+
+
+ value]]>
+
$stub
+
+ !$checked_file_path
+ !$root_file_path
+ $args
+ cased_name]]>
+ $namespace
+
+
+
+
+ !$return_type_string
+
+
+
+
+ !$calling_class_name
+ !$extends
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $found_generic_params
+ $old_method_id
+ $source_file_path
+ $source_file_path
+ $source_file_path
+ $source_file_path
+ $source_file_path
+ $source_file_path
+ $source_file_path
+ $source_file_path
+
+
+
+
+
+ $mapped_name
+ template_extended_params]]>
+ template_extended_params]]>
+ template_extended_params]]>
+ template_types]]>
+ template_extended_params]]>
+
@@ -270,6 +1451,38 @@
$property_name
$property_name
+
+ calling_method_id]]>
+ calling_method_id]]>
+ calling_method_id]]>
+
+
+
+
+ $composer_file_path
+ cased_name]]>
+ cased_name]]>
+
+
+
+
+ specialization_key]]>
+ unspecialized_id]]>
+ escaped_taints]]>
+ unescaped_taints]]>
+ specialization_key]]>
+ path_types)]]>
+
+
+
+
+
+
+
+
+
+ $specialization_key
+
@@ -280,28 +1493,110 @@
stmts[0]]]>
$b_stmt_comments[0]
+
+ stmts]]>
+ stmts]]>
+
$b[$y]
+
+
+ readEnv['CI_PR_NUMBER']]]>
+
+
$exploded[1]
$url
+
+
+ $var_end
+ $var_start
+
+
+
+
+
+ new_php_return_type]]>
+ $last_arg_position
+ new_php_return_type]]>
+ new_phpdoc_return_type]]>
+ return_typehint_colon_start]]>
+ return_typehint_end]]>
+ return_typehint_end]]>
+ return_typehint_start]]>
+ return_typehint_start]]>
+ $php_type
+ new_phpdoc_return_type]]>
+ new_psalm_return_type]]>
+ return_type_description]]>
+ return_type_description]]>
+
+
props[0]]]>
+
+ new_php_type]]>
+ new_php_type]]>
+ new_phpdoc_type]]>
+ typehint_end]]>
+ typehint_end]]>
+ typehint_start]]>
+ typehint_start]]>
+ $preceding_semicolon_pos
+ new_phpdoc_type]]>
+ new_psalm_type]]>
+ type_description]]>
+
+
+
+
+ !$sockets
+
+
+
+
+ tmpIni]]>
+
+
+
+
+ empty($message)
+
+
+
+
+ TCPServerAddress]]>
+ TCPServerAddress]]>
+ onchangeLineLimit]]>
+ empty($additional_info)
+
$method_id_parts[1]
+
+
+ $arg_var_id
+ $arg_var_id
+ $left_var_id
+ $left_var_id
+ $right_var_id
+ $right_var_id
+ $var_id
+ $var_id
+
+
$cs[0]
@@ -313,6 +1608,17 @@
$replacement_stmts[0]
$replacement_stmts[0]
+
+ !$method_contents
+ parser->parse(
+ $hacky_class_fix,
+ $error_handler,
+ )]]>
+ parser->parse(
+ $fake_class,
+ $error_handler,
+ )]]>
+
@@ -321,18 +1627,56 @@
children[0]]]>
children[1]]]>
+
+ !$method_entry
+
+
$l[4]
$r[4]
+
+ !$var_line_parts
+ newModifier]]>
+ $class_name
+ description]]>
+ inheritors]]>
+ yield]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+ aliases->namespace]]>
+ aliases->namespace]]>
+ line_number]]>
+ type_end]]>
+ type_start]]>
+
+
+
+
+ $fq_classlike_name
+ $string_value
+ $string_value
+ $string_value
+
getArgs()[0]]]>
getArgs()[1]]]>
+
+ !$skip_if_descendants
+ !$skip_if_descendants
+ $include_path
+ $path_to_file
+
@@ -346,16 +1690,75 @@
$source_param_string
+
+ namespace]]>
+ template_types]]>
+ template_types]]>
+ description]]>
+ return_type_end]]>
+ return_type_line_number]]>
+ return_type_line_number]]>
+ return_type_start]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+ template_types]]>
+ $template_types
+ $template_types
+ $template_types
+
stmts[0]]]>
+
+ stmts]]>
+ aliases->namespace]]>
+ aliases->namespace]]>
+ template_types]]>
+ $fq_classlike_name
+ $function_id
+ $function_id
+ $method_name_lc
+ stmts]]>
+ stmts]]>
+ stmts]]>
+ stmts]]>
+ aliases->namespace]]>
+ aliases->namespace]]>
+
+
+
+
+ $type_string
+
+
+
+
+ aliases->uses_start]]>
+ aliases->uses_start]]>
+ skip_if_descendants]]>
+ skip_if_descendants]]>
+ skip_if_descendants]]>
+ skip_if_descendants]]>
+ skip_if_descendants]]>
+ skip_if_descendants]]>
+ skip_if_descendants]]>
+ code_location->file_path, 'CoreGenericClasses.phpstub')]]>
+ code_location->file_path, 'CoreGenericFunctions.phpstub')]]>
+ file_path, 'CoreGenericIterators.phpstub')]]>
+
$cs[0]
+
+ $offset_map
+ end_change]]>
+ start_change]]>
+
@@ -383,6 +1786,87 @@
getOption('config')]]>
+
+
+ !$path
+ $explicit_path
+ psalm_header]]>
+ psalm_tag_end_pos]]>
+
+
+
+
+ enabled_plugins]]>
+
+
+
+
+ !$root_cache_directory
+ $file_contents
+ $file_path
+
+
+
+
+ !$cache_directory
+ !$cache_directory
+ !$cache_directory
+ $cache_directory
+
+
+
+
+ cache->getFileMapCache()]]>
+
+
+
+
+ !$root_cache_directory
+
+
+
+
+ $result
+
+
+
+
+ $called_method_name
+
+
+
+
+ $extended_var_id
+
+
+
+
+ !$cache_directory
+ !$root_cache_directory
+ !$root_cache_directory
+ !$root_cache_directory
+
+
+
+
+ !$cache_directory
+ !$cache_directory
+ composer_lock_hash]]>
+ $cache_directory
+
+
+
+
+ !$key_column_name
+
+
+
+
+ $callable_extended_var_id
+ getTemplateTypeMap()]]>
+ getTemplateTypeMap()]]>
+
+
$callable_method_name
@@ -398,6 +1882,73 @@
$method_name
+
+
+ $fetch_class_name
+
+
+
+
+ !$call_args
+
+
+
+
+ $existing_file_contents
+ $existing_file_contents
+ $existing_file_contents
+ $existing_statements
+ $existing_statements
+ $existing_statements
+ $existing_statements
+ $file_changes
+ $file_path
+ parse($file_contents, $error_handler)]]>
+ parse($file_contents, $error_handler)]]>
+
+
+
+
+
+ $first_line_padding
+
+
+
+
+ !$resolved_name
+ $mapped_type = $map[$offset_arg_value] ?? null
+ $mapped_type = $map[$offset_arg_value] ?? null
+
+
+
+
+
+
+ cased_name]]>
+ template_types]]>
+ parent_class]]>
+ template_types]]>
+
+
+
+
+ cased_name]]>
+ cased_name]]>
+ template_types]]>
+
+
+
+
+ $key
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+
+
isContainedBy
@@ -414,6 +1965,36 @@
TCallable|TClosure|null
+
+ !$class_name
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ $calling_method_id
+ params]]>
+ $file_name
+ $file_name
+ $input_variadic_param_idx
+ $member_id
+
+
+
+
+ !($container_type_params_covariant[$i] ?? false)
+
+
+
+
+ $intersection_container_type_lower
+
+
+
+
+ $key
+ $key
+ $key
+
@@ -422,11 +2003,83 @@
$properties[0]
$properties[0]
+
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+
+
+
+
+
+ !$count
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $key
+ $var_id
+ $var_id
+ $var_id
+ $var_id
+
+
+
+
+ template_extended_params]]>
+
getClassTemplateTypes
+
+
+
+ $input_template_types
+ template_extended_params[$container_class])]]>
+ template_extended_params[$base_type->as_type->value])]]>
+ template_extended_params[$base_type->value])]]>
+
+
@@ -437,11 +2090,48 @@
array_type_params[1]]]>
array_type_params[1]]]>
+
+ class_string_types]]>
+ floats]]>
+ ints]]>
+ named_object_types]]>
+ strings]]>
+ array_counts]]>
+ array_min_counts]]>
+ array_min_counts]]>
+ class_string_types]]>
+ class_string_types]]>
+ floats]]>
+ ints]]>
+ ints]]>
+ ints]]>
+ named_object_types]]>
+ strings]]>
+ strings]]>
+ strings]]>
+ strings]]>
+ value_types['string'] instanceof TNonFalsyString
+ ? $type->value
+ : $type->value !== '']]>
+ $shared_classlikes
+
$fallback_params
+
+ template_types]]>
+ $params
+ $parent_class
+ $self_class
+ $self_class
+ $self_class
+ $self_class
+ $self_class
+ $self_class
+ $static_class_type
+
@@ -455,6 +2145,11 @@
array_keys($template_type_map[$fq_classlike_name])[0]
array_keys($template_type_map[$template_param_name])[0]
+
+ $extra_params
+ value, '::')]]>
+ value, '::')]]>
+
@@ -463,6 +2158,59 @@
$type_tokens[$i - 1]
$type_tokens[$i - 1]
+
+ $parent_fqcln
+ $self_fqcln
+
+
+
+
+
+ !$fq_classlike_name
+ template_types]]>
+ template_types]]>
+ calling_method_id]]>
+
+
+
+
+ $function_id
+
+
+
+
+ $function_id
+
+
+
+
+ $function_id
+
+
+
+
+ output_path]]>
+ $parent_issue_type
+
+
+
+
+ other_references]]>
+ taint_trace]]>
+
+
+
+
+
+ other_references]]>
+ taint_trace]]>
+
+
+
+
+
+ taint_trace]]>
+
@@ -477,6 +2225,19 @@
traverse
+
+
+ $this_var_id
+
+
+
+
+ !$namespace
+ $namespace
+ $namespace
+
+
+
classOrInterfaceExists
@@ -495,6 +2256,9 @@
$value
+
+
+
@@ -503,6 +2267,10 @@
replace
replace
+
+ $params
+ $params
+
@@ -513,6 +2281,9 @@
type_params[1]]]>
+
+ !($container_type_params_covariant[$offset] ?? true)
+
@@ -528,6 +2299,10 @@
replace
+
+ !$namespace
+ $namespace
+
@@ -540,6 +2315,12 @@
value_param]]>
+
+
+ !$intersection
+ !$intersection
+
+
replace
@@ -550,6 +2331,17 @@
__construct
+
+
+ !$intersection
+ !$intersection
+
+
+
+
+ !$intersection
+
+
TList
@@ -591,6 +2383,18 @@
type_param]]>
+
+
+ !$namespace
+ $namespace
+
+
+
+
+ !$intersection
+ $intersection
+
+
TList
@@ -604,12 +2408,21 @@
replace
replace
+
+ !$intersection
+ !$intersection
+
replace
+
+
+ !$intersection
+
+
replace
@@ -620,6 +2433,11 @@
replace
+
+
+ extra_types]]>
+
+
$allow_mutations
@@ -630,11 +2448,24 @@
$initialized_class
$reference_free
+
+
+
$const_name
+
+
+
+ $array_key_offset
+ $failed_reconciliation
+
+
+ ')]]>
+
+
@@ -670,10 +2501,41 @@
hasLowercaseString
hasLowercaseString
-
-
-
-
-
+
+ !$php_type
+ exact_id]]>
+ id]]>
+ exact_id]]>
+ exact_id]]>
+ id]]>
+ id]]>
+
+
+
+
+
+
+
+
+
+
+ $level
+ $php_version
+
+
+
+
+
+
+
+
+
+
+ $param_type_1
+ $param_type_2
+ $param_type_3
+ $param_type_4
+ $return_type
+
diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php
index a306cca4ca0..ebbc0a66e4a 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php
@@ -15,8 +15,10 @@
use Psalm\Issue\DocblockTypeContradiction;
use Psalm\Issue\RedundantCondition;
use Psalm\Issue\RedundantConditionGivenDocblockType;
+use Psalm\Issue\RiskyTruthyFalsyComparison;
use Psalm\Issue\TypeDoesNotContainType;
use Psalm\IssueBuffer;
+use Psalm\Type\Atomic\TBool;
use Psalm\Type\Reconciler;
use function array_diff_key;
@@ -366,6 +368,34 @@ public static function handleParadoxicalCondition(
$statements_analyzer->getSuppressedIssues(),
);
}
+ } elseif (!($stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical)
+ && !($stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical)
+ && !($stmt instanceof PhpParser\Node\Expr\BooleanNot)) {
+ if (count($type->getAtomicTypes()) > 1) {
+ $both_types = $type->getBuilder();
+ foreach ($both_types->getAtomicTypes() as $key => $atomic_type) {
+ if ($atomic_type->isTruthy()
+ || $atomic_type->isFalsy()
+ || $atomic_type instanceof TBool) {
+ $both_types->removeType($key);
+ }
+ }
+
+ if (count($both_types->getAtomicTypes()) > 0) {
+ $both_types = $both_types->freeze();
+ IssueBuffer::maybeAdd(
+ new RiskyTruthyFalsyComparison(
+ 'Operand of type ' . $type->getId() . ' contains ' .
+ 'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' .
+ $both_types->getId() . ', which can be falsy and truthy. ' .
+ 'This can cause possibly unexpected behavior. Use strict comparison instead.',
+ new CodeLocation($statements_analyzer, $stmt),
+ $type->getId(),
+ ),
+ $statements_analyzer->getSuppressedIssues(),
+ );
+ }
+ }
}
}
}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php
index 3c75dd9efca..93d17c3f7f5 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php
@@ -3,15 +3,20 @@
namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser;
+use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
+use Psalm\Issue\RiskyTruthyFalsyComparison;
+use Psalm\IssueBuffer;
use Psalm\Type;
use Psalm\Type\Atomic\TBool;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TTrue;
use Psalm\Type\Union;
+use function count;
+
/**
* @internal
*/
@@ -40,6 +45,32 @@ public static function analyze(
} elseif ($expr_type->isAlwaysFalsy()) {
$stmt_type = new TTrue($expr_type->from_docblock);
} else {
+ if (count($expr_type->getAtomicTypes()) > 1) {
+ $both_types = $expr_type->getBuilder();
+ foreach ($both_types->getAtomicTypes() as $key => $atomic_type) {
+ if ($atomic_type->isTruthy()
+ || $atomic_type->isFalsy()
+ || $atomic_type instanceof TBool) {
+ $both_types->removeType($key);
+ }
+ }
+
+ if (count($both_types->getAtomicTypes()) > 0) {
+ $both_types = $both_types->freeze();
+ IssueBuffer::maybeAdd(
+ new RiskyTruthyFalsyComparison(
+ 'Operand of type ' . $expr_type->getId() . ' contains ' .
+ 'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' .
+ $both_types->getId() . ', which can be falsy and truthy. ' .
+ 'This can cause possibly unexpected behavior. Use strict comparison instead.',
+ new CodeLocation($statements_analyzer, $stmt),
+ $expr_type->getId(),
+ ),
+ $statements_analyzer->getSuppressedIssues(),
+ );
+ }
+ }
+
$stmt_type = new TBool();
}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php
index 3b9014f85b4..8dbcca2f9bb 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php
@@ -8,8 +8,15 @@
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Issue\ForbiddenCode;
use Psalm\Issue\InvalidArgument;
+use Psalm\Issue\RiskyTruthyFalsyComparison;
use Psalm\IssueBuffer;
use Psalm\Type;
+use Psalm\Type\Atomic\TBool;
+use Psalm\Type\Atomic\TFalse;
+use Psalm\Type\Atomic\TTrue;
+use Psalm\Type\Union;
+
+use function count;
/**
* @internal
@@ -35,21 +42,64 @@ public static function analyze(
);
}
- if (($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr))
- && $stmt_expr_type->hasBool()
- && $stmt_expr_type->isSingle()
- && !$stmt_expr_type->from_docblock
- ) {
- IssueBuffer::maybeAdd(
- new InvalidArgument(
- 'Calling empty on a boolean value is almost certainly unintended',
- new CodeLocation($statements_analyzer->getSource(), $stmt->expr),
- 'empty',
- ),
- $statements_analyzer->getSuppressedIssues(),
- );
+ $expr_type = $statements_analyzer->node_data->getType($stmt->expr);
+
+ if ($expr_type) {
+ if ($expr_type->hasBool()
+ && $expr_type->isSingle()
+ && !$expr_type->from_docblock
+ ) {
+ IssueBuffer::maybeAdd(
+ new InvalidArgument(
+ 'Calling empty on a boolean value is almost certainly unintended',
+ new CodeLocation($statements_analyzer->getSource(), $stmt->expr),
+ 'empty',
+ ),
+ $statements_analyzer->getSuppressedIssues(),
+ );
+ }
+
+ if ($expr_type->isAlwaysTruthy() && $expr_type->possibly_undefined === false) {
+ $stmt_type = new TFalse($expr_type->from_docblock);
+ } elseif ($expr_type->isAlwaysFalsy()) {
+ $stmt_type = new TTrue($expr_type->from_docblock);
+ } else {
+ if (count($expr_type->getAtomicTypes()) > 1) {
+ $both_types = $expr_type->getBuilder();
+ foreach ($both_types->getAtomicTypes() as $key => $atomic_type) {
+ if ($atomic_type->isTruthy()
+ || $atomic_type->isFalsy()
+ || $atomic_type instanceof TBool) {
+ $both_types->removeType($key);
+ }
+ }
+
+ if (count($both_types->getAtomicTypes()) > 0) {
+ $both_types = $both_types->freeze();
+ IssueBuffer::maybeAdd(
+ new RiskyTruthyFalsyComparison(
+ 'Operand of type ' . $expr_type->getId() . ' contains ' .
+ 'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' .
+ $both_types->getId() . ', which can be falsy and truthy. ' .
+ 'This can cause possibly unexpected behavior. Use strict comparison instead.',
+ new CodeLocation($statements_analyzer, $stmt),
+ $expr_type->getId(),
+ ),
+ $statements_analyzer->getSuppressedIssues(),
+ );
+ }
+ }
+
+ $stmt_type = new TBool();
+ }
+
+ $stmt_type = new Union([$stmt_type], [
+ 'parent_nodes' => $expr_type->parent_nodes,
+ ]);
+ } else {
+ $stmt_type = Type::getBool();
}
- $statements_analyzer->node_data->setType($stmt, Type::getBool());
+ $statements_analyzer->node_data->setType($stmt, $stmt_type);
}
}
diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php
index 4a3a1f5c903..753e2891920 100644
--- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php
+++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php
@@ -313,14 +313,18 @@ public static function analyze(
&& !$context->inside_unset
&& ($stmt_var_type && !$stmt_var_type->hasMixed())
) {
- IssueBuffer::maybeAdd(
+ if (IssueBuffer::accepts(
new PossiblyUndefinedArrayOffset(
'Possibly undefined array key ' . $keyed_array_var_id
. ' on ' . $stmt_var_type->getId(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
),
$statements_analyzer->getSuppressedIssues(),
- );
+ )) {
+ $stmt_type = $stmt_type->getBuilder()->addType(new TNull())->freeze();
+ }
+ } elseif ($stmt_type->possibly_undefined) {
+ $stmt_type = $stmt_type->getBuilder()->addType(new TNull())->freeze();
}
$stmt_type = $stmt_type->setPossiblyUndefined(false);
diff --git a/src/Psalm/Issue/RiskyTruthyFalsyComparison.php b/src/Psalm/Issue/RiskyTruthyFalsyComparison.php
new file mode 100644
index 00000000000..68ab4e1322b
--- /dev/null
+++ b/src/Psalm/Issue/RiskyTruthyFalsyComparison.php
@@ -0,0 +1,17 @@
+dupe_key = $dupe_key;
+ }
+}
diff --git a/src/Psalm/PluginFileExtensionsSocket.php b/src/Psalm/PluginFileExtensionsSocket.php
index 1e698e89cc5..0b5ac1333fa 100644
--- a/src/Psalm/PluginFileExtensionsSocket.php
+++ b/src/Psalm/PluginFileExtensionsSocket.php
@@ -55,8 +55,8 @@ public function addFileTypeScanner(string $fileExtension, string $className): vo
1_622_727_271,
);
}
- if (!empty($this->config->getFiletypeScanners()[$fileExtension])
- || !empty($this->additionalFileTypeScanners[$fileExtension])
+ if (isset($this->config->getFiletypeScanners()[$fileExtension])
+ || isset($this->additionalFileTypeScanners[$fileExtension])
) {
throw new LogicException(
sprintf('Cannot redeclare scanner for file-type %s', $fileExtension),
@@ -91,8 +91,8 @@ public function addFileTypeAnalyzer(string $fileExtension, string $className): v
1_622_727_281,
);
}
- if (!empty($this->config->getFiletypeAnalyzers()[$fileExtension])
- || !empty($this->additionalFileTypeAnalyzers[$fileExtension])
+ if (isset($this->config->getFiletypeAnalyzers()[$fileExtension])
+ || isset($this->additionalFileTypeAnalyzers[$fileExtension])
) {
throw new LogicException(
sprintf('Cannot redeclare analyzer for file-type %s', $fileExtension),
diff --git a/tests/ArrayAccessTest.php b/tests/ArrayAccessTest.php
index 8bc2a488fc1..88eb7d15e52 100644
--- a/tests/ArrayAccessTest.php
+++ b/tests/ArrayAccessTest.php
@@ -654,6 +654,19 @@ function f(array $p): void
'$x3===' => "array{b: 'value'}",
],
],
+ 'possiblyUndefinedArrayOffsetKeyedArray' => [
+ 'code' => ' [
+ '$x===' => '\'a\'',
+ ],
+ 'ignored_issues' => ['PossiblyUndefinedArrayOffset'],
+ ],
'domNodeListAccessible' => [
'code' => ' [],
'ignored_issues' => ['MixedArgument', 'MixedArrayOffset', 'MissingParamType'],
],
- 'suppressPossiblyUndefinedStringArrayOffet' => [
+ 'suppressPossiblyUndefinedStringArrayOffset' => [
'code' => ' 'InvalidArrayOffset',
],
- 'possiblyUndefinedIntArrayOffet' => [
+ 'possiblyUndefinedIntArrayOffset' => [
'code' => ' 'PossiblyUndefinedArrayOffset',
],
- 'possiblyUndefinedStringArrayOffet' => [
+ 'possiblyUndefinedStringArrayOffset' => [
'code' => ' 0.5, "b" => 1.5, "c" => new Exception()]);',
'error_message' => 'InvalidArgument',
],
+ 'possiblyUndefinedArrayOffsetKeyedArray' => [
+ 'code' => ' 'PossiblyUndefinedArrayOffset',
+ ],
];
}
}
diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php
index e63636dff43..4d997070211 100644
--- a/tests/FunctionCallTest.php
+++ b/tests/FunctionCallTest.php
@@ -1937,7 +1937,7 @@ function badpattern() {
'strposAllowDictionary' => [
'code' => ' [
@@ -2138,7 +2138,7 @@ function foo(A $a1, A $a2 = null): void
'strposFirstParamAllowClassString' => [
'code' => ' [
diff --git a/tests/ImmutableAnnotationTest.php b/tests/ImmutableAnnotationTest.php
index da36f660df9..07994b59b44 100644
--- a/tests/ImmutableAnnotationTest.php
+++ b/tests/ImmutableAnnotationTest.php
@@ -300,7 +300,7 @@ public function getError(): ?string {
$dto = new DTO("BOOM!");
- if ($dto->getError()) {
+ if ($dto->getError() !== null) {
takesString($dto->getError());
}',
],
diff --git a/tests/JsonOutputTest.php b/tests/JsonOutputTest.php
index c20d7b9d721..0fefa3f6dec 100644
--- a/tests/JsonOutputTest.php
+++ b/tests/JsonOutputTest.php
@@ -138,6 +138,7 @@ function fooFoo() {
],
'singleIssueForTypeDifference' => [
'code' => ' 1,
'message' => 'Operand of type non-falsy-string is always truthy',
- 'line' => 4,
+ 'line' => 5,
'error' => '$b',
],
];
diff --git a/tests/Loop/DoTest.php b/tests/Loop/DoTest.php
index 5c125de7fac..0c703985b25 100644
--- a/tests/Loop/DoTest.php
+++ b/tests/Loop/DoTest.php
@@ -245,7 +245,7 @@ function bar(?string &$i) : void {}
$c = null;
do {
- if (!$c) {
+ if ($c === null || $c === "" || $c === "0") {
foo($c);
} else {
bar($c);
diff --git a/tests/Loop/WhileTest.php b/tests/Loop/WhileTest.php
index 73dd8085bdf..3056bdc8660 100644
--- a/tests/Loop/WhileTest.php
+++ b/tests/Loop/WhileTest.php
@@ -155,7 +155,7 @@ function foo(): ?A {
}
while ($a = foo()) {
- if ($a->bar) {}
+ if ($a->bar !== null) {}
}',
],
'whileTrueWithBreak' => [
@@ -271,7 +271,7 @@ function bar(?string &$i) : void {}
$c = null;
while (rand(0, 1)) {
- if (!$c) {
+ if ($c === null || $c === "" || $c === "0") {
foo($c);
} else {
bar($c);
diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php
index 9b8ba5b72b2..4c411170272 100644
--- a/tests/MethodCallTest.php
+++ b/tests/MethodCallTest.php
@@ -149,7 +149,7 @@ function printInt(int $int): void {
$obj = new SomeClass();
- if ($obj->getInt()) {
+ if ($obj->getInt() !== null) {
printInt($obj->getInt());
}',
);
@@ -185,7 +185,7 @@ function printInt(int $int): void {
$obj = new SomeClass();
- if ($obj->getInt()) {
+ if ($obj->getInt() !== null) {
printInt($obj->getInt());
}',
);
@@ -936,7 +936,7 @@ final public function getA() {
$a = new A();
- if ($a->getA()) {
+ if ($a->getA() !== null) {
echo strlen($a->getA());
}',
],
@@ -1007,7 +1007,7 @@ function printInt(int $int): void {
$obj = new SomeClass();
- if ($obj->getInt()) {
+ if ($obj->getInt() !== null) {
printInt($obj->getInt());
}',
],
@@ -1031,7 +1031,7 @@ function printInt(int $int): void {
$obj = new SomeClass();
- if ($obj->getInt()) {
+ if ($obj->getInt() !== null) {
printInt($obj->getInt());
}',
],
@@ -1631,7 +1631,7 @@ function getA() {
}
function foo(A $a) : void {
- if ($a->getA()) {
+ if ($a->getA() !== null) {
echo strlen($a->getA());
}
}
@@ -1697,7 +1697,7 @@ function printInt(int $int): void {
$obj = new SomeClass();
- if ($obj->getInt()) {
+ if ($obj->getInt() !== null) {
printInt($obj->getInt());
}',
'error_message' => 'PossiblyNullArgument',
diff --git a/tests/MethodSignatureTest.php b/tests/MethodSignatureTest.php
index a740cf0f4a9..e973c0f7e77 100644
--- a/tests/MethodSignatureTest.php
+++ b/tests/MethodSignatureTest.php
@@ -311,7 +311,7 @@ public function foo(string $s): ?string {
class B extends A {
public function foo(?string $s): string {
- return $s ?: "hello";
+ return $s !== null ? $s : "hello";
}
}
@@ -327,7 +327,7 @@ public function foo(string $s): string {
class B extends A {
public function foo(string $s = null): string {
- return $s ?: "hello";
+ return $s !== null ? $s : "hello";
}
}
@@ -1044,7 +1044,7 @@ public function fooFoo(int $a, bool $c): void {
'code' => 'getX()) {
+ if (is_int($x->getX())) {
XCollector::modify();
if ($x->getX() === null) {}
}
@@ -221,7 +221,7 @@ public function getX() : ?int {
}
function testX(X $x): void {
- if ($x->getX()) {
+ if ($x->getX() !== null) {
XCollector::modify();
if ($x->getX() === null) {}
}
@@ -255,7 +255,7 @@ public function __construct(?int $x) {
}
function testX(X $x): void {
- if ($x->x) {
+ if ($x->x !== null) {
XCollector::modify();
if ($x->x === null) {}
}
@@ -686,6 +686,8 @@ class A {
}
echo substr($a->aa, 1);',
+ 'assertions' => [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'nullableStaticPropertyWithIfCheck' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'returnTypeNotEmptyCheckInElseIf' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'returnTypeNotEmptyCheckInElse' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'returnTypeAfterIf' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'noCrashTemplateInsideGenerator' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'assignmentInIf' => [
'code' => ' 5) {
- } elseif (($a = rand(0, 1) ? new A : null) && $a->foo) {}',
+ } elseif (($a = rand(0, 1) ? new A : null) && is_string($a->foo)) {}',
],
'noParadoxAfterConditionalAssignment' => [
'code' => ' 'InvalidReturnStatement',
- 'ignored_issues' => [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
'php_version' => '8.0',
],
'assignmentInBranchOfAndReferencedAfterIf' => [
diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php
index 700ba17c621..4d48add4f1d 100644
--- a/tests/TypeReconciliation/ConditionalTest.php
+++ b/tests/TypeReconciliation/ConditionalTest.php
@@ -38,6 +38,32 @@ function foo($a): void {
if ($b === $a) { }
}',
],
+ 'nonStrictConditionTruthyFalsyNoOverlap' => [
+ 'code' => ' [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'numericStringAssertion' => [
'code' => ' [
'code' => ' [
'code' => ' 5) {}
}
}',
+ 'assertions' => [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'arrayUnionTypeSwitching' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'propertySetOnElementInConditional' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'issetAssertionOnStaticProperty' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'nonEmptyStringAfterLiteralCheck' => [
'code' => ' 'TypeDoesNotContainType',
],
+ 'nonStrictConditionTruthyFalsy' => [
+ 'code' => ' 'RiskyTruthyFalsyComparison',
+ ],
+ 'nonStrictConditionTruthyFalsyNegated' => [
+ 'code' => ' 'RiskyTruthyFalsyComparison',
+ ],
+ 'nonStrictConditionTruthyFalsyFuncCall' => [
+ 'code' => ' 'RiskyTruthyFalsyComparison',
+ ],
+ 'nonStrictConditionTruthyFalsyFuncCallNegated' => [
+ 'code' => ' 'RiskyTruthyFalsyComparison',
+ ],
'redundantConditionForNonEmptyString' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'emptyExceptionReconciliationAfterIf' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'alwaysBoolResult' => [
'code' => ' [],
- 'ignored_issues' => ['MixedAssignment', 'MissingParamType', 'MixedArgument'],
+ 'ignored_issues' => ['MixedAssignment', 'MissingParamType', 'MixedArgument', 'RiskyTruthyFalsyComparison'],
],
'multipleEmptiesInCondition' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'reconcileEmptyTwiceWithoutReturn' => [
'code' => ' 'string',
],
],
+ 'emptyLiteralTrueFalse' => [
+ 'code' => ' [
+ '$x===' => 'true',
+ ],
+ ],
];
}
@@ -720,6 +734,30 @@ function nonEmptyString(string $str): string {
}',
'error_message' => 'LessSpecificReturnStatement',
],
+ 'impossibleEmptyOnFalsyFunctionCall' => [
+ 'code' => ' 'DocblockTypeContradiction',
+ ],
+ 'redundantEmptyOnFalsyFunctionCall' => [
+ 'code' => ' 'RedundantConditionGivenDocblockType',
+ ],
];
}
}
diff --git a/tests/TypeReconciliation/IssetTest.php b/tests/TypeReconciliation/IssetTest.php
index f9d24846e10..9ca14e2ecf4 100644
--- a/tests/TypeReconciliation/IssetTest.php
+++ b/tests/TypeReconciliation/IssetTest.php
@@ -409,7 +409,7 @@ function foo(array $arr, string $k) : void {
$a = isset($_GET["a"]) ? $_GET["a"] : "";
if ($a) {}',
'assertions' => [],
- 'ignored_issues' => ['MixedAssignment', 'MixedArrayAccess'],
+ 'ignored_issues' => ['MixedAssignment', 'MixedArrayAccess', 'RiskyTruthyFalsyComparison'],
],
'mixedArrayIssetGetStringVar' => [
'code' => ' [],
+ 'ignored_issues' => [
+ 'RiskyTruthyFalsyComparison',
+ ],
],
'noRedundantConditionAfterAssignment' => [
'code' => 'foo) {}
+ if ($i->foo !== null) {}
break;
default:
@@ -180,7 +184,7 @@ function makeA() {
}
if ($a) {}',
'assertions' => [],
- 'ignored_issues' => ['MixedAssignment', 'MixedArrayAccess'],
+ 'ignored_issues' => ['MixedAssignment', 'MixedArrayAccess', 'RiskyTruthyFalsyComparison'],
],
'noComplaintWithIsNumericThenIsEmpty' => [
'code' => ' [],
'ignored_issues' => ['MixedAssignment', 'MixedArrayAccess'],
],
@@ -539,7 +543,7 @@ function bar(string $b) : bool {
exit;
}
- if ($i) {}',
+ if ($i !== array() && $i !== "" && $i !== "0") {}',
],
'emptyWithoutKnowingArrayType' => [
'code' => ' ' 'RedundantCondition',
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'refineTypeInMethodCall' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'twoVarLogicNotNestedWithAllPathsReturning' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'twoVarLogicNotNestedWithAssignmentBeforeReturn' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'invertedTwoVarLogicNotNested' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'invertedTwoVarLogicNotNestedWithAssignmentBeforeReturn' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'twoVarLogicNotNestedWithElseifAndNoNegations' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'threeVarLogicNotNestedWithNoRedefinitionsWithClasses' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'threeVarLogicNotNestedAndOrWithNoRedefinitions' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'twoVarLogicNotNestedWithElseifCorrectlyNegatedInElseIf' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'lotsaTruthyStatements' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'cancelOutDifferentStatement' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'moreChecks' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'compareToIntInsideIfCNF' => [
'code' => ' [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'ternaryAssertionOnBool' => [
'code' => ' 'NullableReturnStatement',
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'invertedTwoVarLogicNotNestedWithElseif' => [
'code' => ' 'NullableReturnStatement',
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'threeVarLogicWithElseifAndAnd' => [
'code' => ' 'TypeDoesNotContainType',
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'twoVarLogicNotNestedWithElseifIncorrectlyReinforcedInIf' => [
'code' => ' 'RedundantCondition',
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'repeatedIfStatements' => [
'code' => ' 'TypeDoesNotContainType',
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'repeatedConditionals' => [
'code' => ' 'RedundantCondition',
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
],
'dependentTypeInvalidated' => [
'code' => ' [
@@ -128,10 +128,10 @@ function foo(string $a): void {
'dummyByRefVar' => [
'code' => 'getMessage();
}
- if ($s) {}
+ if ($s !== null) {}
}',
],
'throwWithMessageCallAndAssignmentInCatchAndReference' => [
@@ -940,7 +940,7 @@ function foo() : void {
if ($foo) {}
} catch (Exception $e) {}
- if ($foo) {}',
+ if ($foo !== false && $foo !== 0) {}',
],
'useTryAssignedVariableInsideFinally' => [
'code' => ' [],
- 'ignored_issues' => [],
+ 'ignored_issues' => ['RiskyTruthyFalsyComparison'],
'php_version' => '8.0',
],
'concatWithUnknownProperty' => [
@@ -3165,7 +3165,7 @@ function bar() : void {
$user = $user_id;
}
- if ($user) {
+ if ($user !== null && $user !== 0) {
$a = 0;
for ($i = 1; $i <= 10; $i++) {
$a += $i;
@@ -3185,7 +3185,7 @@ function bar() : void {
$user = $user_id;
}
- if ($user) {
+ if ($user !== null && $user !== 0) {
$a = 0;
foreach ([1, 2, 3] as $i) {
$a += $i;