1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 22:01:48 +01:00

Merge pull request #7110 from AndrolGenhald/bugfix/stub-parent-class

Fix stub parent class not loaded.
This commit is contained in:
orklah 2022-01-22 21:17:07 +01:00 committed by GitHub
commit c3ad520b1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 353 additions and 272 deletions

View File

@ -120,6 +120,7 @@
- [BC] Method `Psalm\Type\Union::isFormerStaticObject()` was renamed to `isStaticObject()`
- [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>`.
## Removed
- [BC] Property `Psalm\Codebase::$php_major_version` was removed, use

View File

@ -336,7 +336,7 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer
return null;
}
foreach ($class_storage->invalid_dependencies as $dependency_class_name) {
foreach ($class_storage->invalid_dependencies as $dependency_class_name => $_) {
// if the implemented/extended class is stubbed, it may not yet have
// been hydrated
if ($codebase->classlike_storage_provider->has($dependency_class_name)) {

View File

@ -20,8 +20,10 @@ use function array_filter;
use function array_intersect_key;
use function array_keys;
use function array_merge;
use function array_splice;
use function count;
use function in_array;
use function key;
use function reset;
use function strlen;
use function strpos;
@ -139,10 +141,17 @@ class Populator
$dependent_classlikes[$fq_classlike_name_lc] = true;
$this->populateDataFromTraits($storage, $storage_provider, $dependent_classlikes);
foreach ($storage->used_traits as $used_trait_lc => $_) {
$this->populateDataFromTrait($storage, $storage_provider, $dependent_classlikes, $used_trait_lc);
}
if ($storage->parent_classes) {
$this->populateDataFromParentClass($storage, $storage_provider, $dependent_classlikes);
$this->populateDataFromParentClass(
$storage,
$storage_provider,
$dependent_classlikes,
reset($storage->parent_classes)
);
}
if (!strpos($fq_classlike_name_lc, '\\')
@ -154,9 +163,23 @@ class Populator
$storage->methods['__construct'] = $storage->methods[$fq_classlike_name_lc];
}
$this->populateInterfaceDataFromParentInterfaces($storage, $storage_provider, $dependent_classlikes);
foreach ($storage->direct_interface_parents as $parent_interface_lc => $_) {
$this->populateInterfaceDataFromParentInterface(
$storage,
$storage_provider,
$dependent_classlikes,
$parent_interface_lc
);
}
$this->populateDataFromImplementedInterfaces($storage, $storage_provider, $dependent_classlikes);
foreach ($storage->direct_class_interfaces as $implemented_interface_lc => $_) {
$this->populateDataFromImplementedInterface(
$storage,
$storage_provider,
$dependent_classlikes,
$implemented_interface_lc
);
}
if ($storage->location) {
$file_path = $storage->location->file_path;
@ -222,7 +245,7 @@ class Populator
}
}
$this->populateOverriddenMethods($storage);
$this->populateOverriddenMethods($storage, $storage_provider);
$this->progress->debug('Have populated ' . $storage->name . "\n");
@ -230,7 +253,29 @@ class Populator
if (isset($this->invalid_class_storages[$fq_classlike_name_lc])) {
foreach ($this->invalid_class_storages[$fq_classlike_name_lc] as $dependency) {
// Dependencies may not be fully set yet, so we have to loop through dependencies of dependencies
$dependencies = [strtolower($dependency->name) => true];
do {
$current_dependency_name = key(array_splice($dependencies, 0, 1)); // Key shift
$current_dependency = $storage_provider->get($current_dependency_name);
$dependencies += $current_dependency->dependent_classlikes;
if (isset($current_dependency->dependent_classlikes[$fq_classlike_name_lc])) {
if ($dependency->location) {
IssueBuffer::maybeAdd(
new CircularReference(
'Circular reference discovered when loading ' . $dependency->name,
$dependency->location
)
);
}
continue 2;
}
} while (!empty($dependencies));
$dependency->populated = false;
unset($dependency->invalid_dependencies[$fq_classlike_name_lc]);
$this->populateClassLikeStorage($dependency, $dependent_classlikes);
}
@ -239,8 +284,65 @@ class Populator
}
private function populateOverriddenMethods(
ClassLikeStorage $storage
ClassLikeStorage $storage,
ClassLikeStorageProvider $storage_provider
): void {
$interface_method_implementers = [];
foreach ($storage->class_implements as $interface) {
try {
$implemented_interface = strtolower(
$this->classlikes->getUnAliasedName(
$interface
)
);
$implemented_interface_storage = $storage_provider->get($implemented_interface);
} catch (InvalidArgumentException $e) {
continue;
}
$implemented_interface_storage->dependent_classlikes[strtolower($storage->name)] = true;
foreach ($implemented_interface_storage->methods as $method_name => $method) {
if ($method->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC) {
$interface_method_implementers[$method_name][] = new MethodIdentifier(
$implemented_interface_storage->name,
$method_name
);
}
}
}
foreach ($interface_method_implementers as $method_name => $interface_method_ids) {
if (count($interface_method_ids) === 1) {
if (isset($storage->methods[$method_name])) {
$method_storage = $storage->methods[$method_name];
if ($method_storage->signature_return_type
&& !$method_storage->signature_return_type->isVoid()
&& $method_storage->return_type === $method_storage->signature_return_type
) {
$interface_fqcln = $interface_method_ids[0]->fq_class_name;
$interface_storage = $storage_provider->get($interface_fqcln);
if (isset($interface_storage->methods[$method_name])) {
$interface_method_storage = $interface_storage->methods[$method_name];
if ($interface_method_storage->throws
&& (!$method_storage->throws || $method_storage->inheritdoc)
) {
$method_storage->throws += $interface_method_storage->throws;
}
}
}
}
}
foreach ($interface_method_ids as $interface_method_id) {
$storage->overridden_method_ids[$method_name][$interface_method_id->fq_class_name]
= $interface_method_id;
}
}
$storage->documenting_method_ids = [];
foreach ($storage->methods as $method_name => $method_storage) {
@ -332,75 +434,74 @@ class Populator
}
}
private function populateDataFromTraits(
private function populateDataFromTrait(
ClassLikeStorage $storage,
ClassLikeStorageProvider $storage_provider,
array $dependent_classlikes
array $dependent_classlikes,
string $used_trait_lc
): void {
foreach ($storage->used_traits as $used_trait_lc => $_) {
try {
$used_trait_lc = strtolower(
$this->classlikes->getUnAliasedName(
$used_trait_lc
)
);
$trait_storage = $storage_provider->get($used_trait_lc);
} catch (InvalidArgumentException $e) {
continue;
}
try {
$used_trait_lc = strtolower(
$this->classlikes->getUnAliasedName(
$used_trait_lc
)
);
$trait_storage = $storage_provider->get($used_trait_lc);
} catch (InvalidArgumentException $e) {
return;
}
$this->populateClassLikeStorage($trait_storage, $dependent_classlikes);
$this->populateClassLikeStorage($trait_storage, $dependent_classlikes);
$this->inheritMethodsFromParent($storage, $trait_storage);
$this->inheritPropertiesFromParent($storage, $trait_storage);
$this->inheritMethodsFromParent($storage, $trait_storage);
$this->inheritPropertiesFromParent($storage, $trait_storage);
if ($trait_storage->template_types) {
$storage->template_extended_params[$trait_storage->name] = [];
if ($trait_storage->template_types) {
$storage->template_extended_params[$trait_storage->name] = [];
if (isset($storage->template_extended_offsets[$trait_storage->name])) {
foreach ($storage->template_extended_offsets[$trait_storage->name] as $i => $type) {
$trait_template_type_names = array_keys($trait_storage->template_types);
if (isset($storage->template_extended_offsets[$trait_storage->name])) {
foreach ($storage->template_extended_offsets[$trait_storage->name] as $i => $type) {
$trait_template_type_names = array_keys($trait_storage->template_types);
$mapped_name = $trait_template_type_names[$i] ?? null;
$mapped_name = $trait_template_type_names[$i] ?? null;
if ($mapped_name) {
$storage->template_extended_params[$trait_storage->name][$mapped_name] = $type;
}
if ($mapped_name) {
$storage->template_extended_params[$trait_storage->name][$mapped_name] = $type;
}
}
if ($trait_storage->template_extended_params) {
foreach ($trait_storage->template_extended_params as $t_storage_class => $type_map) {
foreach ($type_map as $i => $type) {
$storage->template_extended_params[$t_storage_class][$i] = self::extendType(
$type,
$storage
);
}
}
}
} else {
foreach ($trait_storage->template_types as $template_name => $template_type_map) {
foreach ($template_type_map as $template_type) {
$default_param = clone $template_type;
$default_param->from_docblock = false;
$storage->template_extended_params[$trait_storage->name][$template_name]
= $default_param;
if ($trait_storage->template_extended_params) {
foreach ($trait_storage->template_extended_params as $t_storage_class => $type_map) {
foreach ($type_map as $i => $type) {
$storage->template_extended_params[$t_storage_class][$i] = self::extendType(
$type,
$storage
);
}
}
}
} elseif ($trait_storage->template_extended_params) {
$storage->template_extended_params = array_merge(
$storage->template_extended_params ?: [],
$trait_storage->template_extended_params
);
} else {
foreach ($trait_storage->template_types as $template_name => $template_type_map) {
foreach ($template_type_map as $template_type) {
$default_param = clone $template_type;
$default_param->from_docblock = false;
$storage->template_extended_params[$trait_storage->name][$template_name]
= $default_param;
}
}
}
$storage->pseudo_property_get_types += $trait_storage->pseudo_property_get_types;
$storage->pseudo_property_set_types += $trait_storage->pseudo_property_set_types;
$storage->pseudo_methods += $trait_storage->pseudo_methods;
$storage->declaring_pseudo_method_ids += $trait_storage->declaring_pseudo_method_ids;
} elseif ($trait_storage->template_extended_params) {
$storage->template_extended_params = array_merge(
$storage->template_extended_params ?: [],
$trait_storage->template_extended_params
);
}
$storage->pseudo_property_get_types += $trait_storage->pseudo_property_get_types;
$storage->pseudo_property_set_types += $trait_storage->pseudo_property_set_types;
$storage->pseudo_methods += $trait_storage->pseudo_methods;
$storage->declaring_pseudo_method_ids += $trait_storage->declaring_pseudo_method_ids;
}
private static function extendType(
@ -437,10 +538,9 @@ class Populator
private function populateDataFromParentClass(
ClassLikeStorage $storage,
ClassLikeStorageProvider $storage_provider,
array $dependent_classlikes
array $dependent_classlikes,
string $parent_storage_class
): void {
$parent_storage_class = reset($storage->parent_classes);
$parent_storage_class = strtolower(
$this->classlikes->getUnAliasedName(
$parent_storage_class
@ -452,7 +552,7 @@ class Populator
} catch (InvalidArgumentException $e) {
$this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
$storage->invalid_dependencies[] = $parent_storage_class;
$storage->invalid_dependencies[$parent_storage_class] = true;
$this->invalid_class_storages[$parent_storage_class][] = $storage;
@ -559,252 +659,151 @@ class Populator
$storage->declaring_pseudo_method_ids += $parent_storage->declaring_pseudo_method_ids;
}
private function populateInterfaceDataFromParentInterfaces(
private function populateInterfaceData(
ClassLikeStorage $storage,
ClassLikeStorage $interface_storage,
ClassLikeStorageProvider $storage_provider,
array $dependent_classlikes
): void {
$parent_interfaces = [];
$this->populateClassLikeStorage($interface_storage, $dependent_classlikes);
foreach ($storage->direct_interface_parents as $parent_interface_lc => $_) {
try {
$parent_interface_lc = strtolower(
$this->classlikes->getUnAliasedName(
$parent_interface_lc
)
);
$parent_interface_storage = $storage_provider->get($parent_interface_lc);
} catch (InvalidArgumentException $e) {
$this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
// copy over any constants
$storage->constants = array_merge(
array_filter(
$interface_storage->constants,
fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC
),
$storage->constants
);
$storage->invalid_dependencies[] = $parent_interface_lc;
continue;
}
$storage->invalid_dependencies = array_merge(
$storage->invalid_dependencies,
$interface_storage->invalid_dependencies
);
$this->populateClassLikeStorage($parent_interface_storage, $dependent_classlikes);
if ($interface_storage->template_types) {
$storage->template_extended_params[$interface_storage->name] = [];
// copy over any constants
$storage->constants = array_merge(
array_filter(
$parent_interface_storage->constants,
fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC
),
$storage->constants
);
if (isset($storage->template_extended_offsets[$interface_storage->name])) {
foreach ($storage->template_extended_offsets[$interface_storage->name] as $i => $type) {
$parent_template_type_names = array_keys($interface_storage->template_types);
$storage->invalid_dependencies = array_merge(
$storage->invalid_dependencies,
$parent_interface_storage->invalid_dependencies
);
$mapped_name = $parent_template_type_names[$i] ?? null;
if ($parent_interface_storage->template_types) {
$storage->template_extended_params[$parent_interface_storage->name] = [];
if (isset($storage->template_extended_offsets[$parent_interface_storage->name])) {
foreach ($storage->template_extended_offsets[$parent_interface_storage->name] as $i => $type) {
$parent_template_type_names = array_keys($parent_interface_storage->template_types);
$mapped_name = $parent_template_type_names[$i] ?? null;
if ($mapped_name) {
$storage->template_extended_params[$parent_interface_storage->name][$mapped_name] = $type;
}
if ($mapped_name) {
$storage->template_extended_params[$interface_storage->name][$mapped_name] = $type;
}
}
if ($parent_interface_storage->template_extended_params) {
foreach ($parent_interface_storage->template_extended_params as $t_storage_class => $type_map) {
foreach ($type_map as $i => $type) {
$storage->template_extended_params[$t_storage_class][$i] = self::extendType(
$type,
$storage
);
}
}
}
} else {
foreach ($parent_interface_storage->template_types as $template_name => $template_type_map) {
foreach ($template_type_map as $template_type) {
$default_param = clone $template_type;
$default_param->from_docblock = false;
$storage->template_extended_params[$parent_interface_storage->name][$template_name]
= $default_param;
if ($interface_storage->template_extended_params) {
foreach ($interface_storage->template_extended_params as $t_storage_class => $type_map) {
foreach ($type_map as $i => $type) {
$storage->template_extended_params[$t_storage_class][$i] = self::extendType(
$type,
$storage
);
}
}
}
} elseif ($parent_interface_storage->template_extended_params) {
$storage->template_extended_params = array_merge(
$storage->template_extended_params ?: [],
$parent_interface_storage->template_extended_params
);
} else {
foreach ($interface_storage->template_types as $template_name => $template_type_map) {
foreach ($template_type_map as $template_type) {
$default_param = clone $template_type;
$default_param->from_docblock = false;
$storage->template_extended_params[$interface_storage->name][$template_name]
= $default_param;
}
}
}
$parent_interfaces = array_merge($parent_interfaces, $parent_interface_storage->parent_interfaces);
$this->inheritMethodsFromParent($storage, $parent_interface_storage);
$storage->pseudo_methods += $parent_interface_storage->pseudo_methods;
$storage->declaring_pseudo_method_ids += $parent_interface_storage->declaring_pseudo_method_ids;
} elseif ($interface_storage->template_extended_params) {
$storage->template_extended_params = array_merge(
$storage->template_extended_params ?: [],
$interface_storage->template_extended_params
);
}
$storage->parent_interfaces = array_merge($parent_interfaces, $storage->parent_interfaces);
foreach ($storage->parent_interfaces as $parent_interface_lc => $_) {
$new_parents = array_keys($interface_storage->parent_interfaces);
$new_parents[] = $interface_storage->name;
foreach ($new_parents as $new_parent) {
try {
$parent_interface_lc = strtolower(
$new_parent = strtolower(
$this->classlikes->getUnAliasedName(
$parent_interface_lc
$new_parent
)
);
$parent_interface_storage = $storage_provider->get($parent_interface_lc);
$new_parent_interface_storage = $storage_provider->get($new_parent);
} catch (InvalidArgumentException $e) {
continue;
}
$parent_interface_storage->dependent_classlikes[strtolower($storage->name)] = true;
$new_parent_interface_storage->dependent_classlikes[strtolower($storage->name)] = true;
}
}
private function populateDataFromImplementedInterfaces(
private function populateInterfaceDataFromParentInterface(
ClassLikeStorage $storage,
ClassLikeStorageProvider $storage_provider,
array $dependent_classlikes
array $dependent_classlikes,
string $parent_interface_lc
): void {
$extra_interfaces = [];
foreach ($storage->direct_class_interfaces as $implemented_interface_lc => $_) {
try {
$implemented_interface_lc = strtolower(
$this->classlikes->getUnAliasedName(
$implemented_interface_lc
)
);
$implemented_interface_storage = $storage_provider->get($implemented_interface_lc);
} catch (InvalidArgumentException $e) {
$this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
$storage->invalid_dependencies[] = $implemented_interface_lc;
continue;
}
$this->populateClassLikeStorage($implemented_interface_storage, $dependent_classlikes);
// copy over any constants
$storage->constants = array_merge(
array_filter(
$implemented_interface_storage->constants,
fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC
),
$storage->constants
try {
$parent_interface_lc = strtolower(
$this->classlikes->getUnAliasedName(
$parent_interface_lc
)
);
$parent_interface_storage = $storage_provider->get($parent_interface_lc);
} catch (InvalidArgumentException $e) {
$this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
$storage->invalid_dependencies = array_merge(
$storage->invalid_dependencies,
$implemented_interface_storage->invalid_dependencies
$storage->invalid_dependencies[$parent_interface_lc] = true;
return;
}
$this->populateInterfaceData($storage, $parent_interface_storage, $storage_provider, $dependent_classlikes);
$this->inheritMethodsFromParent($storage, $parent_interface_storage);
$storage->pseudo_methods += $parent_interface_storage->pseudo_methods;
$storage->declaring_pseudo_method_ids += $parent_interface_storage->declaring_pseudo_method_ids;
$storage->parent_interfaces = array_merge(
$parent_interface_storage->parent_interfaces,
$storage->parent_interfaces
);
}
private function populateDataFromImplementedInterface(
ClassLikeStorage $storage,
ClassLikeStorageProvider $storage_provider,
array $dependent_classlikes,
string $implemented_interface_lc
): void {
try {
$implemented_interface_lc = strtolower(
$this->classlikes->getUnAliasedName(
$implemented_interface_lc
)
);
$implemented_interface_storage = $storage_provider->get($implemented_interface_lc);
} catch (InvalidArgumentException $e) {
$this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
if ($implemented_interface_storage->template_types) {
$storage->template_extended_params[$implemented_interface_storage->name] = [];
if (isset($storage->template_extended_offsets[$implemented_interface_storage->name])) {
foreach ($storage->template_extended_offsets[$implemented_interface_storage->name] as $i => $type) {
$parent_template_type_names = array_keys($implemented_interface_storage->template_types);
$mapped_name = $parent_template_type_names[$i] ?? null;
if ($mapped_name) {
$storage->template_extended_params[$implemented_interface_storage->name][$mapped_name]
= $type;
}
}
if ($implemented_interface_storage->template_extended_params) {
foreach ($implemented_interface_storage->template_extended_params as $e_i => $type_map) {
foreach ($type_map as $i => $type) {
$storage->template_extended_params[$e_i][$i] = self::extendType(
$type,
$storage
);
}
}
}
} else {
foreach ($implemented_interface_storage->template_types as $template_name => $template_type_map) {
foreach ($template_type_map as $template_type) {
$default_param = clone $template_type;
$default_param->from_docblock = false;
$storage->template_extended_params[$implemented_interface_storage->name][$template_name]
= $default_param;
}
}
}
} elseif ($implemented_interface_storage->template_extended_params) {
$storage->template_extended_params = array_merge(
$storage->template_extended_params ?: [],
$implemented_interface_storage->template_extended_params
);
}
$extra_interfaces = array_merge($extra_interfaces, $implemented_interface_storage->parent_interfaces);
$storage->invalid_dependencies[$implemented_interface_lc] = true;
return;
}
$storage->class_implements = array_merge($storage->class_implements, $extra_interfaces);
$this->populateInterfaceData(
$storage,
$implemented_interface_storage,
$storage_provider,
$dependent_classlikes
);
$interface_method_implementers = [];
foreach ($storage->class_implements as $implemented_interface_lc => $_) {
try {
$implemented_interface = strtolower(
$this->classlikes->getUnAliasedName(
$implemented_interface_lc
)
);
$implemented_interface_storage = $storage_provider->get($implemented_interface);
} catch (InvalidArgumentException $e) {
continue;
}
$implemented_interface_storage->dependent_classlikes[strtolower($storage->name)] = true;
foreach ($implemented_interface_storage->methods as $method_name => $method) {
if ($method->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC) {
$interface_method_implementers[$method_name][] = new MethodIdentifier(
$implemented_interface_storage->name,
$method_name
);
}
}
}
foreach ($interface_method_implementers as $method_name => $interface_method_ids) {
if (count($interface_method_ids) === 1) {
if (isset($storage->methods[$method_name])) {
$method_storage = $storage->methods[$method_name];
if ($method_storage->signature_return_type
&& !$method_storage->signature_return_type->isVoid()
&& $method_storage->return_type === $method_storage->signature_return_type
) {
$interface_fqcln = $interface_method_ids[0]->fq_class_name;
$interface_storage = $storage_provider->get($interface_fqcln);
if (isset($interface_storage->methods[$method_name])) {
$interface_method_storage = $interface_storage->methods[$method_name];
if ($interface_method_storage->throws
&& (!$method_storage->throws || $method_storage->inheritdoc)
) {
$method_storage->throws += $interface_method_storage->throws;
}
}
}
}
}
foreach ($interface_method_ids as $interface_method_id) {
$storage->overridden_method_ids[$method_name][$interface_method_id->fq_class_name]
= $interface_method_id;
}
}
$storage->class_implements = array_merge(
$storage->class_implements,
$implemented_interface_storage->parent_interfaces
);
}
/**

View File

@ -386,7 +386,7 @@ class ClassLikeStorage
public $initialized_properties = [];
/**
* @var array<string>
* @var array<string, true>
*/
public $invalid_dependencies = [];

View File

@ -212,6 +212,82 @@ class StubTest extends TestCase
$this->analyzeFile($file_path, new Context());
}
public function testStubFileParentClass(): void
{
$this->expectException(CodeException::class);
$this->expectExceptionMessage('ImplementedParamTypeMismatch');
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
dirname(__DIR__),
'<?xml version="1.0"?>
<psalm
errorLevel="1"
>
<projectFiles>
<directory name="src" />
</projectFiles>
<stubs>
<file name="tests/fixtures/stubs/systemclass.phpstub" />
</stubs>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
namespace A\B\C;
class Foo extends \SystemClass
{
public function foo(string $a, string $b): string
{
return $a . $b;
}
}
'
);
$this->analyzeFile($file_path, new Context());
}
public function testStubFileCircularReference(): void
{
$this->expectException(CodeException::class);
$this->expectExceptionMessage('CircularReference');
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
dirname(__DIR__),
'<?xml version="1.0"?>
<psalm
errorLevel="1"
>
<projectFiles>
<directory name="src" />
</projectFiles>
<stubs>
<file name="tests/fixtures/stubs/CircularReference.phpstub" />
</stubs>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
class Foo extends Baz {}
'
);
$this->analyzeFile($file_path, new Context());
}
public function testPhpStormMetaParsingFile(): void
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(

View File

@ -0,0 +1,5 @@
<?php
class Bar extends Foo {}
class Baz extends Bar {}