diff --git a/config.xsd b/config.xsd index 061d84db6..9928e7153 100644 --- a/config.xsd +++ b/config.xsd @@ -160,6 +160,7 @@ + diff --git a/src/Psalm/Checker/Statements/Expression/FetchChecker.php b/src/Psalm/Checker/Statements/Expression/FetchChecker.php index 4659f33a2..c33b61f7a 100644 --- a/src/Psalm/Checker/Statements/Expression/FetchChecker.php +++ b/src/Psalm/Checker/Statements/Expression/FetchChecker.php @@ -27,6 +27,7 @@ use Psalm\Issue\NullPropertyFetch; use Psalm\Issue\NullReference; use Psalm\Issue\ParentNotFound; use Psalm\Issue\PossiblyInvalidArrayAccess; +use Psalm\Issue\PossiblyInvalidPropertyFetch; use Psalm\Issue\PossiblyNullArrayAccess; use Psalm\Issue\PossiblyNullPropertyFetch; use Psalm\Issue\PossiblyNullReference; @@ -167,25 +168,22 @@ class FetchChecker return null; } + $invalid_fetch_types = []; + $has_valid_fetch_type = false; + foreach ($stmt_var_type->types as $lhs_type_part) { if ($lhs_type_part instanceof TNull) { continue; } if (!$lhs_type_part instanceof TNamedObject && !$lhs_type_part instanceof TObject) { - if (IssueBuffer::accepts( - new InvalidPropertyFetch( - 'Cannot fetch property on non-object ' . $stmt_var_id . ' of type ' . $lhs_type_part, - new CodeLocation($statements_checker->getSource(), $stmt) - ), - $statements_checker->getSuppressedIssues() - )) { - // fall through - } + $invalid_fetch_types[] = (string)$lhs_type_part; continue; } + $has_valid_fetch_type = true; + // stdClass and SimpleXMLElement are special cases where we cannot infer the return types // but we don't want to throw an error // Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164 @@ -365,6 +363,32 @@ class FetchChecker } } + if ($invalid_fetch_types) { + $lhs_type_part = $invalid_fetch_types[0]; + + if ($has_valid_fetch_type) { + if (IssueBuffer::accepts( + new PossiblyInvalidPropertyFetch( + 'Cannot fetch property on possible non-object ' . $stmt_var_id . ' of type ' . $lhs_type_part, + new CodeLocation($statements_checker->getSource(), $stmt) + ), + $statements_checker->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new InvalidPropertyFetch( + 'Cannot fetch property on non-object ' . $stmt_var_id . ' of type ' . $lhs_type_part, + new CodeLocation($statements_checker->getSource(), $stmt) + ), + $statements_checker->getSuppressedIssues() + )) { + // fall through + } + } + } + if ($var_id) { $context->vars_in_scope[$var_id] = isset($stmt->inferredType) ? $stmt->inferredType : Type::getMixed(); } diff --git a/src/Psalm/Issue/PossiblyInvalidPropertyFetch.php b/src/Psalm/Issue/PossiblyInvalidPropertyFetch.php new file mode 100644 index 000000000..92d3f7bea --- /dev/null +++ b/src/Psalm/Issue/PossiblyInvalidPropertyFetch.php @@ -0,0 +1,6 @@ +foo;', 'error_message' => 'InvalidPropertyFetch', ], + 'possiblyBadFetch' => [ + ' 3 ? "hello" : new stdClass; + echo $a->foo;', + 'error_message' => 'PossiblyInvalidPropertyFetch', + ], 'mixedPropertyFetch' => [ '