Skip to content

Commit 6b9b4b5

Browse files
committed
Namespace anonymous classes
Fixes #10755
1 parent 20e8604 commit 6b9b4b5

4 files changed

Lines changed: 50 additions & 7 deletions

File tree

src/Psalm/Internal/Analyzer/ClassAnalyzer.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public function __construct(PhpParser\Node\Stmt $class, SourceAnalyzer $source,
123123
throw new UnexpectedValueException('Anonymous enums are not allowed');
124124
}
125125

126-
$fq_class_name = self::getAnonymousClassName($class, $source->getFilePath());
126+
$fq_class_name = self::getAnonymousClassName($class, $source->getAliases(), $source->getFilePath());
127127
}
128128

129129
parent::__construct($class, $source, $fq_class_name);
@@ -137,10 +137,25 @@ public function __construct(PhpParser\Node\Stmt $class, SourceAnalyzer $source,
137137
}
138138

139139
/** @return non-empty-string */
140-
public static function getAnonymousClassName(PhpParser\Node\Stmt\Class_ $class, string $file_path): string
141-
{
142-
return preg_replace('/[^A-Za-z0-9]/', '_', $file_path)
143-
. '_' . $class->getLine() . '_' . (int)$class->getAttribute('startFilePos');
140+
public static function getAnonymousClassName(
141+
PhpParser\Node\Stmt\Class_ $class,
142+
Aliases $aliases,
143+
string $file_path
144+
): string {
145+
$class_name = preg_replace('/[^A-Za-z0-9]/', '_', $file_path)
146+
. '_' . $class->getLine()
147+
. '_' . (int)$class->getAttribute('startFilePos');
148+
149+
$fq_class_name = Type::getFQCLNFromString(
150+
$class_name,
151+
$aliases,
152+
);
153+
154+
if ($fq_class_name === '') {
155+
throw new LogicException('Invalid class name, should never happen');
156+
}
157+
158+
return $fq_class_name;
144159
}
145160

146161
public function analyze(

src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,11 @@ public static function analyze(
159159
}
160160
} elseif ($stmt->class instanceof PhpParser\Node\Stmt\Class_) {
161161
$statements_analyzer->analyze([$stmt->class], $context);
162-
$fq_class_name = ClassAnalyzer::getAnonymousClassName($stmt->class, $statements_analyzer->getFilePath());
162+
$fq_class_name = ClassAnalyzer::getAnonymousClassName(
163+
$stmt->class,
164+
$statements_analyzer->getAliases(),
165+
$statements_analyzer->getFilePath(),
166+
);
163167
} else {
164168
self::analyzeConstructorExpression(
165169
$statements_analyzer,

src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool
161161
throw new LogicException('Anonymous classes are always classes');
162162
}
163163

164-
$fq_classlike_name = ClassAnalyzer::getAnonymousClassName($node, $this->file_path);
164+
$fq_classlike_name = ClassAnalyzer::getAnonymousClassName($node, $this->aliases, $this->file_path);
165165
} else {
166166
$name_location = new CodeLocation($this->file_scanner, $node->name);
167167

tests/InternalAnnotationTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,30 @@ public function baz(): void
606606
}
607607
',
608608
],
609+
'callToInternalMethodFromAnonymousClass' => [
610+
'code' => <<<'PHP'
611+
<?php
612+
namespace X;
613+
614+
/**
615+
* @internal
616+
* @psalm-internal X
617+
*/
618+
class A
619+
{
620+
public function a(): void {}
621+
}
622+
623+
new class (new A)
624+
{
625+
public function __construct(
626+
private A $a
627+
) {
628+
$a->a();
629+
}
630+
};
631+
PHP,
632+
],
609633
];
610634
}
611635

0 commit comments

Comments
 (0)