Skip to content

Commit 8e8fba1

Browse files
authored
Merge pull request #49 from xp-framework/fix/annotations-in-traits
Fix annotation in trait methods not being picked up correctly
2 parents 19e0d20 + f9b6432 commit 8e8fba1

File tree

6 files changed

+95
-39
lines changed

6 files changed

+95
-39
lines changed

src/main/php/lang/meta/FromSyntaxTree.class.php

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?php namespace lang\meta;
22

3-
use lang\IllegalArgumentException;
43
use lang\ast\nodes\{ArrayLiteral, FunctionDeclaration};
54
use lang\ast\{Language, Token, Tokens, Visitor, Code};
5+
use lang\{IllegalArgumentException, IllegalStateException};
66

77
/**
88
* Parses annotations from AST, using PHP language syntax.
@@ -20,6 +20,14 @@ static function __static() {
2020
self::$lang= Language::named('PHP');
2121
}
2222

23+
/** Locates a declared partial of a given class */
24+
private function partial($tree, $class) {
25+
foreach ($class->getTraitNames() as $declared) {
26+
if ($type= $tree->type($declared)) return $type;
27+
}
28+
throw new IllegalStateException('No part of '.$class->name.' declared in tree');
29+
}
30+
2331
/** Locates an anonymous class creation expression */
2432
private function anonymous($tree, $start, $end) {
2533
foreach ($tree->children() as $child) {
@@ -29,31 +37,16 @@ private function anonymous($tree, $start, $end) {
2937
}
3038

3139
/** Returns the syntax tree for a given type using a cache */
32-
private function tree($reflect) {
33-
34-
// Handle generic class names correctly
35-
if ($class= \xp::$cn[$reflect->name] ?? null) {
36-
$class= substr($class, 0, strcspn($class, '<'));
37-
} else {
38-
$class= strtr($reflect->name, '\\', '.');
39-
}
40-
41-
if (!isset($this->cache[$class])) {
42-
if ($reflect->isAnonymous()) {
43-
$tree= self::$lang->parse(new Tokens(file_get_contents($reflect->getFileName()), '<anonymous>'))->tree();
44-
$type= $this->anonymous($tree, $reflect->getStartLine(), $reflect->getEndLine())->current()->definition;
45-
} else {
46-
sscanf(\xp::$cl[$class], '%[^:]://%[^$]', $cl, $argument);
47-
$instanceFor= [literal($cl), 'instanceFor'];
48-
$tree= self::$lang->parse(new Tokens($instanceFor($argument)->loadClassBytes($class), $class))->tree();
49-
$type= $tree->type(strtr($class, '.', '\\'));
50-
}
51-
52-
// Limit cache
53-
$this->cache[$class]= new SyntaxTree($tree, $type);
40+
private function tree($class, $file) {
41+
if (null === ($tree= $this->cache[$file] ?? null)) {
42+
$this->cache[$file]= $tree= self::$lang->parse(new Tokens(file_get_contents($file), $file))->tree();
5443
if (sizeof($this->cache) > self::CACHE_SIZE) unset($this->cache[key($this->cache)]);
5544
}
56-
return $this->cache[$class];
45+
46+
return new SyntaxTree($tree, $class->isAnonymous()
47+
? $this->anonymous($tree, $class->getStartLine(), $class->getEndLine())->current()->definition
48+
: $tree->type($class->name) ?? $this->partial($tree, $class)
49+
);
5750
}
5851

5952
private function parse($code, $resolver) {
@@ -154,7 +147,7 @@ private function parse($code, $resolver) {
154147
}
155148

156149
public function evaluate($arg, $code) {
157-
$tree= $arg instanceof SyntaxTree ? $arg : $this->tree($arg);
150+
$tree= $arg instanceof SyntaxTree ? $arg : $this->tree($arg, $arg->getFileName());
158151
$parsed= self::parse($code, $tree->resolver())->tree()->children();
159152
if (1 === sizeof($parsed)) {
160153
return $parsed[0]->visit($tree);
@@ -197,7 +190,7 @@ private function annotations($tree, $annotated) {
197190
}
198191

199192
public function imports($reflect) {
200-
$resolver= $this->tree($reflect)->resolver();
193+
$resolver= $this->tree($reflect, $reflect->getFileName())->resolver();
201194
$imports= [];
202195
foreach ($resolver->imports as $alias => $type) {
203196
$imports[$alias]= ltrim($type, '\\');
@@ -207,31 +200,34 @@ public function imports($reflect) {
207200

208201
/** @return iterable */
209202
public function ofType($reflect) {
210-
$tree= $this->tree($reflect);
203+
$tree= $this->tree($reflect, $reflect->getFileName());
211204
return $this->annotations($tree, $tree->type());
212205
}
213206

214207
/** @return iterable */
215208
public function ofConstant($reflect) {
216-
$tree= $this->tree($reflect->getDeclaringClass());
209+
$class= $reflect->getDeclaringClass();
210+
$tree= $this->tree($class, $class->getFileName());
217211
return $this->annotations($tree, $tree->type()->constant($reflect->name));
218212
}
219213

220214
/** @return iterable */
221215
public function ofProperty($reflect) {
222-
$tree= $this->tree($reflect->getDeclaringClass());
216+
$class= $reflect->getDeclaringClass();
217+
$tree= $this->tree($class, $class->getFileName());
223218
return $this->annotations($tree, $tree->type()->property($reflect->name));
224219
}
225220

226221
/** @return iterable */
227222
public function ofMethod($reflect) {
228-
$tree= $this->tree($reflect->getDeclaringClass());
223+
$tree= $this->tree($reflect->getDeclaringClass(), $reflect->getFileName());
229224
return $this->annotations($tree, $tree->type()->method($reflect->name));
230225
}
231226

232227
/** @return iterable */
233228
public function ofParameter($method, $reflect) {
234-
$tree= $this->tree($method->getDeclaringClass());
229+
$class= $reflect->getDeclaringClass();
230+
$tree= $this->tree($class, $class->getMethod($method->name)->getFileName());
235231
return $this->annotations($tree, $tree->type()
236232
->method($method->name)
237233
->signature

src/main/php/lang/reflection/Type.class.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php namespace lang\reflection;
22

3-
use ArgumentCountError, TypeError, ReflectionClass, ReflectionException, ReflectionFunction, Throwable;
3+
use ArgumentCountError, TypeError, UnitEnum, ReflectionClass, ReflectionException, ReflectionFunction, Throwable;
44
use lang\{Reflection, Enum, XPClass, Value, VirtualProperty, IllegalArgumentException};
55

66
/**
@@ -54,7 +54,7 @@ public function kind(): Kind {
5454
return Kind::$INTERFACE;
5555
} else if ($this->reflect->isTrait()) {
5656
return Kind::$TRAIT;
57-
} else if ($this->reflect->isSubclassOf(Enum::class) || $this->reflect->isSubclassOf(\UnitEnum::class)) {
57+
} else if ($this->reflect->isSubclassOf(Enum::class) || $this->reflect->isSubclassOf(UnitEnum::class)) {
5858
return Kind::$ENUM;
5959
} else {
6060
return Kind::$CLASS;

src/test/php/lang/reflection/unittest/AnnotationTest.class.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<?php namespace lang\reflection\unittest;
22

33
use ReflectionFunction;
4+
use lang\reflection\unittest\fixture\Schemas;
45
use lang\reflection\{Annotation, CannotInstantiate, InvocationFailed};
56
use lang\{IllegalStateException, Reflection, XPClass};
6-
use test\{Assert, Expect, Test, Values};
7+
use test\{Assert, Before, Expect, Test, Values};
78

89
class AnnotationTest {
910
use TypeDefinition;
@@ -418,4 +419,19 @@ public function all_of($type) {
418419
$t->annotations()->all($type)
419420
);
420421
}
422+
423+
#[Test]
424+
public function used_from_trait() {
425+
$t= Reflection::type(Schemas::class);
426+
427+
foreach ($t->methods() as $name => $method) {
428+
$annotations= [];
429+
foreach ($method->annotations() as $type => $annotation) {
430+
$annotations[$type]= $annotation->arguments();
431+
}
432+
$actual[$name]= $annotations;
433+
}
434+
435+
Assert::equals(['fixture' => [Before::class => []], 'dialect' => [Before::class => []]], $actual);
436+
}
421437
}
Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php namespace lang\reflection\unittest;
22

3-
use lang\Reflection;
3+
use lang\{Reflection, Runnable};
44
use test\{Assert, Test};
55

66
class AnonymousClassTest {
@@ -12,13 +12,34 @@ public function name() {
1212

1313
/** @see https://github.com/xp-framework/reflection/issues/33 */
1414
#[Test]
15-
public function annotation() {
16-
$type= Reflection::type(new class() {
15+
public function annotation_on_new_class() {
16+
$type= Reflection::type(new class() implements Runnable {
1717

1818
#[Test]
19-
public function fixture() { }
19+
public function run() { }
2020
});
2121

22-
Assert::equals(Test::class, $type->method('fixture')->annotation(Test::class)->type());
22+
Assert::equals(Test::class, $type->method('run')->annotation(Test::class)->type());
23+
}
24+
25+
#[Test]
26+
public function annotation_on_newinstance_string() {
27+
$type= Reflection::type(newinstance(Runnable::class, [], '{
28+
29+
#[\test\Test]
30+
public function run() { }
31+
}'));
32+
33+
Assert::equals(Test::class, $type->method('run')->annotation(Test::class)->type());
34+
}
35+
36+
#[Test]
37+
public function annotation_on_newinstance_map() {
38+
$type= Reflection::type(newinstance(Runnable::class, [], [
39+
40+
'#[\test\Test] run' => function() { }
41+
]));
42+
43+
Assert::equals(Test::class, $type->method('run')->annotation(Test::class)->type());
2344
}
2445
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php namespace lang\reflection\unittest\fixture;
2+
3+
use test\Before;
4+
5+
class Schemas {
6+
use WithDialect;
7+
8+
#[Before]
9+
public function fixture() {
10+
// TBI
11+
}
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php namespace lang\reflection\unittest\fixture;
2+
3+
use test\Before;
4+
5+
trait WithDialect {
6+
7+
#[Before]
8+
public function dialect() {
9+
// TBI
10+
}
11+
}

0 commit comments

Comments
 (0)