Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update master from 5.x #10775

Merged
merged 24 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f553392
Fix a false flag issue with InvalidConstantAssignmentValue being thro…
MelechMizrachi Feb 23, 2024
10ed0f3
Set inside_isset false when analyzing ArrayDimFetch dim
edsrzf Feb 26, 2024
3535324
Fix new errors
edsrzf Feb 26, 2024
e9ea999
Merge pull request #10738 from MelechMizrachi/vimeo-fix-long-array-co…
weirdan Feb 26, 2024
8a0bc19
Forbid iterating over generators with non-nullable `send()`
weirdan Feb 12, 2024
27461c9
Strip null used to signify completed iterations in foreach context
weirdan Feb 12, 2024
4eec8bb
Disabled wrong test
weirdan Feb 26, 2024
0ce62a5
Let's see what breaks without this hack
weirdan Feb 26, 2024
a1352eb
Merge pull request #10697 from weirdan/forbid-iterating-over-generato…
weirdan Feb 27, 2024
60badd0
Merge pull request #10752 from edsrzf/array-isset-disable-dim
weirdan Feb 27, 2024
22f32c1
Set inside_isset = false when analyzing arguments
edsrzf Feb 27, 2024
9aa450b
Merge pull request #10753 from edsrzf/arguments-isset-disable
weirdan Feb 27, 2024
52fb472
Fix PHP notice - crash on invalid taint-escape
kkmuffme Feb 29, 2024
8bdf114
Merge pull request #10760 from kkmuffme/fix-crash-invalid-taint-escape
weirdan Feb 29, 2024
97b22ec
Fix version comparison for `@since`
weirdan Mar 1, 2024
496b214
Merge pull request #10764 from weirdan/fix-since-version-comparison
weirdan Mar 1, 2024
e357b97
Avoid duplicating code for RiskyTruthyFalsyComparison
theodorejb Mar 1, 2024
d751c20
fix unrelated Config bug not honoring PHP composer versions 8.2 + 8.3
kkmuffme Mar 2, 2024
a1848a1
@since annotations should only infer PHP version in .phpstub files or…
kkmuffme Mar 2, 2024
d3e070c
Merge pull request #10769 from kkmuffme/since-annotations-outside-php…
weirdan Mar 2, 2024
cabcb02
Merge pull request #10765 from theodorejb/avoid-duplication
weirdan Mar 2, 2024
ceb2908
Initial support for named parameters for callables
weirdan Mar 2, 2024
20e8604
Merge pull request #10772 from weirdan/10766-named-parameters-for-cal…
weirdan Mar 2, 2024
291a671
Merge branch '5.x' into update-master
weirdan Mar 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1513,7 +1513,6 @@
<PossiblyUndefinedIntArrayOffset>
<code><![CDATA[$line_parts[0]]]></code>
<code><![CDATA[$line_parts[1]]]></code>
<code><![CDATA[$since_parts[1]]]></code>
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php">
Expand Down
16 changes: 15 additions & 1 deletion src/Psalm/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -2640,8 +2640,22 @@ public function getPHPVersionFromComposerJson(): ?string
$version_parser = new VersionParser();

$constraint = $version_parser->parseConstraints($php_version);
$php_versions = [
'5.4',
'5.5',
'5.6',
'7.0',
'7.1',
'7.2',
'7.3',
'7.4',
'8.0',
'8.1',
'8.2',
'8.3',
];

foreach (['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] as $candidate) {
foreach ($php_versions as $candidate) {
if ($constraint->matches(new Constraint('<=', "$candidate.0.0-dev"))
|| $constraint->matches(new Constraint('<=', "$candidate.999"))
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,15 +308,6 @@ public static function verifyReturnType(
$source->getParentFQCLN(),
);

// hack until we have proper yield type collection
if ($function_like_storage
&& $function_like_storage->has_yield
&& !$inferred_yield_type
&& !$inferred_return_type->isVoid()
) {
$inferred_return_type = new Union([new TNamedObject('Generator')]);
}

if ($is_to_string) {
$union_comparison_results = new TypeComparisonResult();
if (!$inferred_return_type->hasMixed() &&
Expand Down
55 changes: 52 additions & 3 deletions src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ public static function checkIteratorType(
$key_type,
$value_type,
$has_valid_iterator,
$invalid_iterator_types,
);
} else {
$raw_object_types[] = $iterator_atomic_type->value;
Expand Down Expand Up @@ -720,6 +721,7 @@ public static function checkIteratorType(
return null;
}

/** @param list<string> $invalid_iterator_types */
public static function handleIterable(
StatementsAnalyzer $statements_analyzer,
TNamedObject $iterator_atomic_type,
Expand All @@ -729,6 +731,7 @@ public static function handleIterable(
?Union &$key_type,
?Union &$value_type,
bool &$has_valid_iterator,
array &$invalid_iterator_types = [],
): void {
if ($iterator_atomic_type->extra_types) {
$iterator_atomic_types = [
Expand All @@ -747,9 +750,6 @@ public static function handleIterable(
throw new UnexpectedValueException('Shouldn’t get a generic param here');
}


$has_valid_iterator = true;

if ($iterator_atomic_type instanceof TIterable
|| (strtolower($iterator_atomic_type->value) === 'traversable'
|| $codebase->classImplements(
Expand All @@ -776,6 +776,8 @@ public static function handleIterable(
)
)
) {
$has_valid_iterator = true;

$old_data_provider = $statements_analyzer->node_data;

$statements_analyzer->node_data = clone $statements_analyzer->node_data;
Expand Down Expand Up @@ -862,6 +864,7 @@ public static function handleIterable(
$key_type,
$value_type,
$has_valid_iterator,
$invalid_iterator_types,
);

continue;
Expand Down Expand Up @@ -894,6 +897,51 @@ public static function handleIterable(
$value_type = Type::combineUnionTypes($value_type, $value_type_part);
}
}
} elseif ($iterator_atomic_type instanceof TGenericObject
&& strtolower($iterator_atomic_type->value) === 'generator'
) {
$type_params = $iterator_atomic_type->type_params;
if (isset($type_params[2]) && !$type_params[2]->isNullable() && !$type_params[2]->isMixed()) {
$invalid_iterator_types[] = $iterator_atomic_type->getKey();
} else {
$has_valid_iterator = true;
}

$iterator_value_type = self::getFakeMethodCallType(
$statements_analyzer,
$foreach_expr,
$context,
'current',
);

$iterator_key_type = self::getFakeMethodCallType(
$statements_analyzer,
$foreach_expr,
$context,
'key',
);

if ($iterator_value_type && !$iterator_value_type->isMixed()) {
// remove null coming from current() to signify invalid iterations
// we're in a foreach context, so we know we're not going iterate past the end
if (isset($type_params[1]) && !$type_params[1]->isNullable()) {
$iterator_value_type = $iterator_value_type->getBuilder();
$iterator_value_type->removeType('null');
$iterator_value_type = $iterator_value_type->freeze();
}
$value_type = Type::combineUnionTypes($value_type, $iterator_value_type);
}

if ($iterator_key_type && !$iterator_key_type->isMixed()) {
// remove null coming from key() to signify invalid iterations
// we're in a foreach context, so we know we're not going iterate past the end
if (isset($type_params[0]) && !$type_params[0]->isNullable()) {
$iterator_key_type = $iterator_key_type->getBuilder();
$iterator_key_type->removeType('null');
$iterator_key_type = $iterator_key_type->freeze();
}
$key_type = Type::combineUnionTypes($key_type, $iterator_key_type);
}
} elseif ($codebase->classImplements(
$iterator_atomic_type->value,
'Iterator',
Expand All @@ -906,6 +954,7 @@ public static function handleIterable(
)
)
) {
$has_valid_iterator = true;
$iterator_value_type = self::getFakeMethodCallType(
$statements_analyzer,
$foreach_expr,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@
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;
use function array_filter;
use function array_keys;
use function array_key_first;
use function array_merge;
use function array_values;
use function count;
Expand Down Expand Up @@ -82,7 +80,7 @@ public static function analyze(
$entry_clauses,
static fn(Clause $c): bool => count($c->possibilities) > 1
|| $c->wedge
|| !isset($changed_var_ids[array_keys($c->possibilities)[0]])
|| !isset($changed_var_ids[array_key_first($c->possibilities)])
),
);
}
Expand Down Expand Up @@ -373,33 +371,7 @@ public static function handleParadoxicalCondition(
} 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) {
$has_truthy_or_falsy_exclusive_type = false;
$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);
$has_truthy_or_falsy_exclusive_type = true;
}
}

if (count($both_types->getAtomicTypes()) > 0 && $has_truthy_or_falsy_exclusive_type) {
$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(),
);
}
}
ExpressionAnalyzer::checkRiskyTruthyFalsyComparison($type, $statements_analyzer, $stmt);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@
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
*/
Expand Down Expand Up @@ -47,34 +42,7 @@ public static function analyze(
} elseif ($expr_type->isAlwaysFalsy()) {
$stmt_type = new TTrue($expr_type->from_docblock);
} else {
if (count($expr_type->getAtomicTypes()) > 1) {
$has_truthy_or_falsy_exclusive_type = false;
$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);
$has_truthy_or_falsy_exclusive_type = true;
}
}

if (count($both_types->getAtomicTypes()) > 0 && $has_truthy_or_falsy_exclusive_type) {
$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(),
);
}
}

ExpressionAnalyzer::checkRiskyTruthyFalsyComparison($expr_type, $statements_analyzer, $stmt);
$stmt_type = new TBool();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ public static function analyze(
$was_inside_call = $context->inside_call;
$context->inside_call = true;

$was_inside_isset = $context->inside_isset;
$context->inside_isset = false;

if (ExpressionAnalyzer::analyze(
$statements_analyzer,
$arg->value,
Expand All @@ -240,11 +243,13 @@ public static function analyze(
null,
$high_order_template_result,
) === false) {
$context->inside_isset = $was_inside_isset;
$context->inside_call = $was_inside_call;

return false;
}

$context->inside_isset = $was_inside_isset;
$context->inside_call = $was_inside_call;

if ($high_order_callable_info && $high_order_template_result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,19 +723,27 @@ public static function analyzeAssignment(

// Check assigned type matches docblock type
if ($assigned_type = $statements_analyzer->node_data->getType($const->value)) {
if ($const_storage->type !== null
$const_storage_type = $const_storage->type;

if ($const_storage_type !== null
&& $const_storage->stmt_location !== null
&& $assigned_type !== $const_storage->type
&& $assigned_type !== $const_storage_type
// Check if this type was defined via a dockblock or type hint otherwise the inferred type
// should always match the assigned type and we don't even need to do additional checks
// There is an issue with constants over a certain length where additional values
// are added to fallback_params in the assigned_type but not in const_storage_type
// which causes a false flag for this error to appear. Usually happens with arrays
&& ($const_storage_type->from_docblock || $const_storage_type->from_property)
&& !UnionTypeComparator::isContainedBy(
$statements_analyzer->getCodebase(),
$assigned_type,
$const_storage->type,
$const_storage_type,
)
) {
IssueBuffer::maybeAdd(
new InvalidConstantAssignmentValue(
"{$class_storage->name}::{$const->name->name} with declared type "
. "{$const_storage->type->getId()} cannot be assigned type {$assigned_type->getId()}",
. "{$const_storage_type->getId()} cannot be assigned type {$assigned_type->getId()}",
$const_storage->stmt_location,
"{$class_storage->name}::{$const->name->name}",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@
use PhpParser;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
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
*/
Expand Down Expand Up @@ -66,34 +64,7 @@ public static function analyze(
} elseif ($expr_type->isAlwaysFalsy()) {
$stmt_type = new TTrue($expr_type->from_docblock);
} else {
if (count($expr_type->getAtomicTypes()) > 1) {
$has_truthy_or_falsy_exclusive_type = false;
$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);
$has_truthy_or_falsy_exclusive_type = true;
}
}

if (count($both_types->getAtomicTypes()) > 0 && $has_truthy_or_falsy_exclusive_type) {
$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(),
);
}
}

ExpressionAnalyzer::checkRiskyTruthyFalsyComparison($expr_type, $statements_analyzer, $stmt);
$stmt_type = new TBool();
}

Expand Down
Loading