Skip to content

Commit

Permalink
Immutable Unions
Browse files Browse the repository at this point in the history
  • Loading branch information
danog committed Oct 3, 2022
1 parent 028ac7f commit 3abd0b9
Show file tree
Hide file tree
Showing 74 changed files with 2,134 additions and 1,725 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/bcc.yml
Expand Up @@ -13,7 +13,7 @@ jobs:
tools: composer:v2
coverage: none

- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0

Expand All @@ -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 }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -12,3 +12,4 @@
/tests/fixtures/symlinktest/*

.idea/
.vscode/
9 changes: 9 additions & 0 deletions UPGRADING.md
@@ -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
Expand Down
62 changes: 36 additions & 26 deletions psalm-baseline.xml
@@ -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-&gt;tags['variablesfrom'][0]</code>
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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-&gt;array_type_params[1]</code>
Expand All @@ -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>
Expand All @@ -346,12 +332,36 @@
<code>$this-&gt;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>
Expand Down
14 changes: 11 additions & 3 deletions src/Psalm/Context.php
Expand Up @@ -485,7 +485,10 @@ public function update(
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();
Expand Down Expand Up @@ -770,18 +773,23 @@ public function removeDescendents(
$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();
}
}
}

Expand Down
12 changes: 8 additions & 4 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Expand Up @@ -791,12 +791,12 @@ public static function addContextProperties(

$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
Expand Down Expand Up @@ -1294,7 +1294,11 @@ static function (FunctionLikeParameter $param): PhpParser\Node\Arg {
);
} 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();
}
}
Expand Down Expand Up @@ -1543,7 +1547,7 @@ private function analyzeProperty(
}

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()) {
Expand Down
7 changes: 4 additions & 3 deletions src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php
Expand Up @@ -992,8 +992,9 @@ private function processParams(
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(
Expand Down Expand Up @@ -1844,9 +1845,9 @@ private function getFunctionInformation(
$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);
}
}

Expand Down
23 changes: 14 additions & 9 deletions src/Psalm/Internal/Analyzer/MethodComparator.php
Expand Up @@ -362,7 +362,7 @@ private static function compareMethodParams(
$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) {
Expand Down Expand Up @@ -729,29 +729,34 @@ private static function compareMethodDocblockParams(
}
}

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(
Expand Down Expand Up @@ -1055,7 +1060,7 @@ private static function compareMethodDocblockReturnTypes(
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])) {
Expand Down Expand Up @@ -1092,7 +1097,7 @@ private static function transformTemplates(

$template_result = new TemplateResult([], $template_types);

TemplateInferredTypeReplacer::replace(
$templated_type = TemplateInferredTypeReplacer::replace(
$templated_type,
$template_result,
$codebase
Expand Down
Expand Up @@ -225,10 +225,10 @@ public static function analyze(
}

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();
}
}

Expand Down
Expand Up @@ -218,7 +218,9 @@ public static function updateArrayType(
$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;
Expand Down Expand Up @@ -295,7 +297,7 @@ private static function updateTypeWithKeyValues(
$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) {
Expand Down Expand Up @@ -351,7 +353,7 @@ private static function updateTypeWithKeyValues(
}
}

$child_stmt_type->bustCache();
$child_stmt_type = $child_stmt_type->freeze();

if (!$has_matching_objectlike_property && !$has_matching_string) {
if (count($key_values) === 1) {
Expand Down Expand Up @@ -511,9 +513,7 @@ private static function updateArrayAssignmentChildType(
}
}

$array_atomic_key_type = ArrayFetchAnalyzer::replaceOffsetTypeWithInts(
$key_type
);
$array_atomic_key_type = ArrayFetchAnalyzer::replaceOffsetTypeWithInts($key_type);
} else {
$array_atomic_key_type = Type::getArrayKey();
}
Expand Down Expand Up @@ -557,7 +557,7 @@ private static function updateArrayAssignmentChildType(
]
);

TemplateInferredTypeReplacer::replace(
$value_type = TemplateInferredTypeReplacer::replace(
$value_type,
$template_result,
$codebase
Expand Down Expand Up @@ -742,17 +742,21 @@ private static function analyzeNestedArrayAssignment(

$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,
Expand Down Expand Up @@ -886,8 +890,10 @@ private static function analyzeNestedArrayAssignment(
);
}

$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;
Expand Down

0 comments on commit 3abd0b9

Please sign in to comment.