Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Immutable assertions #8443

Merged
merged 3 commits into from Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
69 changes: 43 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@2488c523963efcc6d8a1a6a8874d08bd0d7a40cf">
<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 @@ -345,13 +331,44 @@
<PossiblyUndefinedIntArrayOffset occurrences="1">
<code>$this-&gt;type_params[1]</code>
</PossiblyUndefinedIntArrayOffset>
<PossiblyUnusedMethod occurrences="3">
<code>replaceTypeParams</code>
<code>replaceTypeParams</code>
<code>replaceTypeParams</code>
</PossiblyUnusedMethod>
</file>
<file src="src/Psalm/Type/Atomic/TTemplateParam.php">
<PossiblyUnusedMethod occurrences="1">
<code>replaceAs</code>
</PossiblyUnusedMethod>
</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
14 changes: 9 additions & 5 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Expand Up @@ -758,7 +758,7 @@ public static function addContextProperties(

// Get actual types used for templates (to support @template-covariant)
$template_standins = new TemplateResult($lower_bounds, []);
TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
$guide_property_type,
$template_standins,
$codebase,
Expand Down 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
9 changes: 5 additions & 4 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 @@ -1836,17 +1837,17 @@ private function getFunctionInformation(
if ($this->storage instanceof MethodStorage && $this->storage->if_this_is_type) {
$template_result = new TemplateResult($this->getTemplateTypeMap() ?? [], []);

TemplateStandinTypeReplacer::replace(
TemplateStandinTypeReplacer::fillTemplateResult(
new Union([$this_object_type]),
$template_result,
$codebase,
null,
$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 @@ -273,7 +273,7 @@ static function (string $fq_catch_class) use ($codebase): TNamedObject {
&& $codebase->interfaceExists($fq_catch_class)
&& !$codebase->interfaceExtends($fq_catch_class, 'Throwable')
) {
$catch_class_type->addIntersectionType(new TNamedObject('Throwable'));
return $catch_class_type->addIntersectionType(new TNamedObject('Throwable'));
}

return $catch_class_type;
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