Skip to content

Commit c43a2f0

Browse files
committed
Fix parsing global imports and grouped imports containing aliases
1 parent 2541d4d commit c43a2f0

File tree

3 files changed

+93
-29
lines changed

3 files changed

+93
-29
lines changed

ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ XP Reflection ChangeLog
33

44
## ?.?.? / ????-??-??
55

6+
* Fixed parsing global imports and grouped imports containing aliases,
7+
which surfaced as either *Syntax error, unexpected token `as`* or
8+
*Cannot use object of type PhpToken as array* errors
9+
(@thekid)
10+
611
### 2.13.3 / 2023-06-04
712

813
* Fixed `lang.reflection.Constant::modifiers()` return type - @thekid

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

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

3+
use PhpToken;
34
use lang\reflection\Annotation;
45

56
/**
@@ -61,43 +62,55 @@ public function ofParameter($method, $reflect) {
6162
* @return [:string]
6263
*/
6364
public function imports($reflect) {
64-
static $break= [T_CLASS => true, T_INTERFACE => true, T_TRAIT => true];
65+
static $break= [T_CLASS => true, T_INTERFACE => true, T_TRAIT => true, 372 /* T_ENUM */ => true];
66+
static $types= [T_WHITESPACE => true, 44 => true, 59 => true, 123 => true];
6567

66-
$tokens= \PhpToken::tokenize(file_get_contents($reflect->getFileName()));
68+
$tokens= PhpToken::tokenize(file_get_contents($reflect->getFileName()));
6769
$imports= [];
6870
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) {
6971
if (isset($break[$tokens[$i]->id])) break;
7072
if (T_USE !== $tokens[$i]->id) continue;
7173

72-
$type= '';
73-
for ($i+= 2; $i < $s, !(59 === $tokens[$i]->id || 123 === $tokens[$i]->id || T_WHITESPACE === $tokens[$i]->id); $i++) {
74-
$type.= $tokens[$i]->text;
75-
}
74+
do {
75+
$type= '';
76+
for ($i+= 2; $i < $s, !isset($types[$tokens[$i]->id]); $i++) {
77+
$type.= $tokens[$i]->text;
78+
}
79+
80+
// Skip over whitespace
81+
if (T_WHITESPACE === $tokens[$i]->id) $i++;
7682

77-
// use `lang\{Type, Primitive as P}` vs. `use lang\Primitive as P;` vs. `use lang\Primitive`
78-
if (123 === $tokens[$i]->id) {
79-
$alias= null;
80-
$group= '';
81-
for ($i+= 1; $i < $s; $i++) {
82-
if (44 === $tokens[$i]->id) {
83-
$imports[$alias ?? $group]= $type.$group;
84-
$alias= null;
85-
$group= '';
86-
} else if (125 === $tokens[$i]->id) {
87-
$imports[$alias ?? $group]= $type.$group;
88-
break;
89-
} else if (T_AS === $tokens[$i]->id) {
90-
$i+= 2;
91-
$alias= $tokens[$i][1];
92-
} else if (T_WHITESPACE !== $tokens[$i]->id) {
93-
$group.= $tokens[$i]->text;
83+
// use `lang\{Type, Primitive as P}` vs. `use lang\Primitive as P;` vs. `use lang\Primitive`
84+
if (123 === $tokens[$i]->id) {
85+
$alias= null;
86+
$group= '';
87+
for ($i+= 1; $i < $s; $i++) {
88+
if (44 === $tokens[$i]->id) {
89+
$imports[$alias ?? $group]= $type.$group;
90+
$alias= null;
91+
$group= '';
92+
} else if (125 === $tokens[$i]->id) {
93+
$imports[$alias ?? $group]= $type.$group;
94+
break;
95+
} else if (T_AS === $tokens[$i]->id) {
96+
$i+= 2;
97+
$alias= $tokens[$i]->text;
98+
} else if (T_WHITESPACE !== $tokens[$i]->id) {
99+
$group.= $tokens[$i]->text;
100+
}
94101
}
102+
} else if (T_AS === $tokens[$i]->id) {
103+
$i+= 2;
104+
$imports[$tokens[$i]->text]= $type;
105+
} else if (false === ($p= strrpos($type, '\\'))) {
106+
$imports[$type]= null;
107+
} else {
108+
$imports[substr($type, strrpos($type, '\\') + 1)]= $type;
95109
}
96-
} else if (T_AS === $tokens[++$i]->id) {
97-
$imports[$tokens[$i + 2]->text]= $type;
98-
} else {
99-
$imports[substr($type, strrpos($type, '\\') + 1)]= $type;
100-
}
110+
111+
// Skip over whitespace
112+
if (T_WHITESPACE === $tokens[$i]->id) $i++;
113+
} while (44 === $tokens[$i]->id);
101114
}
102115
return $imports;
103116
}
@@ -108,7 +121,7 @@ public function evaluate($reflect, $code) {
108121
$header.= 'namespace '.$namespace.';';
109122
}
110123
foreach ($this->imports($reflect) as $import => $type) {
111-
$header.= 'use '.$type.' as '.$import.';';
124+
$header.= $type ? "use {$type} as {$import};" : "use {$import};";
112125
}
113126

114127
$f= eval($header.' return static function() { return '.$code.'; };');
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php namespace lang\reflection\unittest;
2+
3+
use ReflectionClass, StdClass as Dynamic;
4+
use lang\meta\FromAttributes;
5+
use test\{Assert, Test, Ignore as Skip};
6+
use util\Comparison as WithComparison;
7+
8+
class FromAttributesTest {
9+
10+
#[Test]
11+
public function can_create() {
12+
new FromAttributes();
13+
}
14+
15+
#[Test]
16+
public function imports() {
17+
Assert::equals(
18+
[
19+
'ReflectionClass' => null,
20+
'FromAttributes' => FromAttributes::class,
21+
'Dynamic' => Dynamic::class,
22+
'Assert' => Assert::class,
23+
'Test' => Test::class,
24+
'WithComparison' => WithComparison::class,
25+
'Skip' => Skip::class,
26+
],
27+
(new FromAttributes())->imports(new ReflectionClass(self::class))
28+
);
29+
}
30+
31+
#[Test]
32+
public function evaluate_constant() {
33+
Assert::equals(
34+
ReflectionClass::IS_FINAL,
35+
(new FromAttributes())->evaluate(new ReflectionClass(self::class), 'ReflectionClass::IS_FINAL')
36+
);
37+
}
38+
39+
#[Test]
40+
public function evaluate_alias() {
41+
Assert::equals(
42+
new Dynamic(),
43+
(new FromAttributes())->evaluate(new ReflectionClass(self::class), 'new Dynamic()')
44+
);
45+
}
46+
}

0 commit comments

Comments
 (0)