Skip to content

Commit

Permalink
Support constants in traits
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-worman committed Jan 17, 2023
1 parent 8b9cd5f commit 2895865
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 21 deletions.
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -118,6 +118,7 @@
],
"verify-callmap": "phpunit tests/Internal/Codebase/InternalCallMapHandlerTest.php",
"psalm": "@php ./psalm",
"psalm-set-baseline": "@php ./psalm --set-baseline=psalm-baseline.xml",
"tests": [
"@lint",
"@cs",
Expand Down
15 changes: 10 additions & 5 deletions psalm-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="dev-master@dbcfe62c5224603912c94c1eab5d7c31841ada82">
<files psalm-version="dev-master@8b9cd5fb333866c1e84ca9564394816a7ff5ae6f">
<file src="examples/TemplateChecker.php">
<PossiblyUndefinedIntArrayOffset>
<code>$comment_block-&gt;tags['variablesfrom'][0]</code>
Expand Down Expand Up @@ -271,6 +271,12 @@
<code>$function_callables[0]</code>
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/Codebase/Populator.php">
<DeprecatedProperty>
<code>$storage-&gt;trait_alias_map</code>
<code>$storage-&gt;trait_alias_map</code>
</DeprecatedProperty>
</file>
<file src="src/Psalm/Internal/Codebase/Properties.php">
<PossiblyUndefinedIntArrayOffset>
<code>$property_name</code>
Expand Down Expand Up @@ -362,6 +368,9 @@
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php">
<DeprecatedProperty>
<code>$storage-&gt;trait_alias_map</code>
</DeprecatedProperty>
<PossiblyUndefinedIntArrayOffset>
<code>$l[4]</code>
<code>$r[4]</code>
Expand Down Expand Up @@ -537,10 +546,6 @@
<code>replace</code>
<code>replace</code>
</ImpureMethodCall>
<InvalidReturnType>
<code>TTypeParams|null</code>
<code>TTypeParams|null</code>
</InvalidReturnType>
<PossiblyUndefinedIntArrayOffset>
<code>$this-&gt;type_params[1]</code>
</PossiblyUndefinedIntArrayOffset>
Expand Down
40 changes: 38 additions & 2 deletions src/Psalm/Internal/Codebase/Populator.php
Expand Up @@ -24,6 +24,7 @@
use UnitEnum;

use function array_filter;
use function array_flip;
use function array_intersect_key;
use function array_keys;
use function array_merge;
Expand Down Expand Up @@ -439,6 +440,7 @@ private function populateDataFromTrait(

$this->populateClassLikeStorage($trait_storage, $dependent_classlikes);

$this->inheritConstantsFromParent($storage, $trait_storage);
$this->inheritMethodsFromParent($storage, $trait_storage);
$this->inheritPropertiesFromParent($storage, $trait_storage);

Expand Down Expand Up @@ -874,6 +876,40 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file
$storage->populated = true;
}

private function inheritConstantsFromParent(
ClassLikeStorage $storage,
ClassLikeStorage $parent_storage
): void {
foreach ($parent_storage->constants as $constant_name => $class_constant_storage) {
if ($parent_storage->is_trait) {
$trait_alias_map_cased = array_flip($storage->trait_alias_map_cased);
if (isset($trait_alias_map_cased[$constant_name])) {
$aliased_constant_name_lc = strtolower($trait_alias_map_cased[$constant_name]);
$aliased_constant_name = $trait_alias_map_cased[$constant_name];
} else {
$aliased_constant_name_lc = strtolower($constant_name);
$aliased_constant_name = $constant_name;
}
$visibility = $storage->trait_visibility_map[$aliased_constant_name_lc] ?? $class_constant_storage->visibility;
$final = $storage->trait_final_map[$aliased_constant_name_lc] ?? $class_constant_storage->final;
$storage->constants[$aliased_constant_name] = new ClassConstantStorage(
$class_constant_storage->type,
$class_constant_storage->inferred_type,
$visibility,
$class_constant_storage->location,
$class_constant_storage->type_location,
$class_constant_storage->stmt_location,
$class_constant_storage->deprecated,
$final,
$class_constant_storage->unresolved_node,
$class_constant_storage->attributes,
$class_constant_storage->suppressed_issues,
$class_constant_storage->description,
);
}
}
}

protected function inheritMethodsFromParent(
ClassLikeStorage $storage,
ClassLikeStorage $parent_storage
Expand All @@ -890,7 +926,7 @@ protected function inheritMethodsFromParent(
$aliased_method_names = [$method_name_lc];

if ($parent_storage->is_trait
&& $storage->trait_alias_map
&& $storage->trait_alias_map_cased
) {
$aliased_method_names = array_merge(
$aliased_method_names,
Expand Down Expand Up @@ -960,7 +996,7 @@ protected function inheritMethodsFromParent(
$aliased_method_names = [$method_name_lc];

if ($parent_storage->is_trait
&& $storage->trait_alias_map
&& $storage->trait_alias_map_cased
) {
$aliased_method_names = array_merge(
$aliased_method_names,
Expand Down
19 changes: 6 additions & 13 deletions src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php
Expand Up @@ -843,10 +843,6 @@ public function handleTraitUse(PhpParser\Node\Stmt\TraitUse $node): void
throw new UnexpectedValueException('bad');
}

$method_map = $storage->trait_alias_map ?: [];
$visibility_map = $storage->trait_visibility_map ?: [];
$final_map = $storage->trait_final_map ?: [];

foreach ($node->adaptations as $adaptation) {
if ($adaptation instanceof PhpParser\Node\Stmt\TraitUseAdaptation\Alias) {
$old_name = strtolower($adaptation->method->name);
Expand All @@ -856,36 +852,33 @@ public function handleTraitUse(PhpParser\Node\Stmt\TraitUse $node): void
$new_name = strtolower($adaptation->newName->name);

if ($new_name !== $old_name) {
$method_map[$new_name] = $old_name;
$storage->trait_alias_map[$new_name] = $old_name;
$storage->trait_alias_map_cased[$adaptation->newName->name] = $adaptation->method->name;
}
}

if ($adaptation->newModifier) {
switch ($adaptation->newModifier) {
case 1:
$visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PUBLIC;
$storage->trait_visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PUBLIC;
break;

case 2:
$visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PROTECTED;
$storage->trait_visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PROTECTED;
break;

case 4:
$visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PRIVATE;
$storage->trait_visibility_map[$new_name] = ClassLikeAnalyzer::VISIBILITY_PRIVATE;
break;

case 32:
$final_map[$new_name] = true;
$storage->trait_final_map[$new_name] = true;
break;
}
}
}
}

$storage->trait_alias_map = $method_map;
$storage->trait_visibility_map = $visibility_map;
$storage->trait_final_map = $final_map;

foreach ($node->traits as $trait) {
$trait_fqcln = ClassLikeAnalyzer::getFQCLNFromNameObject($trait, $this->aliases);
$this->codebase->scanner->queueClassLikeForScanning($trait_fqcln, $this->file_scanner->will_analyze);
Expand Down
6 changes: 5 additions & 1 deletion src/Psalm/Storage/ClassLikeStorage.php
Expand Up @@ -179,17 +179,21 @@ final class ClassLikeStorage implements HasAttributesInterface
public $used_traits = [];

/**
* @deprecated Will be removed Psalm 6. Use {@see self::$trait_alias_map_cased} instead.
* @var array<lowercase-string, lowercase-string>
*/
public $trait_alias_map = [];

/** @var array<string, string> */
public array $trait_alias_map_cased = [];

/**
* @var array<lowercase-string, bool>
*/
public $trait_final_map = [];

/**
* @var array<string, int>
* @var array<string, 1|2|3>
*/
public $trait_visibility_map = [];

Expand Down
35 changes: 35 additions & 0 deletions tests/TraitTest.php
Expand Up @@ -1001,6 +1001,41 @@ trait T {}
}
',
],
'constant in trait' => [
'code' => <<<'PHP'
<?php
trait TraitA {
public const PUBLIC_CONST = 'PUBLIC_CONST';
protected const PROTECTED_CONST = 'PROTECTED_CONST';
private const PRIVATE_CONST = 'PRIVATE_CONST';
}
class ClassB {
use TraitA;
public static function getPublicConst(): string { return self::PUBLIC_CONST; }
public static function getProtectedConst(): string { return self::PROTECTED_CONST; }
public static function getPrivateConst(): string { return self::PRIVATE_CONST; }
}
class ClassC extends ClassB {
public static function getPublicConst(): string { return self::PUBLIC_CONST; }
public static function getProtectedConst(): string { return self::PROTECTED_CONST; }
}
PHP,
],
'constant in trait with alias' => [
'code' => <<<'PHP'
<?php
trait TraitA {
private const PRIVATE_CONST = 'PRIVATE_CONST';
}
class ClassB {
use TraitA { PRIVATE_CONST as public PUBLIC_CONST; }
}
$c = ClassB::PUBLIC_CONST;
PHP,
'assertions' => [
'$c' => 'string',
],
],
];
}

Expand Down

0 comments on commit 2895865

Please sign in to comment.