mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Immutable Unions
This commit is contained in:
parent
028ac7f540
commit
3abd0b961f
4
.github/workflows/bcc.yml
vendored
4
.github/workflows/bcc.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -24,7 +24,7 @@ jobs:
|
||||
echo "::set-output name=vcs_cache::$(composer config cache-vcs-dir)"
|
||||
|
||||
- name: Cache composer cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ steps.composer-cache.outputs.files_cache }}
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@
|
||||
/tests/fixtures/symlinktest/*
|
||||
|
||||
.idea/
|
||||
.vscode/
|
||||
|
@ -1,5 +1,14 @@
|
||||
# Upgrading from Psalm 4 to Psalm 5
|
||||
## Changed
|
||||
- [BC] `Psalm\Type\Union`s are now partially immutable, mutator methods were removed and moved into `Psalm\Type\MutableUnion`.
|
||||
To modify a union type, use the new `Psalm\Type\Union::getBuilder` method to turn a `Psalm\Type\Union` into a `Psalm\Type\MutableUnion`: once you're done, use `Psalm\Type\MutableUnion::freeze` to get a new `Psalm\Type\Union`.
|
||||
Methods removed from `Psalm\Type\Union` and moved into `Psalm\Type\MutableUnion`:
|
||||
- `replaceTypes`
|
||||
- `addType`
|
||||
- `removeType`
|
||||
- `substitute`
|
||||
- `replaceClassLike`
|
||||
|
||||
- [BC] TPositiveInt has been removed and replaced by TIntRange
|
||||
|
||||
- [BC] The parameter `$php_version` of `Psalm\Type\Atomic::create()` renamed
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="dev-master@f7fc28bf5c23969ef1d9c8fd26f020ade0a2db9e">
|
||||
<files psalm-version="dev-master@d7e897b0eb65539a8b769a911efdf49925dd4e7d">
|
||||
<file src="examples/TemplateChecker.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="2">
|
||||
<code>$comment_block->tags['variablesfrom'][0]</code>
|
||||
@ -112,6 +112,9 @@
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php">
|
||||
<ComplexMethod occurrences="1">
|
||||
<code>verifyType</code>
|
||||
</ComplexMethod>
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="3">
|
||||
<code>$non_existent_method_ids[0]</code>
|
||||
<code>$parts[1]</code>
|
||||
@ -289,6 +292,14 @@
|
||||
<code>$cs[0]</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php">
|
||||
<LessSpecificReturnStatement occurrences="1">
|
||||
<code>$callable</code>
|
||||
</LessSpecificReturnStatement>
|
||||
<MoreSpecificReturnType occurrences="1">
|
||||
<code>TCallable|TClosure|null</code>
|
||||
</MoreSpecificReturnType>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Type/TypeCombiner.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="6">
|
||||
<code>$combination->array_type_params[1]</code>
|
||||
@ -311,31 +322,6 @@
|
||||
<code>array_keys($template_type_map[$template_param_name])[0]</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Node/Stmt/VirtualClass.php">
|
||||
<PropertyNotSetInConstructor occurrences="1">
|
||||
<code>VirtualClass</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/Psalm/Node/Stmt/VirtualFunction.php">
|
||||
<PropertyNotSetInConstructor occurrences="1">
|
||||
<code>VirtualFunction</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/Psalm/Node/Stmt/VirtualInterface.php">
|
||||
<PropertyNotSetInConstructor occurrences="1">
|
||||
<code>VirtualInterface</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/Psalm/Node/Stmt/VirtualTrait.php">
|
||||
<PropertyNotSetInConstructor occurrences="1">
|
||||
<code>VirtualTrait</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/Psalm/Node/VirtualConst.php">
|
||||
<PropertyNotSetInConstructor occurrences="1">
|
||||
<code>VirtualConst</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/Psalm/Type/Atomic.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="1">
|
||||
<code>array_keys($template_type_map[$value])[0]</code>
|
||||
@ -346,12 +332,36 @@
|
||||
<code>$this->type_params[1]</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Type/Atomic/TTemplatePropertiesOf.php">
|
||||
<PropertyTypeCoercion occurrences="1"/>
|
||||
</file>
|
||||
<file src="src/Psalm/Type/MutableUnion.php">
|
||||
<PossiblyUnusedProperty occurrences="6">
|
||||
<code>$allow_mutations</code>
|
||||
<code>$failed_reconciliation</code>
|
||||
<code>$from_template_default</code>
|
||||
<code>$has_mutations</code>
|
||||
<code>$initialized_class</code>
|
||||
<code>$reference_free</code>
|
||||
</PossiblyUnusedProperty>
|
||||
</file>
|
||||
<file src="src/Psalm/Type/Reconciler.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="2">
|
||||
<code>$type[0]</code>
|
||||
<code>$type[0][0]</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Type/Union.php">
|
||||
<PossiblyUnusedProperty occurrences="1">
|
||||
<code>$ignore_isset</code>
|
||||
</PossiblyUnusedProperty>
|
||||
</file>
|
||||
<file src="src/Psalm/Type/UnionTrait.php">
|
||||
<PossiblyUnusedMethod occurrences="2">
|
||||
<code>allFloatLiterals</code>
|
||||
<code>allFloatLiterals</code>
|
||||
</PossiblyUnusedMethod>
|
||||
</file>
|
||||
<file src="vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php">
|
||||
<PossiblyUndefinedStringArrayOffset occurrences="1">
|
||||
<code>$subNodes['expr']</code>
|
||||
|
@ -485,7 +485,10 @@ final class Context
|
||||
if ((!$new_type || !$old_type->equals($new_type))
|
||||
&& ($new_type || count($existing_type->getAtomicTypes()) > 1)
|
||||
) {
|
||||
$existing_type->substitute($old_type, $new_type);
|
||||
$existing_type = $existing_type
|
||||
->getBuilder()
|
||||
->substitute($old_type, $new_type)
|
||||
->freeze();
|
||||
|
||||
if ($new_type && $new_type->from_docblock) {
|
||||
$existing_type->setFromDocblock();
|
||||
@ -770,18 +773,23 @@ final class Context
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
foreach ($this->vars_in_scope as $var_id => $type) {
|
||||
foreach ($this->vars_in_scope as $var_id => &$type) {
|
||||
if (preg_match('/' . preg_quote($remove_var_id, '/') . '[\]\[\-]/', $var_id)) {
|
||||
$this->remove($var_id, false);
|
||||
}
|
||||
|
||||
$builder = null;
|
||||
foreach ($type->getAtomicTypes() as $atomic_type) {
|
||||
if ($atomic_type instanceof DependentType
|
||||
&& $atomic_type->getVarId() === $remove_var_id
|
||||
) {
|
||||
$type->addType($atomic_type->getReplacement());
|
||||
$builder ??= $type->getBuilder();
|
||||
$builder->addType($atomic_type->getReplacement());
|
||||
}
|
||||
}
|
||||
if ($builder) {
|
||||
$type = $builder->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -791,12 +791,12 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
|
||||
$template_result = new TemplateResult([], $lower_bounds);
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$guide_property_type = TemplateInferredTypeReplacer::replace(
|
||||
$guide_property_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$property_type = TemplateInferredTypeReplacer::replace(
|
||||
$property_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
@ -1294,7 +1294,11 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
);
|
||||
} elseif (!$property_storage->has_default) {
|
||||
if (isset($this->inferred_property_types[$property_name])) {
|
||||
$this->inferred_property_types[$property_name]->addType(new TNull());
|
||||
$this->inferred_property_types[$property_name] =
|
||||
$this->inferred_property_types[$property_name]
|
||||
->getBuilder()
|
||||
->addType(new TNull())
|
||||
->freeze();
|
||||
$this->inferred_property_types[$property_name]->setFromDocblock();
|
||||
}
|
||||
}
|
||||
@ -1543,7 +1547,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
}
|
||||
|
||||
if ($suggested_type && !$property_storage->has_default && $property_storage->is_static) {
|
||||
$suggested_type->addType(new TNull());
|
||||
$suggested_type = $suggested_type->getBuilder()->addType(new TNull())->freeze();
|
||||
}
|
||||
|
||||
if ($suggested_type && !$suggested_type->isNull()) {
|
||||
|
@ -992,8 +992,9 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
|
||||
if ($signature_type && $signature_type_location && $signature_type->hasObjectType()) {
|
||||
$referenced_type = $signature_type;
|
||||
if ($referenced_type->isNullable()) {
|
||||
$referenced_type = clone $referenced_type;
|
||||
$referenced_type = $referenced_type->getBuilder();
|
||||
$referenced_type->removeType('null');
|
||||
$referenced_type = $referenced_type->freeze();
|
||||
}
|
||||
[$start, $end] = $signature_type_location->getSelectionBounds();
|
||||
$codebase->analyzer->addOffsetReference(
|
||||
@ -1844,9 +1845,9 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
|
||||
$this->storage->if_this_is_type
|
||||
);
|
||||
|
||||
foreach ($context->vars_in_scope as $var_name => $var_type) {
|
||||
foreach ($context->vars_in_scope as $var_name => &$var_type) {
|
||||
if (0 === mb_strpos($var_name, '$this->')) {
|
||||
TemplateInferredTypeReplacer::replace($var_type, $template_result, $codebase);
|
||||
$var_type = TemplateInferredTypeReplacer::replace($var_type, $template_result, $codebase);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,7 +362,7 @@ class MethodComparator
|
||||
$guide_param_signature_type = $guide_param->type;
|
||||
|
||||
$or_null_guide_param_signature_type = $guide_param->signature_type
|
||||
? clone $guide_param->signature_type
|
||||
? $guide_param->signature_type->getBuilder()
|
||||
: null;
|
||||
|
||||
if ($or_null_guide_param_signature_type) {
|
||||
@ -729,29 +729,34 @@ class MethodComparator
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($implementer_method_storage_param_type->getAtomicTypes() as $k => $t) {
|
||||
$builder = $implementer_method_storage_param_type->getBuilder();
|
||||
foreach ($builder->getAtomicTypes() as $k => $t) {
|
||||
if ($t instanceof TTemplateParam
|
||||
&& strpos($t->defining_class, 'fn-') === 0
|
||||
) {
|
||||
$implementer_method_storage_param_type->removeType($k);
|
||||
$builder->removeType($k);
|
||||
|
||||
foreach ($t->as->getAtomicTypes() as $as_t) {
|
||||
$implementer_method_storage_param_type->addType($as_t);
|
||||
$builder->addType($as_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
$implementer_method_storage_param_type = $builder->freeze();
|
||||
|
||||
foreach ($guide_method_storage_param_type->getAtomicTypes() as $k => $t) {
|
||||
$builder = $guide_method_storage_param_type->getBuilder();
|
||||
foreach ($builder->getAtomicTypes() as $k => $t) {
|
||||
if ($t instanceof TTemplateParam
|
||||
&& strpos($t->defining_class, 'fn-') === 0
|
||||
) {
|
||||
$guide_method_storage_param_type->removeType($k);
|
||||
$builder->removeType($k);
|
||||
|
||||
foreach ($t->as->getAtomicTypes() as $as_t) {
|
||||
$guide_method_storage_param_type->addType($as_t);
|
||||
$builder->addType($as_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
$guide_method_storage_param_type = $builder->freeze();
|
||||
unset($builder);
|
||||
|
||||
if ($implementer_classlike_storage->template_extended_params) {
|
||||
self::transformTemplates(
|
||||
@ -1055,7 +1060,7 @@ class MethodComparator
|
||||
private static function transformTemplates(
|
||||
array $template_extended_params,
|
||||
string $base_class_name,
|
||||
Union $templated_type,
|
||||
Union &$templated_type,
|
||||
Codebase $codebase
|
||||
): void {
|
||||
if (isset($template_extended_params[$base_class_name])) {
|
||||
@ -1092,7 +1097,7 @@ class MethodComparator
|
||||
|
||||
$template_result = new TemplateResult([], $template_types);
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$templated_type = TemplateInferredTypeReplacer::replace(
|
||||
$templated_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -225,10 +225,10 @@ class ArrayAnalyzer
|
||||
}
|
||||
|
||||
if ($bad_types && $good_types) {
|
||||
$item_key_type->substitute(
|
||||
$item_key_type = $item_key_type->getBuilder()->substitute(
|
||||
TypeCombiner::combine($bad_types, $codebase),
|
||||
TypeCombiner::combine($good_types, $codebase)
|
||||
);
|
||||
)->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,7 +218,9 @@ class ArrayAssignmentAnalyzer
|
||||
$new_child_type = $root_type;
|
||||
}
|
||||
|
||||
$new_child_type = $new_child_type->getBuilder();
|
||||
$new_child_type->removeType('null');
|
||||
$new_child_type = $new_child_type->freeze();
|
||||
|
||||
if (!$root_type->hasObjectType()) {
|
||||
$root_type = $new_child_type;
|
||||
@ -295,7 +297,7 @@ class ArrayAssignmentAnalyzer
|
||||
$has_matching_objectlike_property = false;
|
||||
$has_matching_string = false;
|
||||
|
||||
$child_stmt_type = clone $child_stmt_type;
|
||||
$child_stmt_type = $child_stmt_type->getBuilder();
|
||||
|
||||
foreach ($child_stmt_type->getAtomicTypes() as $type) {
|
||||
if ($type instanceof TTemplateParam) {
|
||||
@ -351,7 +353,7 @@ class ArrayAssignmentAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$child_stmt_type->bustCache();
|
||||
$child_stmt_type = $child_stmt_type->freeze();
|
||||
|
||||
if (!$has_matching_objectlike_property && !$has_matching_string) {
|
||||
if (count($key_values) === 1) {
|
||||
@ -511,9 +513,7 @@ class ArrayAssignmentAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$array_atomic_key_type = ArrayFetchAnalyzer::replaceOffsetTypeWithInts(
|
||||
$key_type
|
||||
);
|
||||
$array_atomic_key_type = ArrayFetchAnalyzer::replaceOffsetTypeWithInts($key_type);
|
||||
} else {
|
||||
$array_atomic_key_type = Type::getArrayKey();
|
||||
}
|
||||
@ -557,7 +557,7 @@ class ArrayAssignmentAnalyzer
|
||||
]
|
||||
);
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$value_type = TemplateInferredTypeReplacer::replace(
|
||||
$value_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
@ -742,17 +742,21 @@ class ArrayAssignmentAnalyzer
|
||||
|
||||
$is_last = $i === count($child_stmts) - 1;
|
||||
|
||||
$child_stmt_dim_type_or_int = $child_stmt_dim_type ?? Type::getInt();
|
||||
$child_stmt_type = ArrayFetchAnalyzer::getArrayAccessTypeGivenOffset(
|
||||
$statements_analyzer,
|
||||
$child_stmt,
|
||||
$array_type,
|
||||
$child_stmt_dim_type ?? Type::getInt(),
|
||||
$child_stmt_dim_type_or_int,
|
||||
true,
|
||||
$extended_var_id,
|
||||
$context,
|
||||
$assign_value,
|
||||
!$is_last ? null : $assignment_type
|
||||
);
|
||||
if ($child_stmt->dim) {
|
||||
$statements_analyzer->node_data->setType($child_stmt->dim, $child_stmt_dim_type_or_int);
|
||||
}
|
||||
|
||||
$statements_analyzer->node_data->setType(
|
||||
$child_stmt,
|
||||
@ -886,8 +890,10 @@ class ArrayAssignmentAnalyzer
|
||||
);
|
||||
}
|
||||
|
||||
$new_child_type = $new_child_type->getBuilder();
|
||||
$new_child_type->removeType('null');
|
||||
$new_child_type->possibly_undefined = false;
|
||||
$new_child_type = $new_child_type->freeze();
|
||||
|
||||
if (!$child_stmt_type->hasObjectType()) {
|
||||
$child_stmt_type = $new_child_type;
|
||||
|
@ -1561,7 +1561,10 @@ class AssignmentAnalyzer
|
||||
if (($context->error_suppressing && ($offset || $can_be_empty))
|
||||
|| $has_null
|
||||
) {
|
||||
$context->vars_in_scope[$list_var_id]->addType(new TNull);
|
||||
$context->vars_in_scope[$list_var_id] = $context->vars_in_scope[$list_var_id]
|
||||
->getBuilder()
|
||||
->addType(new TNull)
|
||||
->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ use Psalm\Type\Atomic\TNonEmptyNonspecificLiteralString;
|
||||
use Psalm\Type\Atomic\TNonEmptyString;
|
||||
use Psalm\Type\Atomic\TNonspecificLiteralString;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Union;
|
||||
@ -189,9 +190,11 @@ class ConcatAnalyzer
|
||||
}
|
||||
|
||||
if (!$literal_concat) {
|
||||
$numeric_type = Type::getNumericString();
|
||||
$numeric_type->addType(new TInt());
|
||||
$numeric_type->addType(new TFloat());
|
||||
$numeric_type = new Union([
|
||||
new TNumericString,
|
||||
new TInt,
|
||||
new TFloat
|
||||
]);
|
||||
$left_is_numeric = UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$left_type,
|
||||
@ -212,8 +215,7 @@ class ConcatAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$lowercase_type = clone $numeric_type;
|
||||
$lowercase_type->addType(new TLowercaseString());
|
||||
$lowercase_type = $numeric_type->getBuilder()->addType(new TLowercaseString())->freeze();
|
||||
|
||||
$all_lowercase = UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
@ -225,8 +227,7 @@ class ConcatAnalyzer
|
||||
$lowercase_type
|
||||
);
|
||||
|
||||
$non_empty_string = clone $numeric_type;
|
||||
$non_empty_string->addType(new TNonEmptyString());
|
||||
$non_empty_string = $numeric_type->getBuilder()->addType(new TNonEmptyString())->freeze();
|
||||
|
||||
$has_non_empty = UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
|
@ -44,6 +44,7 @@ class BitwiseNotAnalyzer
|
||||
$unacceptable_type = null;
|
||||
$has_valid_operand = false;
|
||||
|
||||
$stmt_expr_type = $stmt_expr_type->getBuilder();
|
||||
foreach ($stmt_expr_type->getAtomicTypes() as $type_string => $type_part) {
|
||||
if ($type_part instanceof TInt || $type_part instanceof TString) {
|
||||
if ($type_part instanceof TLiteralInt) {
|
||||
|
@ -834,6 +834,7 @@ class ArgumentAnalyzer
|
||||
if ($param_type->hasCallableType() && $param_type->isSingle()) {
|
||||
// we do this replacement early because later we don't have access to the
|
||||
// $statements_analyzer, which is necessary to understand string function names
|
||||
$input_type = $input_type->getBuilder();
|
||||
foreach ($input_type->getAtomicTypes() as $key => $atomic_type) {
|
||||
if (!$atomic_type instanceof TLiteralString
|
||||
|| InternalCallMapHandler::inCallMap($atomic_type->value)
|
||||
@ -854,6 +855,7 @@ class ArgumentAnalyzer
|
||||
$input_type->addType($candidate_callable);
|
||||
}
|
||||
}
|
||||
$input_type = $input_type->freeze();
|
||||
}
|
||||
|
||||
$union_comparison_results = new TypeComparisonResult();
|
||||
@ -1384,9 +1386,10 @@ class ArgumentAnalyzer
|
||||
$was_cloned = false;
|
||||
|
||||
if ($input_type->isNullable() && !$param_type->isNullable()) {
|
||||
$input_type = clone $input_type;
|
||||
$input_type = $input_type->getBuilder();
|
||||
$was_cloned = true;
|
||||
$input_type->removeType('null');
|
||||
$input_type = $input_type->freeze();
|
||||
}
|
||||
|
||||
if ($input_type->getId() === $param_type->getId()) {
|
||||
|
@ -494,7 +494,7 @@ class ArgumentsAnalyzer
|
||||
// The map function expects callable(A):B as second param
|
||||
// We know that previous arg type is list<int> where the int is the A template.
|
||||
// Then we can replace callable(A): B to callable(int):B using $inferred_template_result.
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$replaced_container_hof_atomic = TemplateInferredTypeReplacer::replace(
|
||||
$replaced_container_hof_atomic,
|
||||
$inferred_template_result,
|
||||
$codebase
|
||||
@ -601,7 +601,7 @@ class ArgumentsAnalyzer
|
||||
$context->calling_method_id ?: $context->calling_function_id
|
||||
);
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$replaced_type = TemplateInferredTypeReplacer::replace(
|
||||
$replaced_type,
|
||||
$replace_template_result,
|
||||
$codebase
|
||||
@ -1234,7 +1234,7 @@ class ArgumentsAnalyzer
|
||||
);
|
||||
|
||||
if ($template_result->lower_bounds) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$original_by_ref_type = TemplateInferredTypeReplacer::replace(
|
||||
$original_by_ref_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
@ -1259,7 +1259,7 @@ class ArgumentsAnalyzer
|
||||
);
|
||||
|
||||
if ($template_result->lower_bounds) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$original_by_ref_out_type = TemplateInferredTypeReplacer::replace(
|
||||
$original_by_ref_out_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
@ -1386,16 +1386,18 @@ class ArgumentsAnalyzer
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
foreach ($context->vars_in_scope[$var_id]->getAtomicTypes() as $type) {
|
||||
$t = $context->vars_in_scope[$var_id]->getBuilder();
|
||||
foreach ($t->getAtomicTypes() as $type) {
|
||||
if ($type instanceof TArray && $type->isEmptyArray()) {
|
||||
$context->vars_in_scope[$var_id]->removeType('array');
|
||||
$context->vars_in_scope[$var_id]->addType(
|
||||
$t->removeType('array');
|
||||
$t->addType(
|
||||
new TArray(
|
||||
[Type::getArrayKey(), Type::getMixed()]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
$context->vars_in_scope[$var_id] = $t->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +266,7 @@ class ArrayFunctionArgumentsAnalyzer
|
||||
new Union([new TArray([$new_offset_type, Type::getMixed()])])
|
||||
);
|
||||
} elseif ($arg->unpack) {
|
||||
$arg_value_type = clone $arg_value_type;
|
||||
$arg_value_type = $arg_value_type->getBuilder();
|
||||
|
||||
foreach ($arg_value_type->getAtomicTypes() as $arg_value_atomic_type) {
|
||||
if ($arg_value_atomic_type instanceof TKeyedArray) {
|
||||
@ -285,6 +285,7 @@ class ArrayFunctionArgumentsAnalyzer
|
||||
$arg_value_type->addType($arg_value_atomic_type);
|
||||
}
|
||||
}
|
||||
$arg_value_type = $arg_value_type->freeze();
|
||||
|
||||
$by_ref_type = Type::combineUnionTypes(
|
||||
$by_ref_type,
|
||||
@ -508,7 +509,7 @@ class ArrayFunctionArgumentsAnalyzer
|
||||
$context->removeVarFromConflictingClauses($var_id, null, $statements_analyzer);
|
||||
|
||||
if (isset($context->vars_in_scope[$var_id])) {
|
||||
$array_type = clone $context->vars_in_scope[$var_id];
|
||||
$array_type = $context->vars_in_scope[$var_id]->getBuilder();
|
||||
|
||||
$array_atomic_types = $array_type->getAtomicTypes();
|
||||
|
||||
@ -574,6 +575,7 @@ class ArrayFunctionArgumentsAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$array_type = $array_type->freeze();
|
||||
$context->removeDescendents($var_id, $array_type);
|
||||
$context->vars_in_scope[$var_id] = $array_type;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
@ -172,7 +173,7 @@ class FunctionCallReturnTypeFetcher
|
||||
null
|
||||
);
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$return_type = TemplateInferredTypeReplacer::replace(
|
||||
$return_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
@ -501,8 +502,10 @@ class FunctionCallReturnTypeFetcher
|
||||
break;
|
||||
|
||||
case 'fgetcsv':
|
||||
$string_type = Type::getString();
|
||||
$string_type->addType(new TNull);
|
||||
$string_type = new Union([
|
||||
new TString,
|
||||
new TNull
|
||||
]);
|
||||
$string_type->ignore_nullable_issues = true;
|
||||
|
||||
$call_map_return_type = new Union([
|
||||
@ -609,10 +612,8 @@ class FunctionCallReturnTypeFetcher
|
||||
$conditionally_removed_taints = [];
|
||||
|
||||
foreach ($function_storage->conditionally_removed_taints as $conditionally_removed_taint) {
|
||||
$conditionally_removed_taint = clone $conditionally_removed_taint;
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$conditionally_removed_taint,
|
||||
$conditionally_removed_taint = TemplateInferredTypeReplacer::replace(
|
||||
clone $conditionally_removed_taint,
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
|
@ -300,9 +300,11 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer
|
||||
if ($method_storage) {
|
||||
if ($method_storage->if_this_is_type) {
|
||||
$class_type = new Union([$lhs_type_part]);
|
||||
$if_this_is_type = clone $method_storage->if_this_is_type;
|
||||
|
||||
TemplateInferredTypeReplacer::replace($if_this_is_type, $template_result, $codebase);
|
||||
$if_this_is_type = TemplateInferredTypeReplacer::replace(
|
||||
clone $method_storage->if_this_is_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
|
||||
if (!UnionTypeComparator::isContainedBy($codebase, $class_type, $if_this_is_type)) {
|
||||
IssueBuffer::maybeAdd(
|
||||
|
@ -618,7 +618,7 @@ class MethodCallReturnTypeFetcher
|
||||
null
|
||||
);
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate = TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -149,7 +149,7 @@ class MissingMethodCallHandler
|
||||
$return_type_candidate = clone $pseudo_method_storage->return_type;
|
||||
|
||||
if ($found_generic_params) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate = TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate,
|
||||
new TemplateResult([], $found_generic_params),
|
||||
$codebase
|
||||
@ -315,7 +315,7 @@ class MissingMethodCallHandler
|
||||
$return_type_candidate = clone $pseudo_method_storage->return_type;
|
||||
|
||||
if ($found_generic_params) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate = TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate,
|
||||
new TemplateResult([], $found_generic_params),
|
||||
$codebase
|
||||
|
@ -400,7 +400,7 @@ class MethodCallAnalyzer extends CallAnalyzer
|
||||
) {
|
||||
$keys_to_remove = [];
|
||||
|
||||
$class_type = clone $class_type;
|
||||
$class_type = $class_type->getBuilder();
|
||||
|
||||
foreach ($class_type->getAtomicTypes() as $key => $type) {
|
||||
if (!$type instanceof TNamedObject) {
|
||||
@ -418,7 +418,7 @@ class MethodCallAnalyzer extends CallAnalyzer
|
||||
|
||||
$context->removeVarFromConflictingClauses($lhs_var_id, null, $statements_analyzer);
|
||||
|
||||
$context->vars_in_scope[$lhs_var_id] = $class_type;
|
||||
$context->vars_in_scope[$lhs_var_id] = $class_type->freeze();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -298,10 +298,8 @@ class StaticCallAnalyzer extends CallAnalyzer
|
||||
|
||||
if ($method_storage && $template_result) {
|
||||
foreach ($method_storage->conditionally_removed_taints as $conditionally_removed_taint) {
|
||||
$conditionally_removed_taint = clone $conditionally_removed_taint;
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$conditionally_removed_taint,
|
||||
$conditionally_removed_taint = TemplateInferredTypeReplacer::replace(
|
||||
clone $conditionally_removed_taint,
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
|
@ -469,13 +469,13 @@ class AtomicStaticCallAnalyzer
|
||||
$tGenericMixin,
|
||||
$class_storage,
|
||||
$mixin_declaring_class_storage
|
||||
);
|
||||
)->getBuilder();
|
||||
|
||||
foreach ($mixin_candidate_type->getAtomicTypes() as $type) {
|
||||
$new_mixin_candidate_type->addType($type);
|
||||
}
|
||||
|
||||
$mixin_candidate_type = $new_mixin_candidate_type;
|
||||
$mixin_candidate_type = $new_mixin_candidate_type->freeze();
|
||||
}
|
||||
|
||||
$new_lhs_type = TypeExpander::expandUnion(
|
||||
@ -720,7 +720,7 @@ class AtomicStaticCallAnalyzer
|
||||
if (isset($context->vars_in_scope['$this'])
|
||||
&& $method_call_type = $statements_analyzer->node_data->getType($stmt)
|
||||
) {
|
||||
$method_call_type = clone $method_call_type;
|
||||
$method_call_type = $method_call_type->getBuilder();
|
||||
|
||||
foreach ($method_call_type->getAtomicTypes() as $name => $type) {
|
||||
if ($type instanceof TNamedObject && $type->is_static && $type->value === $fq_class_name) {
|
||||
@ -730,7 +730,7 @@ class AtomicStaticCallAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $method_call_type);
|
||||
$statements_analyzer->node_data->setType($stmt, $method_call_type->freeze());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -567,7 +567,7 @@ class ExistingAtomicStaticCallAnalyzer
|
||||
null
|
||||
);
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate = TemplateInferredTypeReplacer::replace(
|
||||
$return_type_candidate,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -756,9 +756,8 @@ class CallAnalyzer
|
||||
$assertion_type_atomic = $assertion_rule->getAtomicType();
|
||||
|
||||
if ($assertion_type_atomic) {
|
||||
$assertion_type = new Union([clone $assertion_type_atomic]);
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$assertion_type,
|
||||
$assertion_type = TemplateInferredTypeReplacer::replace(
|
||||
new Union([clone $assertion_type_atomic]),
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
|
@ -78,7 +78,7 @@ class CastAnalyzer
|
||||
}
|
||||
|
||||
if ($maybe_type->hasBool()) {
|
||||
$casted_type = clone $maybe_type;
|
||||
$casted_type = $maybe_type->getBuilder();
|
||||
if (isset($casted_type->getAtomicTypes()['bool'])) {
|
||||
$casted_type->addType(new TLiteralInt(0));
|
||||
$casted_type->addType(new TLiteralInt(1));
|
||||
@ -95,7 +95,7 @@ class CastAnalyzer
|
||||
$casted_type->removeType('false');
|
||||
|
||||
if ($casted_type->isInt()) {
|
||||
$valid_int_type = $casted_type;
|
||||
$valid_int_type = $casted_type->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,7 @@ use Psalm\Type\Atomic\TTemplateKeyOf;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTemplateParamClass;
|
||||
use Psalm\Type\Atomic\TTrue;
|
||||
use Psalm\Type\MutableUnion;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
@ -271,7 +272,7 @@ class ArrayFetchAnalyzer
|
||||
&& !$const_array_key_type->hasMixed()
|
||||
&& !$stmt_dim_type->hasMixed()
|
||||
) {
|
||||
$new_offset_type = clone $stmt_dim_type;
|
||||
$new_offset_type = $stmt_dim_type->getBuilder();
|
||||
$const_array_key_atomic_types = $const_array_key_type->getAtomicTypes();
|
||||
|
||||
foreach ($new_offset_type->getAtomicTypes() as $offset_key => $offset_atomic_type) {
|
||||
@ -295,6 +296,8 @@ class ArrayFetchAnalyzer
|
||||
$new_offset_type->removeType($offset_key);
|
||||
}
|
||||
}
|
||||
|
||||
$new_offset_type = $new_offset_type->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -456,14 +459,17 @@ class ArrayFetchAnalyzer
|
||||
public static function getArrayAccessTypeGivenOffset(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
||||
Union $array_type,
|
||||
Union $offset_type,
|
||||
Union &$array_type_original,
|
||||
Union &$offset_type_original,
|
||||
bool $in_assignment,
|
||||
?string $extended_var_id,
|
||||
Context $context,
|
||||
PhpParser\Node\Expr $assign_value = null,
|
||||
Union $replacement_type = null
|
||||
): Union {
|
||||
$array_type = $array_type_original->getBuilder();
|
||||
$offset_type = $offset_type_original->getBuilder();
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$has_array_access = false;
|
||||
@ -847,6 +853,9 @@ class ArrayFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$array_type_original = $array_type->freeze();
|
||||
$offset_type_original = $offset_type->freeze();
|
||||
|
||||
if ($array_access_type === null) {
|
||||
// shouldn’t happen, but don’t crash
|
||||
return Type::getMixed();
|
||||
@ -864,7 +873,7 @@ class ArrayFetchAnalyzer
|
||||
}
|
||||
|
||||
private static function checkLiteralIntArrayOffset(
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
Union $expected_offset_type,
|
||||
?string $extended_var_id,
|
||||
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
||||
@ -912,7 +921,7 @@ class ArrayFetchAnalyzer
|
||||
}
|
||||
|
||||
private static function checkLiteralStringArrayOffset(
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
Union $expected_offset_type,
|
||||
?string $extended_var_id,
|
||||
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
||||
@ -961,26 +970,16 @@ class ArrayFetchAnalyzer
|
||||
|
||||
public static function replaceOffsetTypeWithInts(Union $offset_type): Union
|
||||
{
|
||||
$offset_type = $offset_type->getBuilder();
|
||||
$offset_types = $offset_type->getAtomicTypes();
|
||||
|
||||
$cloned = false;
|
||||
|
||||
foreach ($offset_types as $key => $offset_type_part) {
|
||||
if ($offset_type_part instanceof TLiteralString) {
|
||||
if (preg_match('/^(0|[1-9][0-9]*)$/', $offset_type_part->value)) {
|
||||
if (!$cloned) {
|
||||
$offset_type = clone $offset_type;
|
||||
$cloned = true;
|
||||
}
|
||||
$offset_type->addType(new TLiteralInt((int) $offset_type_part->value));
|
||||
$offset_type->removeType($key);
|
||||
}
|
||||
} elseif ($offset_type_part instanceof TBool) {
|
||||
if (!$cloned) {
|
||||
$offset_type = clone $offset_type;
|
||||
$cloned = true;
|
||||
}
|
||||
|
||||
if ($offset_type_part instanceof TFalse) {
|
||||
if (!$offset_type->ignore_falsable_issues) {
|
||||
$offset_type->addType(new TLiteralInt(0));
|
||||
@ -997,7 +996,7 @@ class ArrayFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
return $offset_type;
|
||||
return $offset_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1085,11 +1084,11 @@ class ArrayFetchAnalyzer
|
||||
bool $in_assignment,
|
||||
Atomic &$type,
|
||||
array &$key_values,
|
||||
Union $array_type,
|
||||
MutableUnion $array_type,
|
||||
string $type_string,
|
||||
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
||||
?Union $replacement_type,
|
||||
Union &$offset_type,
|
||||
MutableUnion $offset_type,
|
||||
Atomic $original_type,
|
||||
Codebase $codebase,
|
||||
?string $extended_var_id,
|
||||
@ -1143,7 +1142,7 @@ class ArrayFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$offset_type = self::replaceOffsetTypeWithInts($offset_type);
|
||||
$offset_type = self::replaceOffsetTypeWithInts($offset_type->freeze())->getBuilder();
|
||||
|
||||
if ($type instanceof TList
|
||||
&& (($in_assignment && $stmt->dim)
|
||||
@ -1226,10 +1225,10 @@ class ArrayFetchAnalyzer
|
||||
Codebase $codebase,
|
||||
Context $context,
|
||||
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
||||
Union $array_type,
|
||||
MutableUnion $array_type,
|
||||
?string $extended_var_id,
|
||||
TArray $type,
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
bool $in_assignment,
|
||||
array &$expected_offset_types,
|
||||
?Union &$array_access_type,
|
||||
@ -1241,7 +1240,7 @@ class ArrayFetchAnalyzer
|
||||
if ($type->isEmptyArray()) {
|
||||
$type->type_params[0] = $offset_type->isMixed()
|
||||
? Type::getArrayKey()
|
||||
: $offset_type;
|
||||
: $offset_type->freeze();
|
||||
}
|
||||
} elseif (!$type->isEmptyArray()) {
|
||||
$expected_offset_type = $type->type_params[0]->hasMixed()
|
||||
@ -1278,7 +1277,7 @@ class ArrayFetchAnalyzer
|
||||
} else {
|
||||
$offset_type_contained_by_expected = UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$offset_type,
|
||||
$offset_type->freeze(),
|
||||
$expected_offset_type,
|
||||
true,
|
||||
$offset_type->ignore_falsable_issues,
|
||||
@ -1336,7 +1335,7 @@ class ArrayFetchAnalyzer
|
||||
|
||||
if (UnionTypeComparator::canExpressionTypesBeIdentical(
|
||||
$codebase,
|
||||
$offset_type,
|
||||
$offset_type->freeze(),
|
||||
$expected_offset_type
|
||||
)) {
|
||||
$has_valid_offset = true;
|
||||
@ -1378,7 +1377,7 @@ class ArrayFetchAnalyzer
|
||||
private static function handleArrayAccessOnClassStringMap(
|
||||
Codebase $codebase,
|
||||
TClassStringMap $type,
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
?Union $replacement_type,
|
||||
?Union &$array_access_type
|
||||
): void {
|
||||
@ -1440,7 +1439,7 @@ class ArrayFetchAnalyzer
|
||||
|
||||
$expected_value_param_get = clone $type->value_param;
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$expected_value_param_get = TemplateInferredTypeReplacer::replace(
|
||||
$expected_value_param_get,
|
||||
$template_result_get,
|
||||
$codebase
|
||||
@ -1449,7 +1448,7 @@ class ArrayFetchAnalyzer
|
||||
if ($replacement_type) {
|
||||
$expected_value_param_set = clone $type->value_param;
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$replacement_type = TemplateInferredTypeReplacer::replace(
|
||||
$replacement_type,
|
||||
$template_result_set,
|
||||
$codebase
|
||||
@ -1483,11 +1482,11 @@ class ArrayFetchAnalyzer
|
||||
?Union &$array_access_type,
|
||||
bool $in_assignment,
|
||||
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
?string $extended_var_id,
|
||||
Context $context,
|
||||
TKeyedArray $type,
|
||||
Union $array_type,
|
||||
MutableUnion $array_type,
|
||||
array &$expected_offset_types,
|
||||
string $type_string,
|
||||
bool &$has_valid_offset
|
||||
@ -1589,7 +1588,7 @@ class ArrayFetchAnalyzer
|
||||
|
||||
$is_contained = UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$offset_type,
|
||||
$offset_type->freeze(),
|
||||
$key_type,
|
||||
true,
|
||||
$offset_type->ignore_falsable_issues,
|
||||
@ -1600,7 +1599,7 @@ class ArrayFetchAnalyzer
|
||||
$is_contained = UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$key_type,
|
||||
$offset_type,
|
||||
$offset_type->freeze(),
|
||||
true,
|
||||
$offset_type->ignore_falsable_issues
|
||||
);
|
||||
@ -1620,7 +1619,7 @@ class ArrayFetchAnalyzer
|
||||
|
||||
$new_key_type = Type::combineUnionTypes(
|
||||
$generic_key_type,
|
||||
$offset_type->isMixed() ? Type::getArrayKey() : $offset_type
|
||||
$offset_type->isMixed() ? Type::getArrayKey() : $offset_type->freeze()
|
||||
);
|
||||
|
||||
$property_count = $type->sealed ? count($type->properties) : null;
|
||||
@ -1682,7 +1681,7 @@ class ArrayFetchAnalyzer
|
||||
Codebase $codebase,
|
||||
PhpParser\Node\Expr\ArrayDimFetch $stmt,
|
||||
TList $type,
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
?string $extended_var_id,
|
||||
array $key_values,
|
||||
Context $context,
|
||||
@ -1897,7 +1896,7 @@ class ArrayFetchAnalyzer
|
||||
Context $context,
|
||||
?Union $replacement_type,
|
||||
TString $type,
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
array &$expected_offset_types,
|
||||
?Union &$array_access_type,
|
||||
bool &$has_valid_offset
|
||||
@ -1960,7 +1959,7 @@ class ArrayFetchAnalyzer
|
||||
|
||||
if (!UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$offset_type,
|
||||
$offset_type->freeze(),
|
||||
$valid_offset_type,
|
||||
true
|
||||
)) {
|
||||
@ -1981,7 +1980,7 @@ class ArrayFetchAnalyzer
|
||||
* @param Atomic[] $offset_types
|
||||
*/
|
||||
private static function checkArrayOffsetType(
|
||||
Union $offset_type,
|
||||
MutableUnion $offset_type,
|
||||
array $offset_types,
|
||||
Codebase $codebase
|
||||
): bool {
|
||||
|
@ -743,7 +743,7 @@ class AtomicPropertyFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$class_property_type = TemplateInferredTypeReplacer::replace(
|
||||
$class_property_type,
|
||||
new TemplateResult([], $template_types),
|
||||
$codebase
|
||||
|
@ -262,7 +262,8 @@ class InstancePropertyFetchAnalyzer
|
||||
$stmt_type = $statements_analyzer->node_data->getType($stmt);
|
||||
|
||||
if ($stmt_var_type->isNullable() && !$context->inside_isset && $stmt_type) {
|
||||
$stmt_type->addType(new TNull);
|
||||
$stmt_type = $stmt_type->getBuilder()->addType(new TNull)->freeze();
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
|
||||
if ($stmt_var_type->ignore_nullable_issues) {
|
||||
$stmt_type->ignore_nullable_issues = true;
|
||||
@ -388,7 +389,10 @@ class InstancePropertyFetchAnalyzer
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
);
|
||||
|
||||
$stmt_type->addType(new TNull);
|
||||
$stmt_type = $stmt_type->getBuilder()->addType(new TNull)->freeze();
|
||||
|
||||
$context->vars_in_scope[$var_id] = $stmt_type;
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ class SimpleTypeInferer
|
||||
return null;
|
||||
}
|
||||
|
||||
$invalidTypes = clone $stmt_expr_type;
|
||||
$invalidTypes = $stmt_expr_type->getBuilder();
|
||||
$invalidTypes->removeType('string');
|
||||
$invalidTypes->removeType('int');
|
||||
$invalidTypes->removeType('float');
|
||||
|
@ -177,7 +177,7 @@ class YieldAnalyzer
|
||||
}
|
||||
|
||||
if ($yield_type) {
|
||||
$expression_type->substitute($expression_type, $yield_type);
|
||||
$expression_type = $expression_type->getBuilder()->substitute($expression_type, $yield_type)->freeze();
|
||||
}
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $expression_type);
|
||||
|
@ -284,10 +284,8 @@ class ReturnAnalyzer
|
||||
unset($found_generic_params[$template_name][$fq_class_name]);
|
||||
}
|
||||
|
||||
$local_return_type = clone $local_return_type;
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$local_return_type,
|
||||
$local_return_type = TemplateInferredTypeReplacer::replace(
|
||||
clone $local_return_type,
|
||||
new TemplateResult([], $found_generic_params),
|
||||
$codebase
|
||||
);
|
||||
|
@ -58,7 +58,7 @@ class UnsetAnalyzer
|
||||
);
|
||||
|
||||
if ($root_var_id && isset($context->vars_in_scope[$root_var_id])) {
|
||||
$root_type = clone $context->vars_in_scope[$root_var_id];
|
||||
$root_type = $context->vars_in_scope[$root_var_id]->getBuilder();
|
||||
|
||||
foreach ($root_type->getAtomicTypes() as $atomic_root_type) {
|
||||
if ($atomic_root_type instanceof TKeyedArray) {
|
||||
@ -126,7 +126,7 @@ class UnsetAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$root_var_id] = $root_type;
|
||||
$context->vars_in_scope[$root_var_id] = $root_type->freeze();
|
||||
|
||||
$context->removeVarFromConflictingClauses(
|
||||
$root_var_id,
|
||||
|
@ -1460,9 +1460,9 @@ class ClassLikes
|
||||
|
||||
foreach ($codebase->class_transforms as $old_fq_class_name => $new_fq_class_name) {
|
||||
if ($type->containsClassLike($old_fq_class_name)) {
|
||||
$type = clone $type;
|
||||
$type = $type->getBuilder();
|
||||
|
||||
$type->replaceClassLike($old_fq_class_name, $new_fq_class_name);
|
||||
$type = $type->replaceClassLike($old_fq_class_name, $new_fq_class_name)->freeze();
|
||||
|
||||
$bounds = $type_location->getSelectionBounds();
|
||||
|
||||
@ -1500,9 +1500,9 @@ class ClassLikes
|
||||
$destination_class = $codebase->classes_to_move[$fq_class_name_lc];
|
||||
|
||||
if ($type->containsClassLike($fq_class_name_lc)) {
|
||||
$type = clone $type;
|
||||
$type = $type->getBuilder();
|
||||
|
||||
$type->replaceClassLike($fq_class_name_lc, $destination_class);
|
||||
$type = $type->replaceClassLike($fq_class_name_lc, $destination_class)->freeze();
|
||||
}
|
||||
|
||||
$this->airliftClassDefinedDocblockType(
|
||||
|
@ -494,7 +494,7 @@ class Methods
|
||||
if ($params[$i]->signature_type
|
||||
&& $params[$i]->signature_type->isNullable()
|
||||
) {
|
||||
$params[$i]->type->addType(new TNull);
|
||||
$params[$i]->type = $params[$i]->type->getBuilder()->addType(new TNull)->freeze();
|
||||
}
|
||||
|
||||
$params[$i]->type_location = $overridden_storage->params[$i]->type_location;
|
||||
@ -520,7 +520,7 @@ class Methods
|
||||
return $type;
|
||||
}
|
||||
|
||||
$type = clone $type;
|
||||
$type = $type->getBuilder();
|
||||
|
||||
foreach ($type->getAtomicTypes() as $key => $atomic_type) {
|
||||
if ($atomic_type instanceof TTemplateParam
|
||||
@ -618,9 +618,7 @@ class Methods
|
||||
}
|
||||
}
|
||||
|
||||
$type->bustCache();
|
||||
|
||||
return $type;
|
||||
return $type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1629,7 +1629,7 @@ class ClassLikeNodeScanner
|
||||
if ($property_storage->signature_type->isNullable()
|
||||
&& !$property_storage->type->isNullable()
|
||||
) {
|
||||
$property_storage->type->addType(new TNull());
|
||||
$property_storage->type = $property_storage->type->getBuilder()->addType(new TNull())->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -846,7 +846,7 @@ class FunctionLikeDocblockScanner
|
||||
&& !$new_param_type->isNullable()
|
||||
&& !$new_param_type->hasTemplate()
|
||||
) {
|
||||
$new_param_type->addType(new TNull());
|
||||
$new_param_type = $new_param_type->getBuilder()->addType(new TNull())->freeze();
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
@ -888,7 +888,7 @@ class FunctionLikeDocblockScanner
|
||||
}
|
||||
|
||||
if ($existing_param_type_nullable && !$new_param_type->isNullable()) {
|
||||
$new_param_type->addType(new TNull());
|
||||
$new_param_type = $new_param_type->getBuilder()->addType(new TNull())->freeze();
|
||||
}
|
||||
|
||||
$storage_param->type = $new_param_type;
|
||||
@ -1010,7 +1010,7 @@ class FunctionLikeDocblockScanner
|
||||
$storage->signature_return_type
|
||||
)
|
||||
) {
|
||||
$storage->return_type->addType(new TNull());
|
||||
$storage->return_type = $storage->return_type->getBuilder()->addType(new TNull())->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -839,7 +839,7 @@ class FunctionLikeNodeScanner
|
||||
);
|
||||
|
||||
if ($is_nullable) {
|
||||
$param_type->addType(new TNull);
|
||||
$param_type = $param_type->getBuilder()->addType(new TNull)->freeze();
|
||||
} else {
|
||||
$is_nullable = $param_type->isNullable();
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ class TypeHintResolver
|
||||
}
|
||||
|
||||
if ($is_nullable) {
|
||||
$type->addType(new TNull);
|
||||
$type = $type->getBuilder()->addType(new TNull)->freeze();
|
||||
}
|
||||
|
||||
return $type;
|
||||
|
@ -153,11 +153,11 @@ class ArrayFilterReturnTypeProvider implements FunctionReturnTypeProviderInterfa
|
||||
}
|
||||
|
||||
if ($key_type->getLiteralStrings()) {
|
||||
$key_type->addType(new TString);
|
||||
$key_type = $key_type->getBuilder()->addType(new TString)->freeze();
|
||||
}
|
||||
|
||||
if ($key_type->getLiteralInts()) {
|
||||
$key_type->addType(new TInt);
|
||||
$key_type = $key_type->getBuilder()->addType(new TInt)->freeze();
|
||||
}
|
||||
|
||||
if ($inner_type->isUnionEmpty()) {
|
||||
|
@ -86,7 +86,7 @@ class ArrayPointerAdjustmentReturnTypeProvider implements FunctionReturnTypeProv
|
||||
if ($value_type->isNever()) {
|
||||
$value_type = Type::getFalse();
|
||||
} elseif (($function_id !== 'reset' && $function_id !== 'end') || !$definitely_has_items) {
|
||||
$value_type->addType(new TFalse);
|
||||
$value_type = $value_type->getBuilder()->addType(new TFalse)->freeze();
|
||||
|
||||
$codebase = $statements_source->getCodebase();
|
||||
|
||||
|
@ -85,7 +85,7 @@ class ArrayPopReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
}
|
||||
|
||||
if ($nullable) {
|
||||
$value_type->addType(new TNull);
|
||||
$value_type = $value_type->getBuilder()->addType(new TNull)->freeze();
|
||||
|
||||
$codebase = $statements_source->getCodebase();
|
||||
|
||||
|
@ -104,7 +104,7 @@ class FilterVarReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
$options_array->properties['default']
|
||||
);
|
||||
} else {
|
||||
$filter_type->addType(new TFalse);
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TFalse)->freeze();
|
||||
}
|
||||
|
||||
if (isset($atomic_type->properties['flags'])
|
||||
@ -116,20 +116,20 @@ class FilterVarReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
if ($filter_type->hasBool()
|
||||
&& $filter_flag_type->value === FILTER_NULL_ON_FAILURE
|
||||
) {
|
||||
$filter_type->addType(new TNull);
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TNull)->freeze();
|
||||
}
|
||||
}
|
||||
} elseif ($atomic_type instanceof TLiteralInt) {
|
||||
if ($atomic_type->value === FILTER_NULL_ON_FAILURE) {
|
||||
$filter_null = true;
|
||||
$filter_type->addType(new TNull);
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TNull)->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_object_like && !$filter_null && $filter_type) {
|
||||
$filter_type->addType(new TFalse);
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TFalse)->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
/**
|
||||
@ -34,15 +35,13 @@ class FirstArgStringReturnTypeProvider implements FunctionReturnTypeProviderInte
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
$return_type = Type::getString();
|
||||
|
||||
if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value))
|
||||
&& $first_arg_type->isString()
|
||||
) {
|
||||
return $return_type;
|
||||
return new Union([new TString]);
|
||||
}
|
||||
|
||||
$return_type->addType(new TNull);
|
||||
$return_type = new Union([new TString, new TNull]);
|
||||
$return_type->ignore_nullable_issues = true;
|
||||
|
||||
return $return_type;
|
||||
|
@ -7,6 +7,7 @@ use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
use function count;
|
||||
@ -50,7 +51,7 @@ class StrReplaceReturnTypeProvider implements FunctionReturnTypeProviderInterfac
|
||||
$return_type = Type::getString();
|
||||
|
||||
if (in_array($function_id, ['preg_replace', 'preg_replace_callback'], true)) {
|
||||
$return_type->addType(new TNull());
|
||||
$return_type = new Union([new TString, new TNull()]);
|
||||
|
||||
$codebase = $statements_source->getCodebase();
|
||||
|
||||
|
@ -18,19 +18,21 @@ class ReferenceConstraint
|
||||
public function __construct(?Union $type = null)
|
||||
{
|
||||
if ($type) {
|
||||
$this->type = clone $type;
|
||||
$type = $type->getBuilder();
|
||||
|
||||
if ($this->type->getLiteralStrings()) {
|
||||
$this->type->addType(new TString);
|
||||
if ($type->getLiteralStrings()) {
|
||||
$type->addType(new TString);
|
||||
}
|
||||
|
||||
if ($this->type->getLiteralInts()) {
|
||||
$this->type->addType(new TInt);
|
||||
if ($type->getLiteralInts()) {
|
||||
$type->addType(new TInt);
|
||||
}
|
||||
|
||||
if ($this->type->getLiteralFloats()) {
|
||||
$this->type->addType(new TFloat);
|
||||
if ($type->getLiteralFloats()) {
|
||||
$type->addType(new TFloat);
|
||||
}
|
||||
|
||||
$this->type = $type->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -943,6 +943,7 @@ class AssertionReconciler extends Reconciler
|
||||
$can_be_equal = false;
|
||||
$did_remove_type = false;
|
||||
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) {
|
||||
if (get_class($atomic_type) === TNamedObject::class
|
||||
&& $atomic_type->value === $fq_enum_name
|
||||
@ -958,6 +959,7 @@ class AssertionReconciler extends Reconciler
|
||||
$can_be_equal = true;
|
||||
}
|
||||
}
|
||||
$existing_var_type = $existing_var_type->freeze();
|
||||
|
||||
if ($var_id
|
||||
&& $code_location
|
||||
|
@ -27,6 +27,7 @@ use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function end;
|
||||
@ -440,15 +441,11 @@ class CallableTypeComparator
|
||||
);
|
||||
|
||||
if ($template_result) {
|
||||
$replaced_callable = clone $callable;
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
new Type\Union([$replaced_callable]),
|
||||
$callable = TemplateInferredTypeReplacer::replace(
|
||||
new Union([clone $callable]),
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
|
||||
$callable = $replaced_callable;
|
||||
)->getSingleAtomic();
|
||||
}
|
||||
|
||||
return $callable;
|
||||
|
@ -174,14 +174,15 @@ class UnionTypeComparator
|
||||
&& $atomic_comparison_result->replacement_atomic_type
|
||||
) {
|
||||
if (!$union_comparison_result->replacement_union_type) {
|
||||
$union_comparison_result->replacement_union_type = clone $input_type;
|
||||
$union_comparison_result->replacement_union_type = $input_type;
|
||||
}
|
||||
|
||||
$union_comparison_result->replacement_union_type->removeType($input_type->getKey());
|
||||
|
||||
$union_comparison_result->replacement_union_type->addType(
|
||||
$replacement = $union_comparison_result->replacement_union_type->getBuilder();
|
||||
$replacement->removeType($input_type->getKey());
|
||||
$replacement->addType(
|
||||
$atomic_comparison_result->replacement_atomic_type
|
||||
);
|
||||
$union_comparison_result->replacement_union_type = $replacement->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,10 +322,10 @@ class UnionTypeComparator
|
||||
return false;
|
||||
}
|
||||
|
||||
$input_type_not_null = clone $input_type;
|
||||
$input_type_not_null = $input_type->getBuilder();
|
||||
$input_type_not_null->removeType('null');
|
||||
|
||||
$container_type_not_null = clone $container_type;
|
||||
$container_type_not_null = $container_type->getBuilder();
|
||||
$container_type_not_null->removeType('null');
|
||||
|
||||
if ($input_type_not_null->getId() === $container_type_not_null->getId()) {
|
||||
|
@ -91,18 +91,20 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
$existing_var_atomic_types = $existing_var_type->getAtomicTypes();
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
|
||||
if ($assertion_type instanceof TFalse && isset($existing_var_atomic_types['bool'])) {
|
||||
$existing_var_type->removeType('bool');
|
||||
$existing_var_type->addType(new TTrue);
|
||||
} elseif ($assertion_type instanceof TTrue && isset($existing_var_atomic_types['bool'])) {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$existing_var_type->removeType('bool');
|
||||
$existing_var_type->addType(new TFalse);
|
||||
} else {
|
||||
$simple_negated_type = SimpleNegatedAssertionReconciler::reconcile(
|
||||
$statements_analyzer->getCodebase(),
|
||||
$assertion,
|
||||
$existing_var_type,
|
||||
$existing_var_type->freeze(),
|
||||
$key,
|
||||
$negated,
|
||||
$code_location,
|
||||
@ -142,7 +144,7 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
|
||||
$existing_var_type->from_calculation = false;
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
if (!$is_equality
|
||||
@ -158,7 +160,7 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
$existing_var_type->addType(new TNamedObject('DateTime'));
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
if (!$is_equality && $assertion_type instanceof TNamedObject) {
|
||||
@ -251,6 +253,8 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
$existing_var_type = $existing_var_type->freeze();
|
||||
|
||||
if ($assertion instanceof IsNotIdentical
|
||||
&& ($key !== '$this'
|
||||
|| !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer))
|
||||
@ -322,6 +326,7 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$existing_var_atomic_types = $existing_var_type->getAtomicTypes();
|
||||
|
||||
$did_remove_type = false;
|
||||
@ -443,6 +448,8 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
$existing_var_type = $existing_var_type->freeze();
|
||||
|
||||
if ($key && $code_location) {
|
||||
if ($did_match_literal_type
|
||||
&& (!$did_remove_type || count($existing_var_atomic_types) === 1)
|
||||
|
@ -504,6 +504,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
bool $is_equality,
|
||||
bool $inside_loop
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
|
||||
// if key references an array offset
|
||||
@ -554,7 +555,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
$existing_var_type->possibly_undefined_from_try = false;
|
||||
$existing_var_type->ignore_isset = false;
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -570,6 +571,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
bool $is_equality
|
||||
): Union {
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
|
||||
if ($existing_var_type->hasType('array')) {
|
||||
$array_atomic_type = $existing_var_type->getAtomicTypes()['array'];
|
||||
@ -665,7 +667,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -675,6 +677,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
Union $existing_var_type,
|
||||
int $count
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
if ($existing_var_type->hasType('array')) {
|
||||
$array_atomic_type = $existing_var_type->getAtomicTypes()['array'];
|
||||
|
||||
@ -701,7 +704,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1177,6 +1180,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
if ($existing_var_type->hasMixed()) {
|
||||
return Type::getNumeric();
|
||||
}
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
|
||||
@ -1631,6 +1635,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
//we add 1 from the assertion value because we're on a strict operator
|
||||
$assertion_value = $assertion->value + 1;
|
||||
|
||||
@ -1720,7 +1725,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
$existing_var_type->addType(new TNever());
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1738,6 +1743,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
): Union {
|
||||
//we remove 1 from the assertion value because we're on a strict operator
|
||||
$assertion_value = $assertion->value - 1;
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
|
||||
$did_remove_type = false;
|
||||
|
||||
@ -1822,7 +1828,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
$existing_var_type->addType(new TNever());
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2354,6 +2360,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
int &$failed_reconciliation,
|
||||
bool $recursive_check
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
|
||||
//empty is used a lot to check for array offset existence, so we have to silent errors a lot
|
||||
@ -2412,7 +2419,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
|
||||
$failed_reconciliation = 1;
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
$existing_var_type->possibly_undefined = false;
|
||||
@ -2501,7 +2508,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
if ($existing_var_type->isSingle()) {
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2532,7 +2539,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
assert(!$existing_var_type->isUnionEmpty());
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -413,6 +413,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
private static function reconcileCallable(
|
||||
Union $existing_var_type
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_key => $type) {
|
||||
if ($type instanceof TLiteralString
|
||||
&& InternalCallMapHandler::inCallMap($type->value)
|
||||
@ -425,7 +426,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -513,6 +514,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
bool $is_equality,
|
||||
?int $min_count
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
$existing_var_atomic_types = $existing_var_type->getAtomicTypes();
|
||||
|
||||
@ -570,7 +572,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -587,6 +589,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
int &$failed_reconciliation,
|
||||
bool $is_equality
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
$did_remove_type = false;
|
||||
|
||||
@ -633,7 +636,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
if (!$existing_var_type->isUnionEmpty()) {
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
$failed_reconciliation = Reconciler::RECONCILIATION_EMPTY;
|
||||
@ -660,6 +663,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
$did_remove_type = $existing_var_type->hasScalar();
|
||||
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
if ($existing_var_type->hasType('false')) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType('false');
|
||||
@ -703,7 +707,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
if (!$existing_var_type->isUnionEmpty()) {
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
$failed_reconciliation = Reconciler::RECONCILIATION_EMPTY;
|
||||
@ -728,6 +732,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
int &$failed_reconciliation,
|
||||
bool $recursive_check
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$old_var_type_string = $existing_var_type->getId();
|
||||
|
||||
$did_remove_type = $existing_var_type->possibly_undefined
|
||||
@ -786,7 +791,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
|
||||
$failed_reconciliation = 1;
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
if ($existing_var_type->hasType('bool')) {
|
||||
@ -894,7 +899,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
assert(!$existing_var_type->isUnionEmpty());
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1621,7 +1626,9 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
|
||||
if ($existing_var_type->hasType('resource')) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$existing_var_type->removeType('resource');
|
||||
$existing_var_type = $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
foreach ($existing_var_type->getAtomicTypes() as $type) {
|
||||
@ -1685,6 +1692,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$assertion_value = $assertion->value;
|
||||
|
||||
$did_remove_type = false;
|
||||
@ -1773,7 +1781,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
$existing_var_type->addType(new TNever());
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1789,6 +1797,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
$existing_var_type = $existing_var_type->getBuilder();
|
||||
$assertion_value = $assertion->value;
|
||||
|
||||
$did_remove_type = false;
|
||||
@ -1874,6 +1883,6 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
$existing_var_type->addType(new TNever());
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
return $existing_var_type->freeze();
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class TemplateInferredTypeReplacer
|
||||
Union $union,
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
): Union {
|
||||
$keys_to_unset = [];
|
||||
|
||||
$new_types = [];
|
||||
@ -56,6 +56,7 @@ class TemplateInferredTypeReplacer
|
||||
|
||||
$inferred_lower_bounds = $template_result->lower_bounds ?: [];
|
||||
|
||||
$union = $union->getBuilder();
|
||||
foreach ($union->getAtomicTypes() as $key => $atomic_type) {
|
||||
$atomic_type->replaceTemplateTypesWithArgTypes($template_result, $codebase);
|
||||
|
||||
@ -214,14 +215,12 @@ class TemplateInferredTypeReplacer
|
||||
throw new UnexpectedValueException('This array should be full');
|
||||
}
|
||||
|
||||
$union->replaceTypes(
|
||||
return $union->replaceTypes(
|
||||
TypeCombiner::combine(
|
||||
$new_types,
|
||||
$codebase
|
||||
)->getAtomicTypes()
|
||||
);
|
||||
|
||||
return;
|
||||
)->freeze();
|
||||
}
|
||||
|
||||
foreach ($keys_to_unset as $key) {
|
||||
@ -230,12 +229,12 @@ class TemplateInferredTypeReplacer
|
||||
|
||||
$atomic_types = array_values(array_merge($union->getAtomicTypes(), $new_types));
|
||||
|
||||
$union->replaceTypes(
|
||||
return $union->replaceTypes(
|
||||
TypeCombiner::combine(
|
||||
$atomic_types,
|
||||
$codebase
|
||||
)->getAtomicTypes()
|
||||
);
|
||||
)->freeze();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,9 +260,9 @@ class TemplateInferredTypeReplacer
|
||||
$template_type = $traversed_type;
|
||||
|
||||
if (!$atomic_type->as->isMixed() && $template_type->isMixed()) {
|
||||
$template_type = clone $atomic_type->as;
|
||||
$template_type = $atomic_type->as->getBuilder();
|
||||
} else {
|
||||
$template_type = clone $template_type;
|
||||
$template_type = $template_type->getBuilder();
|
||||
}
|
||||
|
||||
if ($atomic_type->extra_types) {
|
||||
@ -289,6 +288,7 @@ class TemplateInferredTypeReplacer
|
||||
}
|
||||
}
|
||||
}
|
||||
$template_type = $template_type->freeze();
|
||||
} elseif ($codebase) {
|
||||
foreach ($inferred_lower_bounds as $template_type_map) {
|
||||
foreach ($template_type_map as $template_class => $_) {
|
||||
@ -410,7 +410,7 @@ class TemplateInferredTypeReplacer
|
||||
$atomic_type = clone $atomic_type;
|
||||
|
||||
if ($template_type) {
|
||||
self::replace(
|
||||
$atomic_type->as_type = self::replace(
|
||||
$atomic_type->as_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
@ -478,7 +478,7 @@ class TemplateInferredTypeReplacer
|
||||
)
|
||||
];
|
||||
|
||||
self::replace(
|
||||
$if_template_type = self::replace(
|
||||
$if_template_type,
|
||||
$refined_template_result,
|
||||
$codebase
|
||||
@ -508,7 +508,7 @@ class TemplateInferredTypeReplacer
|
||||
)
|
||||
];
|
||||
|
||||
self::replace(
|
||||
$else_template_type = self::replace(
|
||||
$else_template_type,
|
||||
$refined_template_result,
|
||||
$codebase
|
||||
@ -517,13 +517,13 @@ class TemplateInferredTypeReplacer
|
||||
}
|
||||
|
||||
if (!$if_template_type && !$else_template_type) {
|
||||
self::replace(
|
||||
$atomic_type->if_type = self::replace(
|
||||
$atomic_type->if_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
|
||||
self::replace(
|
||||
$atomic_type->else_type = self::replace(
|
||||
$atomic_type->else_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -84,7 +84,7 @@ class TemplateStandinTypeReplacer
|
||||
// when they're also in the union type, so those shared atomic
|
||||
// types will never be inferred as part of the generic type
|
||||
if ($input_type && !$input_type->isSingle()) {
|
||||
$new_input_type = clone $input_type;
|
||||
$new_input_type = $input_type->getBuilder();
|
||||
|
||||
foreach ($original_atomic_types as $key => $_) {
|
||||
if ($new_input_type->hasType($key)) {
|
||||
@ -93,7 +93,7 @@ class TemplateStandinTypeReplacer
|
||||
}
|
||||
|
||||
if (!$new_input_type->isUnionEmpty()) {
|
||||
$input_type = $new_input_type;
|
||||
$input_type = $new_input_type->freeze();
|
||||
} else {
|
||||
return $union_type;
|
||||
}
|
||||
@ -766,7 +766,7 @@ class TemplateStandinTypeReplacer
|
||||
)
|
||||
)
|
||||
) {
|
||||
$generic_param = clone $input_type;
|
||||
$generic_param = $input_type->getBuilder();
|
||||
|
||||
if ($matching_input_keys) {
|
||||
$generic_param_keys = array_keys($generic_param->getAtomicTypes());
|
||||
@ -777,6 +777,7 @@ class TemplateStandinTypeReplacer
|
||||
}
|
||||
}
|
||||
}
|
||||
$generic_param = $generic_param->freeze();
|
||||
|
||||
if ($add_lower_bound) {
|
||||
return array_values($generic_param->getAtomicTypes());
|
||||
@ -858,7 +859,7 @@ class TemplateStandinTypeReplacer
|
||||
$matching_input_keys
|
||||
)
|
||||
) {
|
||||
$generic_param = clone $input_type;
|
||||
$generic_param = $input_type->getBuilder();
|
||||
|
||||
if ($matching_input_keys) {
|
||||
$generic_param_keys = array_keys($generic_param->getAtomicTypes());
|
||||
@ -869,6 +870,7 @@ class TemplateStandinTypeReplacer
|
||||
}
|
||||
}
|
||||
}
|
||||
$generic_param = $generic_param->freeze();
|
||||
|
||||
$upper_bound = $template_result->upper_bounds
|
||||
[$param_name_key]
|
||||
@ -1258,7 +1260,7 @@ class TemplateStandinTypeReplacer
|
||||
|
||||
$new_input_param = clone $new_input_param;
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$new_input_param = TemplateInferredTypeReplacer::replace(
|
||||
$new_input_param,
|
||||
new TemplateResult([], $replacement_templates),
|
||||
$codebase
|
||||
|
@ -255,7 +255,7 @@ class TypeParser
|
||||
);
|
||||
|
||||
if ($non_nullable_type instanceof Union) {
|
||||
$non_nullable_type->addType(new TNull);
|
||||
$non_nullable_type = $non_nullable_type->getBuilder()->addType(new TNull)->freeze();
|
||||
|
||||
return $non_nullable_type;
|
||||
}
|
||||
@ -396,7 +396,7 @@ class TypeParser
|
||||
|
||||
private static function getGenericParamClass(
|
||||
string $param_name,
|
||||
Union $as,
|
||||
Union &$as,
|
||||
string $defining_class
|
||||
): TTemplateParamClass {
|
||||
if ($as->hasMixed()) {
|
||||
@ -430,7 +430,7 @@ class TypeParser
|
||||
$t->type_params
|
||||
);
|
||||
|
||||
$as->substitute(new Union([$t]), new Union([$traversable]));
|
||||
$as = $as->getBuilder()->substitute(new Union([$t]), new Union([$traversable]))->freeze();
|
||||
|
||||
return new TTemplateParamClass(
|
||||
$param_name,
|
||||
|
@ -45,7 +45,7 @@ final class Possibilities
|
||||
|
||||
if ($assertion_type) {
|
||||
$union = new Union([clone $assertion_type]);
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$union = TemplateInferredTypeReplacer::replace(
|
||||
$union,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -45,6 +45,7 @@ use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTrue;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\MutableUnion;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
@ -601,13 +602,16 @@ abstract class Type
|
||||
|
||||
if (null !== $intersection_atomic) {
|
||||
if (null === $combined_type) {
|
||||
$combined_type = new Union([$intersection_atomic]);
|
||||
$combined_type = new MutableUnion([$intersection_atomic]);
|
||||
} else {
|
||||
$combined_type->addType($intersection_atomic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($combined_type) {
|
||||
$combined_type = $combined_type->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
//if a type is contained by the other, the intersection is the narrowest type
|
||||
|
@ -549,7 +549,7 @@ abstract class Atomic implements TypeNode
|
||||
}
|
||||
|
||||
if ($this instanceof TTemplateParam) {
|
||||
$this->as->replaceClassLike($old, $new);
|
||||
$this->as = $this->as->getBuilder()->replaceClassLike($old, $new)->freeze();
|
||||
}
|
||||
|
||||
if ($this instanceof TLiteralClassString) {
|
||||
@ -562,14 +562,14 @@ abstract class Atomic implements TypeNode
|
||||
|| $this instanceof TGenericObject
|
||||
|| $this instanceof TIterable
|
||||
) {
|
||||
foreach ($this->type_params as $type_param) {
|
||||
$type_param->replaceClassLike($old, $new);
|
||||
foreach ($this->type_params as &$type_param) {
|
||||
$type_param = $type_param->getBuilder()->replaceClassLike($old, $new)->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this instanceof TKeyedArray) {
|
||||
foreach ($this->properties as $property_type) {
|
||||
$property_type->replaceClassLike($old, $new);
|
||||
foreach ($this->properties as &$property_type) {
|
||||
$property_type = $property_type->getBuilder()->replaceClassLike($old, $new)->freeze();
|
||||
}
|
||||
}
|
||||
|
||||
@ -579,13 +579,13 @@ abstract class Atomic implements TypeNode
|
||||
if ($this->params) {
|
||||
foreach ($this->params as $param) {
|
||||
if ($param->type) {
|
||||
$param->type->replaceClassLike($old, $new);
|
||||
$param->type = $param->type->getBuilder()->replaceClassLike($old, $new)->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->return_type) {
|
||||
$this->return_type->replaceClassLike($old, $new);
|
||||
$this->return_type = $this->return_type->getBuilder()->replaceClassLike($old, $new)->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ trait CallableTrait
|
||||
continue;
|
||||
}
|
||||
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$param->type = TemplateInferredTypeReplacer::replace(
|
||||
$param->type,
|
||||
$template_result,
|
||||
$codebase
|
||||
@ -271,7 +271,7 @@ trait CallableTrait
|
||||
}
|
||||
|
||||
if ($this->return_type) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$this->return_type = TemplateInferredTypeReplacer::replace(
|
||||
$this->return_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -234,8 +234,8 @@ trait GenericTrait
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
foreach ($this->type_params as $offset => $type_param) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
foreach ($this->type_params as $offset => &$type_param) {
|
||||
$type_param = TemplateInferredTypeReplacer::replace(
|
||||
$type_param,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -182,7 +182,7 @@ final class TClassStringMap extends Atomic
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$this->value_param = TemplateInferredTypeReplacer::replace(
|
||||
$this->value_param,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -128,7 +128,7 @@ final class TConditional extends Atomic
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$this->conditional_type = TemplateInferredTypeReplacer::replace(
|
||||
$this->conditional_type,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -322,8 +322,8 @@ class TKeyedArray extends Atomic
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
foreach ($this->properties as $property) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
foreach ($this->properties as &$property) {
|
||||
$property = TemplateInferredTypeReplacer::replace(
|
||||
$property,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -165,7 +165,7 @@ class TList extends Atomic
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$this->type_param = TemplateInferredTypeReplacer::replace(
|
||||
$this->type_param,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -216,8 +216,8 @@ final class TObjectWithProperties extends TObject
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
foreach ($this->properties as $property) {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
foreach ($this->properties as &$property) {
|
||||
$property = TemplateInferredTypeReplacer::replace(
|
||||
$property,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -85,7 +85,7 @@ final class TTemplateKeyOf extends Atomic
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$this->as = TemplateInferredTypeReplacer::replace(
|
||||
$this->as,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
@ -80,10 +80,10 @@ final class TTemplatePropertiesOf extends Atomic
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$this->as = TemplateInferredTypeReplacer::replace(
|
||||
new Union([$this->as]),
|
||||
$template_result,
|
||||
$codebase
|
||||
);
|
||||
)->getSingleAtomic();
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ final class TTemplateValueOf extends Atomic
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase
|
||||
): void {
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$this->as = TemplateInferredTypeReplacer::replace(
|
||||
$this->as,
|
||||
$template_result,
|
||||
$codebase
|
||||
|
455
src/Psalm/Type/MutableUnion.php
Normal file
455
src/Psalm/Type/MutableUnion.php
Normal file
@ -0,0 +1,455 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Type;
|
||||
|
||||
use Psalm\Internal\DataFlow\DataFlowNode;
|
||||
use Psalm\Internal\Type\TypeCombiner;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TClassString;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TIntRange;
|
||||
use Psalm\Type\Atomic\TLiteralFloat;
|
||||
use Psalm\Type\Atomic\TLiteralInt;
|
||||
use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TTemplateParamClass;
|
||||
use Psalm\Type\Atomic\TTrue;
|
||||
use Stringable;
|
||||
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function get_object_vars;
|
||||
use function strpos;
|
||||
|
||||
final class MutableUnion implements TypeNode, Stringable
|
||||
{
|
||||
use UnionTrait;
|
||||
|
||||
/**
|
||||
* @var non-empty-array<string, Atomic>
|
||||
*/
|
||||
private $types;
|
||||
|
||||
/**
|
||||
* Whether the type originated in a docblock
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $from_docblock = false;
|
||||
|
||||
/**
|
||||
* Whether the type originated from integer calculation
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $from_calculation = false;
|
||||
|
||||
/**
|
||||
* Whether the type originated from a property
|
||||
*
|
||||
* This helps turn isset($foo->bar) into a different sort of issue
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $from_property = false;
|
||||
|
||||
/**
|
||||
* Whether the type originated from *static* property
|
||||
*
|
||||
* Unlike non-static properties, static properties have no prescribed place
|
||||
* like __construct() to be initialized in
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $from_static_property = false;
|
||||
|
||||
/**
|
||||
* Whether the property that this type has been derived from has been initialized in a constructor
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $initialized = true;
|
||||
|
||||
/**
|
||||
* Which class the type was initialised in
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
public $initialized_class;
|
||||
|
||||
/**
|
||||
* Whether or not the type has been checked yet
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $checked = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $failed_reconciliation = false;
|
||||
|
||||
/**
|
||||
* Whether or not to ignore issues with possibly-null values
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $ignore_nullable_issues = false;
|
||||
|
||||
/**
|
||||
* Whether or not to ignore issues with possibly-false values
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $ignore_falsable_issues = false;
|
||||
|
||||
/**
|
||||
* Whether or not to ignore issues with isset on this type
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $ignore_isset = false;
|
||||
|
||||
/**
|
||||
* Whether or not this variable is possibly undefined
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $possibly_undefined = false;
|
||||
|
||||
/**
|
||||
* Whether or not this variable is possibly undefined
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $possibly_undefined_from_try = false;
|
||||
|
||||
/**
|
||||
* Whether or not this union had a template, since replaced
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $had_template = false;
|
||||
|
||||
/**
|
||||
* Whether or not this union comes from a template "as" default
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $from_template_default = false;
|
||||
|
||||
/**
|
||||
* @var array<string, TLiteralString>
|
||||
*/
|
||||
private $literal_string_types = [];
|
||||
|
||||
/**
|
||||
* @var array<string, TClassString>
|
||||
*/
|
||||
private $typed_class_strings = [];
|
||||
|
||||
/**
|
||||
* @var array<string, TLiteralInt>
|
||||
*/
|
||||
private $literal_int_types = [];
|
||||
|
||||
/**
|
||||
* @var array<string, TLiteralFloat>
|
||||
*/
|
||||
private $literal_float_types = [];
|
||||
|
||||
/**
|
||||
* True if the type was passed or returned by reference, or if the type refers to an object's
|
||||
* property or an item in an array. Note that this is not true for locally created references
|
||||
* that don't refer to properties or array items (see Context::$references_in_scope).
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $by_ref = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $reference_free = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $allow_mutations = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $has_mutations = true;
|
||||
|
||||
/**
|
||||
* This is a cache of getId on non-exact mode
|
||||
* @var null|string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* This is a cache of getId on exact mode
|
||||
* @var null|string
|
||||
*/
|
||||
private $exact_id;
|
||||
|
||||
|
||||
/**
|
||||
* @var array<string, DataFlowNode>
|
||||
*/
|
||||
public $parent_nodes = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $different = false;
|
||||
|
||||
/**
|
||||
* @param non-empty-array<string, Atomic> $types
|
||||
*/
|
||||
public function replaceTypes(array $types): self
|
||||
{
|
||||
$this->types = $types;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addType(Atomic $type): self
|
||||
{
|
||||
$this->types[$type->getKey()] = $type;
|
||||
|
||||
if ($type instanceof TLiteralString) {
|
||||
$this->literal_string_types[$type->getKey()] = $type;
|
||||
} elseif ($type instanceof TLiteralInt) {
|
||||
$this->literal_int_types[$type->getKey()] = $type;
|
||||
} elseif ($type instanceof TLiteralFloat) {
|
||||
$this->literal_float_types[$type->getKey()] = $type;
|
||||
} elseif ($type instanceof TString && $this->literal_string_types) {
|
||||
foreach ($this->literal_string_types as $key => $_) {
|
||||
unset($this->literal_string_types[$key], $this->types[$key]);
|
||||
}
|
||||
if (!$type instanceof TClassString
|
||||
|| (!$type->as_type && !$type instanceof TTemplateParamClass)
|
||||
) {
|
||||
foreach ($this->typed_class_strings as $key => $_) {
|
||||
unset($this->typed_class_strings[$key], $this->types[$key]);
|
||||
}
|
||||
}
|
||||
} elseif ($type instanceof TInt && $this->literal_int_types) {
|
||||
//we remove any literal that is already included in a wider type
|
||||
$int_type_in_range = TIntRange::convertToIntRange($type);
|
||||
foreach ($this->literal_int_types as $key => $literal_int_type) {
|
||||
if ($int_type_in_range->contains($literal_int_type->value)) {
|
||||
unset($this->literal_int_types[$key], $this->types[$key]);
|
||||
}
|
||||
}
|
||||
} elseif ($type instanceof TFloat && $this->literal_float_types) {
|
||||
foreach ($this->literal_float_types as $key => $_) {
|
||||
unset($this->literal_float_types[$key], $this->types[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->bustCache();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeType(string $type_string): bool
|
||||
{
|
||||
if (isset($this->types[$type_string])) {
|
||||
unset($this->types[$type_string]);
|
||||
|
||||
if (strpos($type_string, '(')) {
|
||||
unset(
|
||||
$this->literal_string_types[$type_string],
|
||||
$this->literal_int_types[$type_string],
|
||||
$this->literal_float_types[$type_string]
|
||||
);
|
||||
}
|
||||
|
||||
$this->bustCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($type_string === 'string') {
|
||||
if ($this->literal_string_types) {
|
||||
foreach ($this->literal_string_types as $literal_key => $_) {
|
||||
unset($this->types[$literal_key]);
|
||||
}
|
||||
$this->literal_string_types = [];
|
||||
}
|
||||
|
||||
if ($this->typed_class_strings) {
|
||||
foreach ($this->typed_class_strings as $typed_class_key => $_) {
|
||||
unset($this->types[$typed_class_key]);
|
||||
}
|
||||
$this->typed_class_strings = [];
|
||||
}
|
||||
|
||||
unset($this->types['class-string'], $this->types['trait-string']);
|
||||
} elseif ($type_string === 'int' && $this->literal_int_types) {
|
||||
foreach ($this->literal_int_types as $literal_key => $_) {
|
||||
unset($this->types[$literal_key]);
|
||||
}
|
||||
$this->literal_int_types = [];
|
||||
} elseif ($type_string === 'float' && $this->literal_float_types) {
|
||||
foreach ($this->literal_float_types as $literal_key => $_) {
|
||||
unset($this->types[$literal_key]);
|
||||
}
|
||||
$this->literal_float_types = [];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function bustCache(): void
|
||||
{
|
||||
$this->id = null;
|
||||
$this->exact_id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Union|MutableUnion $old_type
|
||||
* @param Union|MutableUnion|null $new_type
|
||||
*/
|
||||
public function substitute($old_type, $new_type = null): self
|
||||
{
|
||||
if ($this->hasMixed() && !$this->isEmptyMixed()) {
|
||||
return $this;
|
||||
}
|
||||
$old_type = $old_type->getBuilder();
|
||||
if ($new_type) {
|
||||
$new_type = $new_type->getBuilder();
|
||||
}
|
||||
|
||||
if ($new_type && $new_type->ignore_nullable_issues) {
|
||||
$this->ignore_nullable_issues = true;
|
||||
}
|
||||
|
||||
if ($new_type && $new_type->ignore_falsable_issues) {
|
||||
$this->ignore_falsable_issues = true;
|
||||
}
|
||||
|
||||
foreach ($old_type->types as $old_type_part) {
|
||||
$had = isset($this->types[$old_type_part->getKey()]);
|
||||
$this->removeType($old_type_part->getKey());
|
||||
if (!$had) {
|
||||
if ($old_type_part instanceof TFalse
|
||||
&& isset($this->types['bool'])
|
||||
&& !isset($this->types['true'])
|
||||
) {
|
||||
$this->removeType('bool');
|
||||
$this->types['true'] = new TTrue;
|
||||
} elseif ($old_type_part instanceof TTrue
|
||||
&& isset($this->types['bool'])
|
||||
&& !isset($this->types['false'])
|
||||
) {
|
||||
$this->removeType('bool');
|
||||
$this->types['false'] = new TFalse;
|
||||
} elseif (isset($this->types['iterable'])) {
|
||||
if ($old_type_part instanceof TNamedObject
|
||||
&& $old_type_part->value === 'Traversable'
|
||||
&& !isset($this->types['array'])
|
||||
) {
|
||||
$this->removeType('iterable');
|
||||
$this->types['array'] = new TArray([Type::getArrayKey(), Type::getMixed()]);
|
||||
}
|
||||
|
||||
if ($old_type_part instanceof TArray
|
||||
&& !isset($this->types['traversable'])
|
||||
) {
|
||||
$this->removeType('iterable');
|
||||
$this->types['traversable'] = new TNamedObject('Traversable');
|
||||
}
|
||||
} elseif (isset($this->types['array-key'])) {
|
||||
if ($old_type_part instanceof TString
|
||||
&& !isset($this->types['int'])
|
||||
) {
|
||||
$this->removeType('array-key');
|
||||
$this->types['int'] = new TInt();
|
||||
}
|
||||
|
||||
if ($old_type_part instanceof TInt
|
||||
&& !isset($this->types['string'])
|
||||
) {
|
||||
$this->removeType('array-key');
|
||||
$this->types['string'] = new TString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($new_type) {
|
||||
foreach ($new_type->types as $key => $new_type_part) {
|
||||
if (!isset($this->types[$key])
|
||||
|| ($new_type_part instanceof Scalar
|
||||
&& get_class($new_type_part) === get_class($this->types[$key]))
|
||||
) {
|
||||
$this->types[$key] = $new_type_part;
|
||||
} else {
|
||||
$this->types[$key] = TypeCombiner::combine([$new_type_part, $this->types[$key]])->getSingleAtomic();
|
||||
}
|
||||
}
|
||||
} elseif (count($this->types) === 0) {
|
||||
$this->types['mixed'] = new TMixed();
|
||||
}
|
||||
|
||||
$this->bustCache();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function replaceClassLike(string $old, string $new): self
|
||||
{
|
||||
foreach ($this->types as $key => $atomic_type) {
|
||||
$atomic_type->replaceClassLike($old, $new);
|
||||
|
||||
$this->removeType($key);
|
||||
$this->addType($atomic_type);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBuilder(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function freeze(): Union
|
||||
{
|
||||
$union = new Union($this->getAtomicTypes());
|
||||
foreach (get_object_vars($this) as $key => $value) {
|
||||
if ($key === 'types') {
|
||||
continue;
|
||||
}
|
||||
if ($key === 'id') {
|
||||
continue;
|
||||
}
|
||||
if ($key === 'exact_id') {
|
||||
continue;
|
||||
}
|
||||
if ($key === 'literal_string_types') {
|
||||
continue;
|
||||
}
|
||||
if ($key === 'typed_class_strings') {
|
||||
continue;
|
||||
}
|
||||
if ($key === 'literal_int_types') {
|
||||
continue;
|
||||
}
|
||||
if ($key === 'literal_float_types') {
|
||||
continue;
|
||||
}
|
||||
$union->{$key} = $value;
|
||||
}
|
||||
return $union;
|
||||
}
|
||||
}
|
@ -283,7 +283,7 @@ class Reconciler
|
||||
);
|
||||
|
||||
if ($result_type_candidate->isUnionEmpty()) {
|
||||
$result_type_candidate->addType(new TNever);
|
||||
$result_type_candidate = $result_type_candidate->getBuilder()->addType(new TNever)->freeze();
|
||||
}
|
||||
|
||||
$orred_type = Type::combineUnionTypes(
|
||||
@ -715,7 +715,9 @@ class Reconciler
|
||||
|
||||
if (($has_isset || $has_inverted_isset) && isset($new_assertions[$new_base_key])) {
|
||||
if ($has_inverted_isset && $new_base_key === $key) {
|
||||
$new_base_type_candidate = $new_base_type_candidate->getBuilder();
|
||||
$new_base_type_candidate->addType(new TNull);
|
||||
$new_base_type_candidate = $new_base_type_candidate->freeze();
|
||||
}
|
||||
|
||||
$new_base_type_candidate->possibly_undefined = true;
|
||||
@ -729,7 +731,10 @@ class Reconciler
|
||||
|
||||
if (($has_isset || $has_inverted_isset) && isset($new_assertions[$new_base_key])) {
|
||||
if ($has_inverted_isset && $new_base_key === $key) {
|
||||
$new_base_type_candidate->addType(new TNull);
|
||||
$new_base_type_candidate = $new_base_type_candidate
|
||||
->getBuilder()
|
||||
->addType(new TNull)
|
||||
->freeze();
|
||||
}
|
||||
|
||||
$new_base_type_candidate->possibly_undefined = true;
|
||||
@ -963,11 +968,11 @@ class Reconciler
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Union|MutableUnion $existing_var_type
|
||||
* @param string[] $suppressed_issues
|
||||
*
|
||||
*/
|
||||
protected static function triggerIssueForImpossible(
|
||||
Union $existing_var_type,
|
||||
$existing_var_type,
|
||||
string $old_var_type_string,
|
||||
string $key,
|
||||
Assertion $assertion,
|
||||
@ -1161,7 +1166,7 @@ class Reconciler
|
||||
$base_atomic_type->properties[$array_key_offset] = clone $result_type;
|
||||
}
|
||||
|
||||
$new_base_type->addType($base_atomic_type);
|
||||
$new_base_type = $new_base_type->getBuilder()->addType($base_atomic_type)->freeze();
|
||||
|
||||
$changed_var_ids[$base_key . '[' . $array_key . ']'] = true;
|
||||
|
||||
@ -1181,8 +1186,9 @@ class Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
protected static function refineArrayKey(Union $key_type): void
|
||||
protected static function refineArrayKey(Union &$key_type): void
|
||||
{
|
||||
$key_type = $key_type->getBuilder();
|
||||
foreach ($key_type->getAtomicTypes() as $key => $cat) {
|
||||
if ($cat instanceof TTemplateParam) {
|
||||
self::refineArrayKey($cat->as);
|
||||
@ -1200,5 +1206,6 @@ class Reconciler
|
||||
// this should ideally prompt some sort of error
|
||||
$key_type->addType(new TArrayKey());
|
||||
}
|
||||
$key_type = $key_type->freeze();
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
1287
src/Psalm/Type/UnionTrait.php
Normal file
1287
src/Psalm/Type/UnionTrait.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -1098,7 +1098,7 @@ class ArrayAccessTest extends TestCase
|
||||
$_arr2[$index] = 5;',
|
||||
'assertions' => [
|
||||
'$_arr1===' => 'non-empty-array<1, 5>',
|
||||
'$_arr2===' => 'non-empty-array<1, 5>',
|
||||
'$_arr2===' => 'array{1: 5}',
|
||||
]
|
||||
],
|
||||
'accessArrayWithSingleStringLiteralOffset' => [
|
||||
|
@ -4457,7 +4457,7 @@ class ClassTemplateTest extends TestCase
|
||||
}
|
||||
$a = new A(function() { return "a";});
|
||||
$a->setCallback(function() { return "b";});',
|
||||
'error_message' => 'InvalidScalarArgument',
|
||||
'error_message' => 'InvalidArgument',
|
||||
],
|
||||
'preventBoundsMismatchDifferentContainers' => [
|
||||
'code' => '<?php
|
||||
|
Loading…
x
Reference in New Issue
Block a user