mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Merge pull request #7492 from zoonru/all_template_parameters
This commit is contained in:
commit
958e3cc12b
@ -122,6 +122,7 @@
|
||||
- [BC] Method `Psalm\Type\Union::hasFormerStaticObject()` was renamed to `hasStaticObject()`
|
||||
- [BC] Function assertions (from `@psalm-assert Foo $bar`) have been converted from strings to specific `Assertion` objects.
|
||||
- [BC] Property `Psalm\Storage\ClassLikeStorage::$invalid_dependencies` changed from `array<string>` to `array<string, true>`.
|
||||
- [BC] Property `Psalm\Storage\ClassLikeStorage::$template_extended_count` was renamed to `$template_type_extends_count`, its type was changed from `int|null` to `array<string, int>|null`.
|
||||
- [BC] Event classes became final and their constructors were marked `@internal`:
|
||||
- `Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent`
|
||||
- `Psalm\Plugin\EventHandler\Event\AfterAnalysisEvent`
|
||||
|
@ -6,5 +6,8 @@ implemented by implementing either `IteratorAggregate` or `Iterator`
|
||||
```php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @implements Traversable<mixed, mixed>
|
||||
*/
|
||||
final class C implements Traversable {} // will cause fatal error
|
||||
```
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="dev-master@093edcbe1d5c90ad574efcddd62fe741819e7cf2">
|
||||
<files psalm-version="dev-master@485e516088a22efd18e902926a2efdad1b040d72">
|
||||
<file src="examples/TemplateChecker.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="2">
|
||||
<code>$comment_block->tags['variablesfrom'][0]</code>
|
||||
@ -18,13 +18,6 @@
|
||||
<code>$symbol_parts[1]</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Config.php">
|
||||
<DeprecatedMethod occurrences="3">
|
||||
<code>getAdditionalFileExtensions</code>
|
||||
<code>getAdditionalFileTypeAnalyzers</code>
|
||||
<code>getAdditionalFileTypeScanners</code>
|
||||
</DeprecatedMethod>
|
||||
</file>
|
||||
<file src="src/Psalm/Config/FileFilter.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="1">
|
||||
<code>explode('::', $method_id)[1]</code>
|
||||
@ -38,10 +31,6 @@
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Analyzer/ClassAnalyzer.php">
|
||||
<DeprecatedProperty occurrences="2">
|
||||
<code>$storage->template_extended_count</code>
|
||||
<code>$storage->template_extended_count</code>
|
||||
</DeprecatedProperty>
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="3">
|
||||
<code>$comments[0]</code>
|
||||
<code>$stmt->props[0]</code>
|
||||
@ -61,11 +50,6 @@
|
||||
<code>$iterator_atomic_type->type_params[1]</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php">
|
||||
<ReferenceConstraintViolation occurrences="1">
|
||||
<code>$newly_reconciled_var_ids</code>
|
||||
</ReferenceConstraintViolation>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="1">
|
||||
<code>$pre_conditions[0]</code>
|
||||
@ -274,9 +258,6 @@
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php">
|
||||
<DeprecatedProperty occurrences="1">
|
||||
<code>$storage->template_extended_count</code>
|
||||
</DeprecatedProperty>
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="3">
|
||||
<code>$l[4]</code>
|
||||
<code>$r[4]</code>
|
||||
@ -361,12 +342,6 @@
|
||||
<code>VirtualConst</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="src/Psalm/PluginRegistrationSocket.php">
|
||||
<DeprecatedMethod occurrences="2">
|
||||
<code>addFileExtension</code>
|
||||
<code>addFileExtension</code>
|
||||
</DeprecatedMethod>
|
||||
</file>
|
||||
<file src="src/Psalm/Type/Atomic.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="1">
|
||||
<code>array_keys($template_type_map[$value])[0]</code>
|
||||
|
@ -39,14 +39,12 @@ use Psalm\Issue\InaccessibleMethod;
|
||||
use Psalm\Issue\InternalClass;
|
||||
use Psalm\Issue\InvalidEnumCaseValue;
|
||||
use Psalm\Issue\InvalidExtendClass;
|
||||
use Psalm\Issue\InvalidTemplateParam;
|
||||
use Psalm\Issue\InvalidTraversableImplementation;
|
||||
use Psalm\Issue\MethodSignatureMismatch;
|
||||
use Psalm\Issue\MismatchingDocblockPropertyType;
|
||||
use Psalm\Issue\MissingConstructor;
|
||||
use Psalm\Issue\MissingImmutableAnnotation;
|
||||
use Psalm\Issue\MissingPropertyType;
|
||||
use Psalm\Issue\MissingTemplateParam;
|
||||
use Psalm\Issue\MutableDependency;
|
||||
use Psalm\Issue\NoEnumProperties;
|
||||
use Psalm\Issue\NonInvariantDocblockPropertyType;
|
||||
@ -55,7 +53,6 @@ use Psalm\Issue\OverriddenPropertyAccess;
|
||||
use Psalm\Issue\ParseError;
|
||||
use Psalm\Issue\PropertyNotSetInConstructor;
|
||||
use Psalm\Issue\ReservedWord;
|
||||
use Psalm\Issue\TooManyTemplateParams;
|
||||
use Psalm\Issue\UndefinedClass;
|
||||
use Psalm\Issue\UndefinedInterface;
|
||||
use Psalm\Issue\UndefinedTrait;
|
||||
@ -87,7 +84,6 @@ use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_pop;
|
||||
use function array_search;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
@ -586,18 +582,16 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
|
||||
$fq_trait_name_lc = strtolower($fq_trait_name);
|
||||
|
||||
if (isset($storage->template_type_uses_count[$fq_trait_name_lc])) {
|
||||
$this->checkTemplateParams(
|
||||
$codebase,
|
||||
$storage,
|
||||
$trait_storage,
|
||||
new CodeLocation(
|
||||
$this,
|
||||
$trait
|
||||
),
|
||||
$storage->template_type_uses_count[$fq_trait_name_lc]
|
||||
);
|
||||
}
|
||||
$this->checkTemplateParams(
|
||||
$codebase,
|
||||
$storage,
|
||||
$trait_storage,
|
||||
new CodeLocation(
|
||||
$this,
|
||||
$trait
|
||||
),
|
||||
$storage->template_type_uses_count[$fq_trait_name_lc] ?? 0
|
||||
);
|
||||
|
||||
foreach ($trait_node->stmts as $trait_stmt) {
|
||||
if ($trait_stmt instanceof PhpParser\Node\Stmt\Property) {
|
||||
@ -1974,178 +1968,6 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
);
|
||||
}
|
||||
|
||||
private function checkTemplateParams(
|
||||
Codebase $codebase,
|
||||
ClassLikeStorage $storage,
|
||||
ClassLikeStorage $parent_storage,
|
||||
CodeLocation $code_location,
|
||||
int $given_param_count
|
||||
): void {
|
||||
$expected_param_count = $parent_storage->template_types === null
|
||||
? 0
|
||||
: count($parent_storage->template_types);
|
||||
|
||||
if ($expected_param_count > $given_param_count) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new MissingTemplateParam(
|
||||
$storage->name . ' has missing template params when extending ' . $parent_storage->name
|
||||
. ' , expecting ' . $expected_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} elseif ($expected_param_count < $given_param_count) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new TooManyTemplateParams(
|
||||
$storage->name . ' has too many template params when extending ' . $parent_storage->name
|
||||
. ' , expecting ' . $expected_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
|
||||
$storage_param_count = ($storage->template_types ? count($storage->template_types) : 0);
|
||||
|
||||
if ($parent_storage->enforce_template_inheritance
|
||||
&& $expected_param_count !== $storage_param_count
|
||||
) {
|
||||
if ($expected_param_count > $storage_param_count) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new MissingTemplateParam(
|
||||
$storage->name . ' requires the same number of template params as ' . $parent_storage->name
|
||||
. ' but saw ' . $storage_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} else {
|
||||
IssueBuffer::maybeAdd(
|
||||
new TooManyTemplateParams(
|
||||
$storage->name . ' requires the same number of template params as ' . $parent_storage->name
|
||||
. ' but saw ' . $storage_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent_storage->template_types && $storage->template_extended_params) {
|
||||
$i = 0;
|
||||
|
||||
$previous_extended = [];
|
||||
|
||||
foreach ($parent_storage->template_types as $template_name => $type_map) {
|
||||
// declares the variables
|
||||
foreach ($type_map as $declaring_class => $template_type) {
|
||||
}
|
||||
|
||||
if (isset($storage->template_extended_params[$parent_storage->name][$template_name])) {
|
||||
$extended_type = $storage->template_extended_params[$parent_storage->name][$template_name];
|
||||
|
||||
if (isset($parent_storage->template_covariants[$i])
|
||||
&& !$parent_storage->template_covariants[$i]
|
||||
) {
|
||||
foreach ($extended_type->getAtomicTypes() as $t) {
|
||||
if ($t instanceof TTemplateParam
|
||||
&& $storage->template_types
|
||||
&& $storage->template_covariants
|
||||
&& ($local_offset
|
||||
= array_search($t->param_name, array_keys($storage->template_types)))
|
||||
!== false
|
||||
&& !empty($storage->template_covariants[$local_offset])
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Cannot extend an invariant template param ' . $template_name
|
||||
. ' into a covariant context',
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent_storage->enforce_template_inheritance) {
|
||||
foreach ($extended_type->getAtomicTypes() as $t) {
|
||||
if (!$t instanceof TTemplateParam
|
||||
|| !isset($storage->template_types[$t->param_name])
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Cannot extend a strictly-enforced parent template param '
|
||||
. $template_name
|
||||
. ' with a non-template type',
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} elseif ($storage->template_types[$t->param_name][$storage->name]->getId()
|
||||
!== $template_type->getId()
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Cannot extend a strictly-enforced parent template param '
|
||||
. $template_name
|
||||
. ' with constraint ' . $template_type->getId()
|
||||
. ' with a child template param ' . $t->param_name
|
||||
. ' with different constraint '
|
||||
. $storage->template_types[$t->param_name][$storage->name]->getId(),
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$template_type->isMixed()) {
|
||||
$template_type_copy = clone $template_type;
|
||||
|
||||
$template_result = new TemplateResult(
|
||||
$previous_extended ?: [],
|
||||
[]
|
||||
);
|
||||
|
||||
$template_type_copy = TemplateStandinTypeReplacer::replace(
|
||||
$template_type_copy,
|
||||
$template_result,
|
||||
$codebase,
|
||||
null,
|
||||
$extended_type,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
if (!UnionTypeComparator::isContainedBy($codebase, $extended_type, $template_type_copy)) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Extended template param ' . $template_name
|
||||
. ' expects type ' . $template_type_copy->getId()
|
||||
. ', type ' . $extended_type->getId() . ' given',
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} else {
|
||||
$previous_extended[$template_name] = [
|
||||
$declaring_class => $extended_type
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$previous_extended[$template_name] = [
|
||||
$declaring_class => $extended_type
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Stmt\Class_|PhpParser\Node\Stmt\Enum_ $class
|
||||
*/
|
||||
@ -2234,15 +2056,13 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($storage->template_type_implements_count[$fq_interface_name_lc])) {
|
||||
$this->checkTemplateParams(
|
||||
$codebase,
|
||||
$storage,
|
||||
$interface_storage,
|
||||
$code_location,
|
||||
$storage->template_type_implements_count[$fq_interface_name_lc]
|
||||
);
|
||||
}
|
||||
$this->checkTemplateParams(
|
||||
$codebase,
|
||||
$storage,
|
||||
$interface_storage,
|
||||
$code_location,
|
||||
$storage->template_type_implements_count[$fq_interface_name_lc] ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($storage->class_implements as $fq_interface_name_lc => $fq_interface_name) {
|
||||
@ -2555,22 +2375,20 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
);
|
||||
}
|
||||
|
||||
if ($storage->template_extended_count !== null || $parent_class_storage->enforce_template_inheritance) {
|
||||
$code_location = new CodeLocation(
|
||||
$this,
|
||||
$class->name ?: $class,
|
||||
$class_context->include_location ?? null,
|
||||
true
|
||||
);
|
||||
$code_location = new CodeLocation(
|
||||
$this,
|
||||
$class->name ?: $class,
|
||||
$class_context->include_location ?? null,
|
||||
true
|
||||
);
|
||||
|
||||
$this->checkTemplateParams(
|
||||
$codebase,
|
||||
$storage,
|
||||
$parent_class_storage,
|
||||
$code_location,
|
||||
$storage->template_extended_count ?? 0
|
||||
);
|
||||
}
|
||||
$this->checkTemplateParams(
|
||||
$codebase,
|
||||
$storage,
|
||||
$parent_class_storage,
|
||||
$code_location,
|
||||
$storage->template_type_extends_count[$parent_fq_class_name] ?? 0
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -10,10 +10,16 @@ use Psalm\Codebase;
|
||||
use Psalm\Context;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use Psalm\Internal\Provider\NodeDataProvider;
|
||||
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
||||
use Psalm\Internal\Type\TemplateResult;
|
||||
use Psalm\Internal\Type\TemplateStandinTypeReplacer;
|
||||
use Psalm\Issue\InaccessibleProperty;
|
||||
use Psalm\Issue\InvalidClass;
|
||||
use Psalm\Issue\InvalidTemplateParam;
|
||||
use Psalm\Issue\MissingDependency;
|
||||
use Psalm\Issue\MissingTemplateParam;
|
||||
use Psalm\Issue\ReservedWord;
|
||||
use Psalm\Issue\TooManyTemplateParams;
|
||||
use Psalm\Issue\UndefinedAttributeClass;
|
||||
use Psalm\Issue\UndefinedClass;
|
||||
use Psalm\Issue\UndefinedDocblockClass;
|
||||
@ -22,10 +28,14 @@ use Psalm\Plugin\EventHandler\Event\AfterClassLikeExistenceCheckEvent;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Storage\ClassLikeStorage;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function array_keys;
|
||||
use function array_pop;
|
||||
use function array_search;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
@ -629,6 +639,178 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer
|
||||
return $emit_issues ? null : true;
|
||||
}
|
||||
|
||||
protected function checkTemplateParams(
|
||||
Codebase $codebase,
|
||||
ClassLikeStorage $storage,
|
||||
ClassLikeStorage $parent_storage,
|
||||
CodeLocation $code_location,
|
||||
int $given_param_count
|
||||
): void {
|
||||
$expected_param_count = $parent_storage->template_types === null
|
||||
? 0
|
||||
: count($parent_storage->template_types);
|
||||
|
||||
if ($expected_param_count > $given_param_count) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new MissingTemplateParam(
|
||||
$storage->name . ' has missing template params when extending ' . $parent_storage->name
|
||||
. ', expecting ' . $expected_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} elseif ($expected_param_count < $given_param_count) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new TooManyTemplateParams(
|
||||
$storage->name . ' has too many template params when extending ' . $parent_storage->name
|
||||
. ', expecting ' . $expected_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
|
||||
$storage_param_count = ($storage->template_types ? count($storage->template_types) : 0);
|
||||
|
||||
if ($parent_storage->enforce_template_inheritance
|
||||
&& $expected_param_count !== $storage_param_count
|
||||
) {
|
||||
if ($expected_param_count > $storage_param_count) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new MissingTemplateParam(
|
||||
$storage->name . ' requires the same number of template params as ' . $parent_storage->name
|
||||
. ' but saw ' . $storage_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} else {
|
||||
IssueBuffer::maybeAdd(
|
||||
new TooManyTemplateParams(
|
||||
$storage->name . ' requires the same number of template params as ' . $parent_storage->name
|
||||
. ' but saw ' . $storage_param_count,
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent_storage->template_types && $storage->template_extended_params) {
|
||||
$i = 0;
|
||||
|
||||
$previous_extended = [];
|
||||
|
||||
foreach ($parent_storage->template_types as $template_name => $type_map) {
|
||||
// declares the variables
|
||||
foreach ($type_map as $declaring_class => $template_type) {
|
||||
}
|
||||
|
||||
if (isset($storage->template_extended_params[$parent_storage->name][$template_name])) {
|
||||
$extended_type = $storage->template_extended_params[$parent_storage->name][$template_name];
|
||||
|
||||
if (isset($parent_storage->template_covariants[$i])
|
||||
&& !$parent_storage->template_covariants[$i]
|
||||
) {
|
||||
foreach ($extended_type->getAtomicTypes() as $t) {
|
||||
if ($t instanceof TTemplateParam
|
||||
&& $storage->template_types
|
||||
&& $storage->template_covariants
|
||||
&& ($local_offset
|
||||
= array_search($t->param_name, array_keys($storage->template_types)))
|
||||
!== false
|
||||
&& !empty($storage->template_covariants[$local_offset])
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Cannot extend an invariant template param ' . $template_name
|
||||
. ' into a covariant context',
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent_storage->enforce_template_inheritance) {
|
||||
foreach ($extended_type->getAtomicTypes() as $t) {
|
||||
if (!$t instanceof TTemplateParam
|
||||
|| !isset($storage->template_types[$t->param_name])
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Cannot extend a strictly-enforced parent template param '
|
||||
. $template_name
|
||||
. ' with a non-template type',
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} elseif ($storage->template_types[$t->param_name][$storage->name]->getId()
|
||||
!== $template_type->getId()
|
||||
) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Cannot extend a strictly-enforced parent template param '
|
||||
. $template_name
|
||||
. ' with constraint ' . $template_type->getId()
|
||||
. ' with a child template param ' . $t->param_name
|
||||
. ' with different constraint '
|
||||
. $storage->template_types[$t->param_name][$storage->name]->getId(),
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$template_type->isMixed()) {
|
||||
$template_type_copy = clone $template_type;
|
||||
|
||||
$template_result = new TemplateResult(
|
||||
$previous_extended ?: [],
|
||||
[]
|
||||
);
|
||||
|
||||
$template_type_copy = TemplateStandinTypeReplacer::replace(
|
||||
$template_type_copy,
|
||||
$template_result,
|
||||
$codebase,
|
||||
null,
|
||||
$extended_type,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
if (!UnionTypeComparator::isContainedBy($codebase, $extended_type, $template_type_copy)) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidTemplateParam(
|
||||
'Extended template param ' . $template_name
|
||||
. ' expects type ' . $template_type_copy->getId()
|
||||
. ', type ' . $extended_type->getId() . ' given',
|
||||
$code_location
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
);
|
||||
} else {
|
||||
$previous_extended[$template_name] = [
|
||||
$declaring_class => $extended_type
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$previous_extended[$template_name] = [
|
||||
$declaring_class => $extended_type
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
|
@ -43,6 +43,14 @@ class InterfaceAnalyzer extends ClassLikeAnalyzer
|
||||
$codebase = $project_analyzer->getCodebase();
|
||||
$config = $project_analyzer->getConfig();
|
||||
|
||||
$fq_interface_name = $this->getFQCLN();
|
||||
|
||||
if (!$fq_interface_name) {
|
||||
throw new UnexpectedValueException('bad');
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($fq_interface_name);
|
||||
|
||||
if ($this->class->extends) {
|
||||
foreach ($this->class->extends as $extended_interface) {
|
||||
$extended_interface_name = self::getFQCLNFromNameObject(
|
||||
@ -66,12 +74,12 @@ class InterfaceAnalyzer extends ClassLikeAnalyzer
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$extended_interface_storage->is_interface) {
|
||||
$code_location = new CodeLocation(
|
||||
$this,
|
||||
$extended_interface
|
||||
);
|
||||
$code_location = new CodeLocation(
|
||||
$this,
|
||||
$extended_interface
|
||||
);
|
||||
|
||||
if (!$extended_interface_storage->is_interface) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new UndefinedInterface(
|
||||
$extended_interface_name . ' is not an interface',
|
||||
@ -92,17 +100,17 @@ class InterfaceAnalyzer extends ClassLikeAnalyzer
|
||||
$extended_interface_name
|
||||
);
|
||||
}
|
||||
|
||||
$this->checkTemplateParams(
|
||||
$codebase,
|
||||
$class_storage,
|
||||
$extended_interface_storage,
|
||||
$code_location,
|
||||
$class_storage->template_type_extends_count[$extended_interface_name] ?? 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$fq_interface_name = $this->getFQCLN();
|
||||
|
||||
if (!$fq_interface_name) {
|
||||
throw new UnexpectedValueException('bad');
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($fq_interface_name);
|
||||
|
||||
foreach ($class_storage->attributes as $attribute) {
|
||||
AttributeAnalyzer::analyze(
|
||||
$this,
|
||||
|
@ -988,8 +988,8 @@ class ClassLikeNodeScanner
|
||||
|
||||
$extended_type_parameters = [];
|
||||
|
||||
$storage->template_extended_count = count($atomic_type->type_params);
|
||||
|
||||
$storage->template_type_extends_count[$atomic_type->value] = count($atomic_type->type_params);
|
||||
|
||||
foreach ($atomic_type->type_params as $type_param) {
|
||||
$extended_type_parameters[] = $type_param;
|
||||
}
|
||||
|
@ -361,10 +361,10 @@ class ClassLikeStorage
|
||||
public $template_extended_params;
|
||||
|
||||
/**
|
||||
* @deprecated Will be replaced with $template_type_extends_count in Psalm v5
|
||||
* @var ?int
|
||||
* @var array<string, int>|null
|
||||
*/
|
||||
public $template_extended_count;
|
||||
public $template_type_extends_count;
|
||||
|
||||
|
||||
/**
|
||||
* @var array<string, int>|null
|
||||
|
@ -788,6 +788,9 @@ class ArrayAccessTest extends TestCase
|
||||
|
||||
$c = isset($array["foo"]) ? $array["foo"] : null;
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class C implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
@ -944,6 +947,9 @@ class ArrayAccessTest extends TestCase
|
||||
public function foo() : void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class C implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
|
@ -689,6 +689,9 @@ class ArrayAssignmentTest extends TestCase
|
||||
],
|
||||
'implementsArrayAccess' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements \ArrayAccess<array-key, mixed>
|
||||
*/
|
||||
class A implements \ArrayAccess {
|
||||
/**
|
||||
* @param string|int $offset
|
||||
@ -734,6 +737,9 @@ class ArrayAssignmentTest extends TestCase
|
||||
],
|
||||
'implementsArrayAccessInheritingDocblock' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements \ArrayAccess<string, mixed>
|
||||
*/
|
||||
class A implements \ArrayAccess
|
||||
{
|
||||
/**
|
||||
|
@ -208,6 +208,9 @@ class AttributeTest extends TestCase
|
||||
use IteratorAggregate;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
final class EmptyCollection implements IteratorAggregate
|
||||
{
|
||||
#[ReturnTypeWillChange]
|
||||
@ -229,6 +232,9 @@ class AttributeTest extends TestCase
|
||||
use IteratorAggregate;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
final class EmptyCollection implements IteratorAggregate
|
||||
{
|
||||
#[ReturnTypeWillChange]
|
||||
|
@ -613,6 +613,10 @@ class ClassTest extends TestCase
|
||||
],
|
||||
'allowTraversableImplementationAlongWithIteratorAggregate' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements Traversable<int, 1>
|
||||
* @implements IteratorAggregate<int, 1>
|
||||
*/
|
||||
final class C implements Traversable, IteratorAggregate {
|
||||
public function getIterator() {
|
||||
yield 1;
|
||||
@ -622,6 +626,10 @@ class ClassTest extends TestCase
|
||||
],
|
||||
'allowTraversableImplementationAlongWithIterator' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements Traversable<1, 1>
|
||||
* @implements Iterator<1, 1>
|
||||
*/
|
||||
final class C implements Traversable, Iterator {
|
||||
public function current() { return 1; }
|
||||
public function key() { return 1; }
|
||||
@ -633,11 +641,20 @@ class ClassTest extends TestCase
|
||||
],
|
||||
'allowTraversableImplementationOnAbstractClass' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
*
|
||||
* @implements Traversable<TKey, TValue>
|
||||
*/
|
||||
abstract class C implements Traversable {}
|
||||
',
|
||||
],
|
||||
'allowIndirectTraversableImplementationOnAbstractClass' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @extends Traversable<int, int>
|
||||
*/
|
||||
interface I extends Traversable {}
|
||||
abstract class C implements I {}
|
||||
',
|
||||
@ -803,7 +820,15 @@ class ClassTest extends TestCase
|
||||
],
|
||||
'abstractReflectedClassMethod' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @extends FilterIterator<TKey, TValue, Iterator<TKey, TValue>>
|
||||
*/
|
||||
class DedupeIterator extends FilterIterator {
|
||||
/**
|
||||
* @param Iterator<TKey, TValue> $i
|
||||
*/
|
||||
public function __construct(Iterator $i) {
|
||||
parent::__construct($i);
|
||||
}
|
||||
@ -908,17 +933,77 @@ class ClassTest extends TestCase
|
||||
],
|
||||
'preventTraversableImplementation' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements Traversable<int, int>
|
||||
*/
|
||||
final class C implements Traversable {}
|
||||
',
|
||||
'error_message' => 'InvalidTraversableImplementation',
|
||||
],
|
||||
'preventIndirectTraversableImplementation' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @extends Traversable<int, int>
|
||||
*/
|
||||
interface I extends Traversable {}
|
||||
final class C implements I {}
|
||||
',
|
||||
'error_message' => 'InvalidTraversableImplementation',
|
||||
],
|
||||
'detectMissingTemplateExtends' => [
|
||||
'code' => '<?php
|
||||
/** @template T */
|
||||
abstract class A {}
|
||||
final class B extends A {}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
'detectMissingTemplateImplements' => [
|
||||
'code' => '<?php
|
||||
/** @template T */
|
||||
interface A {}
|
||||
final class B implements A {}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
'detectMissingTemplateUse' => [
|
||||
'code' => '<?php
|
||||
/** @template T */
|
||||
trait A {}
|
||||
final class B {
|
||||
use A;
|
||||
}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
|
||||
'detectMissingTemplateExtendsNative' => [
|
||||
'code' => '<?php
|
||||
final class C extends ArrayObject {}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
|
||||
'detectMissingTemplateImplementsNative' => [
|
||||
'code' => '<?php
|
||||
final class C implements Iterator {
|
||||
public function current(): mixed {
|
||||
return 0;
|
||||
}
|
||||
public function key(): mixed {
|
||||
return 0;
|
||||
}
|
||||
public function next(): void {
|
||||
}
|
||||
public function rewind(): void {
|
||||
}
|
||||
public function valid(): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -517,6 +517,9 @@ class FunctionCallTest extends TestCase
|
||||
],
|
||||
'iteratorToArrayWithGetIterator' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements IteratorAggregate<int, string>
|
||||
*/
|
||||
class C implements IteratorAggregate {
|
||||
/**
|
||||
* @return Traversable<int,string>
|
||||
@ -532,9 +535,12 @@ class FunctionCallTest extends TestCase
|
||||
],
|
||||
'iteratorToArrayWithGetIteratorReturningList' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements IteratorAggregate<int, string>
|
||||
*/
|
||||
class C implements IteratorAggregate {
|
||||
/**
|
||||
* @return Traversable<int,string>
|
||||
* @return Traversable<int, string>
|
||||
*/
|
||||
public function getIterator() {
|
||||
yield 1 => "1";
|
||||
|
@ -307,6 +307,11 @@ class InterfaceTest extends TestCase
|
||||
],
|
||||
'extendIteratorIterator' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @extends IteratorIterator<TKey, TValue, Traversable<TKey, TValue>>
|
||||
*/
|
||||
class SomeIterator extends IteratorIterator {}',
|
||||
],
|
||||
'SKIPPED-suppressMismatch' => [
|
||||
@ -395,6 +400,10 @@ class InterfaceTest extends TestCase
|
||||
],
|
||||
'interfaceExtendsTraversible' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @extends IteratorAggregate<mixed, mixed>
|
||||
* @extends ArrayAccess<mixed, mixed>
|
||||
*/
|
||||
interface Collection extends Countable, IteratorAggregate, ArrayAccess {}
|
||||
|
||||
function takesCollection(Collection $c): void {
|
||||
@ -429,8 +438,14 @@ class InterfaceTest extends TestCase
|
||||
],
|
||||
'filterIteratorExtension' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @extends Iterator<mixed, mixed>
|
||||
*/
|
||||
interface I2 extends Iterator {}
|
||||
|
||||
/**
|
||||
* @extends FilterIterator<mixed, mixed, Iterator<mixed, mixed>>
|
||||
*/
|
||||
class DedupeIterator extends FilterIterator {
|
||||
public function __construct(I2 $i) {
|
||||
parent::__construct($i);
|
||||
@ -964,6 +979,31 @@ class InterfaceTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'MissingParamType'
|
||||
],
|
||||
'missingTemplateExtendsInterface' => [
|
||||
'code' => '<?php
|
||||
/** @template T */
|
||||
interface A {}
|
||||
interface B extends A {}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
'missingTemplateExtendsNativeInterface' => [
|
||||
'code' => '<?php
|
||||
interface a extends Iterator {
|
||||
}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
'missingTemplateExtendsNativeMultipleInterface' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @extends Iterator<mixed, mixed>
|
||||
*/
|
||||
interface a extends Iterator, Traversable {
|
||||
}
|
||||
',
|
||||
'error_message' => 'MissingTemplateParam',
|
||||
],
|
||||
'reconcileAfterClassInstanceof' => [
|
||||
'code' => '<?php
|
||||
interface Base {}
|
||||
|
@ -686,6 +686,9 @@ class ForeachTest extends TestCase
|
||||
],
|
||||
'iteratorAggregateIteration' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class C implements IteratorAggregate
|
||||
{
|
||||
public function getIterator(): Iterator
|
||||
@ -790,6 +793,9 @@ class ForeachTest extends TestCase
|
||||
public $prop = "var";
|
||||
}
|
||||
|
||||
/**
|
||||
* @implements IteratorAggregate<array-key, Item>
|
||||
*/
|
||||
class Collection implements IteratorAggregate {
|
||||
/**
|
||||
* @var Item[]
|
||||
@ -878,6 +884,9 @@ class ForeachTest extends TestCase
|
||||
'code' => '<?php
|
||||
class Value {}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class ValueCollection implements \Countable, \IteratorAggregate {
|
||||
/**
|
||||
* @var Value[]
|
||||
@ -924,6 +933,9 @@ class ForeachTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class ValueCollectionIterator implements \Countable, \Iterator
|
||||
{
|
||||
/**
|
||||
|
@ -59,6 +59,7 @@ class MagicMethodAnnotationTest extends TestCase
|
||||
public function find() {}
|
||||
}
|
||||
|
||||
/** @psalm-suppress MissingTemplateParam */
|
||||
class B extends A {}
|
||||
|
||||
class Obj {}
|
||||
|
@ -604,6 +604,9 @@ class MethodSignatureTest extends TestCase
|
||||
],
|
||||
'allowMixedExtensionOfIteratorAggregate' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class C implements IteratorAggregate {
|
||||
public function getIterator(): Iterator {
|
||||
return new ArrayIterator([]);
|
||||
|
@ -118,6 +118,9 @@ class MixinAnnotationTest extends TestCase
|
||||
],
|
||||
'wrapCustomIterator' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements Iterator<1, 2>
|
||||
*/
|
||||
class Subject implements Iterator {
|
||||
/**
|
||||
* the index method exists
|
||||
|
@ -197,6 +197,9 @@ class Php71Test extends TestCase
|
||||
],
|
||||
'traversableObject' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @implements Iterator<0, mixed>
|
||||
*/
|
||||
class IteratorObj implements Iterator {
|
||||
function rewind(): void {}
|
||||
/** @return mixed */
|
||||
|
@ -335,6 +335,9 @@ class PropertyTypeInvarianceTest extends TestCase
|
||||
protected $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class FooPair extends Pair
|
||||
{
|
||||
/** @var Foo|null */ // Template defaults to mixed, this is invariant
|
||||
|
@ -1186,6 +1186,9 @@ class StubTest extends TestCase
|
||||
public function find($id, $lockMode = null, $lockVersion = null) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class B extends A {}
|
||||
|
||||
class Obj {}
|
||||
|
@ -679,6 +679,9 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
*/
|
||||
class Collection1 extends ArrayIterator{}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class Collection2 extends Collection1{}
|
||||
|
||||
class Collection3 extends Collection2{}
|
||||
@ -1372,6 +1375,8 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class C implements I {
|
||||
/** @var array<T> */
|
||||
@ -1411,6 +1416,8 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
|
||||
/**
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class C implements I {
|
||||
/** @var array<T2> */
|
||||
@ -2059,9 +2066,17 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
public function getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
* @implements ICollection<TKey, TValue>
|
||||
*/
|
||||
class Collection implements ICollection {
|
||||
/** @var array */
|
||||
/** @var array<TKey, TValue> */
|
||||
private $data;
|
||||
/**
|
||||
* @param array<TKey, TValue> $data
|
||||
*/
|
||||
public function __construct(array $data) {
|
||||
$this->data = $data;
|
||||
}
|
||||
@ -2070,7 +2085,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/** @var ICollection<string, int> */
|
||||
$c = new Collection(["a" => 1]);
|
||||
|
||||
foreach ($c->getIterator() as $k => $v) { atan($v); strlen($k); }',
|
||||
@ -2233,6 +2247,11 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TT
|
||||
*
|
||||
* @extends Container<TT>
|
||||
*/
|
||||
class MyContainer extends Container {}
|
||||
|
||||
$a = (new MyContainer("hello"))->getAnother();',
|
||||
@ -2608,6 +2627,9 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class AChild extends A {
|
||||
/**
|
||||
* @template T3
|
||||
@ -3707,6 +3729,9 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
public function foo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class Concrete implements Templated {
|
||||
private array $t;
|
||||
|
||||
|
@ -531,24 +531,37 @@ class ClassTemplateTest extends TestCase
|
||||
/**
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
*
|
||||
* @extends \IteratorAggregate<TKey, TValue>
|
||||
*/
|
||||
interface ICollection extends \IteratorAggregate {
|
||||
/** @return \Traversable<TKey,TValue> */
|
||||
public function getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey as array-key
|
||||
* @template TValue
|
||||
*
|
||||
* @implements ICollection<TKey, TValue>
|
||||
*/
|
||||
class Collection implements ICollection {
|
||||
/** @var array */
|
||||
/** @var array<TKey, TValue> */
|
||||
private $data;
|
||||
/**
|
||||
* @param array<TKey, TValue> $data
|
||||
*/
|
||||
public function __construct(array $data) {
|
||||
$this->data = $data;
|
||||
}
|
||||
/**
|
||||
* @return \Traversable<TKey, TValue>
|
||||
*/
|
||||
public function getIterator(): \Traversable {
|
||||
return new \ArrayIterator($this->data);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var ICollection<string, int> */
|
||||
$c = new Collection(["a" => 1]);
|
||||
|
||||
foreach ($c as $k => $v) { atan($v); strlen($k); }',
|
||||
@ -862,6 +875,7 @@ class ClassTemplateTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
*/
|
||||
@ -926,6 +940,8 @@ class ClassTemplateTest extends TestCase
|
||||
/**
|
||||
* @template TKey
|
||||
* @template Tv
|
||||
*
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class KeyValueContainer extends ValueContainer
|
||||
{
|
||||
@ -985,6 +1001,9 @@ class ClassTemplateTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class AppUser extends User {}
|
||||
|
||||
$au = new AppUser(-1);
|
||||
@ -1732,6 +1751,7 @@ class ClassTemplateTest extends TestCase
|
||||
function foo();
|
||||
}
|
||||
|
||||
/** @psalm-suppress MissingTemplateParam */
|
||||
interface IChild extends IParent {}
|
||||
|
||||
class C {}
|
||||
@ -1766,6 +1786,8 @@ class ClassTemplateTest extends TestCase
|
||||
* @psalm-param class-string<T> $className
|
||||
*
|
||||
* @psalm-return T&I<T>
|
||||
*
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
function makeConcrete(string $className) : object
|
||||
{
|
||||
@ -1926,6 +1948,7 @@ class ClassTemplateTest extends TestCase
|
||||
/** @psalm-param T $val */
|
||||
public function set($val) : void {
|
||||
$this->value = $val;
|
||||
/** @psalm-suppress MissingTemplateParam */
|
||||
new class extends Foo {};
|
||||
}
|
||||
|
||||
|
@ -480,6 +480,9 @@ class FunctionTemplateTest extends TestCase
|
||||
'code' => '<?php
|
||||
class Foo {}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class SomeIterator implements IteratorAggregate
|
||||
{
|
||||
public function getIterator() {
|
||||
|
@ -725,6 +725,9 @@ class TraitTemplateTest extends TestCase
|
||||
use T1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class D extends C {
|
||||
/**
|
||||
* @param mixed $offset
|
||||
|
@ -739,7 +739,7 @@ class UnusedCodeTest extends TestCase
|
||||
],
|
||||
'useIteratorMethodsWhenCallingForeach' => [
|
||||
'code' => '<?php
|
||||
/** @psalm-suppress UnimplementedInterfaceMethod */
|
||||
/** @psalm-suppress UnimplementedInterfaceMethod, MissingTemplateParam */
|
||||
class IterableResult implements \Iterator {
|
||||
public function current() {
|
||||
return null;
|
||||
|
@ -2255,6 +2255,7 @@ class UnusedVariableTest extends TestCase
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @psalm-immutable
|
||||
* @psalm-suppress MissingTemplateParam
|
||||
*/
|
||||
class A implements IteratorAggregate
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user