Skip to content

Commit 2413491

Browse files
committed
Testing a bunch of type
1 parent bdc534e commit 2413491

File tree

7 files changed

+286
-98
lines changed

7 files changed

+286
-98
lines changed
Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use PHPCfg\Op;
1414
use RuntimeException;
1515

16-
class Parser
16+
class Helper
1717
{
1818
public const KIND_VAR = 1;
1919
public const KIND_PARAM = 2;
@@ -54,22 +54,35 @@ public static function null(): Type
5454
return self::makeCachedType(Type::TYPE_NULL);
5555
}
5656

57-
public static function object(): Type
57+
public static function object(?string $userType = null): Type
5858
{
59+
if ($userType !== null) {
60+
return new Type(Type::TYPE_OBJECT, [], $userType);
61+
}
5962
return self::makeCachedType(Type::TYPE_OBJECT);
6063
}
6164

65+
public static function array(?Type $subType = null): Type
66+
{
67+
if ($subType !== null) {
68+
return new Type(Type::TYPE_ARRAY, [$subType]);
69+
}
70+
return self::makeCachedType(Type::TYPE_ARRAY);
71+
}
72+
6273
public static function void(): Type
6374
{
6475
return self::makeCachedType(Type::TYPE_VOID);
6576
}
6677

78+
public static function callable(): Type
79+
{
80+
return self::makeCachedType(Type::TYPE_CALLABLE);
81+
}
82+
6783
public static function nullable(Type $type): Type
6884
{
69-
return (new Type(Type::TYPE_UNION, [
70-
$type,
71-
new Type(Type::TYPE_NULL),
72-
]))->simplify();
85+
return static::union($type, static::null())->simplify();
7386
}
7487

7588
private static function makeCachedType(int $key): Type
@@ -83,7 +96,7 @@ private static function makeCachedType(int $key): Type
8396
public static function numeric(): Type
8497
{
8598
if (!isset(self::$typeCache["numeric"])) {
86-
self::$typeCache["numeric"] = new Type(Type::TYPE_UNION, [self::int(), self::float()]);
99+
self::$typeCache["numeric"] = static::union(static::int(), static::float());
87100
}
88101
return self::$typeCache["numeric"];
89102
}
@@ -96,14 +109,19 @@ public static function mixed(): Type
96109
foreach (Type::getPrimitives() as $key => $name) {
97110
$subs[] = self::makeCachedType($key);
98111
}
99-
self::$typeCache["mixed"] = new Type(Type::TYPE_UNION, $subs);
112+
self::$typeCache["mixed"] = static::union(...$subs);
100113
}
101114
return self::$typeCache["mixed"];
102115
}
103116

104117
public static function union(Type ...$subTypes): Type
105118
{
106-
return new Type(Type::TYPE_UNION, $subTypes);
119+
return (new Type(Type::TYPE_UNION, $subTypes))->simplify();
120+
}
121+
122+
public static function intersection(Type ...$subTypes): Type
123+
{
124+
return (new Type(Type::TYPE_INTERSECTION, $subTypes))->simplify();
107125
}
108126

109127
/**
@@ -153,38 +171,41 @@ public static function parseDecl(string $decl): Type
153171
case 'bool':
154172
case 'false':
155173
case 'true':
156-
return new Type(Type::TYPE_BOOLEAN);
174+
return static::bool();
157175
case 'integer':
158176
case 'int':
159-
return new Type(Type::TYPE_LONG);
177+
return static::int();
160178
case 'double':
161179
case 'real':
162180
case 'float':
163-
return new Type(Type::TYPE_DOUBLE);
181+
return static::float();
164182
case 'string':
165-
return new Type(Type::TYPE_STRING);
183+
return static::string();
166184
case 'array':
167-
return new Type(Type::TYPE_ARRAY);
185+
return static::array();
168186
case 'callable':
169-
return new Type(Type::TYPE_CALLABLE);
187+
return static::callable();
170188
case 'null':
189+
return static::null();
171190
case 'void':
172-
return new Type(Type::TYPE_NULL);
191+
return static::void();
173192
case 'numeric':
174193
return static::parseDecl('int|float');
194+
case 'mixed':
195+
return static::mixed();
175196
}
176197
if (strpos($decl, '|') !== false || strpos($decl, '&') !== false || strpos($decl, '(') !== false) {
177198
return self::parseCompexDecl($decl)->simplify();
178199
}
179200
if (substr($decl, -2) === '[]') {
180201
$type = static::parseDecl(substr($decl, 0, -2));
181-
return new Type(Type::TYPE_ARRAY, [$type]);
202+
return static::array($type);
182203
}
183204
$regex = '(^([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\\\)*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$)';
184-
if (!preg_match($regex, $decl)) {
185-
throw new RuntimeException("Unknown type declaration found: $decl");
205+
if ($decl === 'unknown' || !preg_match($regex, $decl)) {
206+
return static::unknown();
186207
}
187-
return new Type(Type::TYPE_OBJECT, [], $decl);
208+
return static::object($decl);
188209
}
189210

190211
private static function parseCompexDecl(string $decl): Type
@@ -231,9 +252,9 @@ private static function parseCompexDecl(string $decl): Type
231252
$combinator = substr($decl, $pos, 1);
232253
}
233254
if ($combinator === '|') {
234-
return new Type(Type::TYPE_UNION, [$left, $right]);
255+
return static::union($left, $right);
235256
} elseif ($combinator === '&') {
236-
return new Type(Type::TYPE_INTERSECTION, [$left, $right]);
257+
return static::intersection($left, $right);
237258
}
238259
throw new RuntimeException("Unknown combinator $combinator");
239260
}
@@ -258,16 +279,11 @@ public static function fromOpType(Op\Type $type): Type
258279
return self::nullable(static::fromOpType($type->subtype));
259280
}
260281
if ($type instanceof Op\Type\Union) {
261-
return (new Type(
262-
Type::TYPE_UNION,
263-
array_map(fn($sub) => static::fromOpType($sub), $type->subtypes)
264-
))->simplify();
282+
return static::union(...array_map(fn($sub) => static::fromOpType($sub), $type->subtypes))->simplify();
265283
}
266284
if ($type instanceof Op\Type\Intersection) {
267-
return (new Type(
268-
Type::TYPE_INTERSECTION,
269-
array_map(fn($sub) => static::fromOpType($sub), $type->subtypes)
270-
))->simplify();
285+
return static::intersection(...array_map(fn($sub) => static::fromOpType($sub), $type->subtypes)
286+
)->simplify();
271287
}
272288
throw new LogicException("Unknown type " . $type->getType());
273289
}

lib/PHPCfg/Types/Type.php

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ class Type
3535
self::TYPE_INTERSECTION => self::TYPE_INTERSECTION,
3636
];
3737

38-
public int $type = 0;
38+
public readonly int $type;
3939

4040
/**
4141
* @var Type[]
4242
*/
43-
public array $subTypes = [];
43+
public readonly array $subTypes;
4444

4545
/**
4646
* @var string
4747
*/
48-
public string $userType = '';
48+
public readonly ?string $userType;
4949

5050
/**
5151
* Get the primitives
@@ -74,18 +74,23 @@ public static function getPrimitives(): array
7474
public function __construct(int $type, array $subTypes = [], ?string $userType = null)
7575
{
7676
$this->type = $type;
77-
if ($type === self::TYPE_OBJECT) {
78-
$this->userType = (string) $userType;
77+
if ($type === self::TYPE_OBJECT && $userType !== null) {
78+
$this->userType = $userType;
79+
$this->subTypes = [];
7980
} elseif (isset(self::HAS_SUBTYPES[$type])) {
80-
$this->subTypes = $subTypes;
81-
foreach ($subTypes as $sub) {
82-
if (!$sub instanceof Type) {
83-
throw new RuntimeException("Sub types must implement Type");
84-
}
85-
}
81+
$this->setSubTypes(...$subTypes);
82+
$this->userType = null;
83+
} else {
84+
$this->userType = null;
85+
$this->subTypes = [];
8686
}
8787
}
8888

89+
private function setSubTypes(Type ...$subTypes): void
90+
{
91+
$this->subTypes = $subTypes;
92+
}
93+
8994
/**
9095
* @return string
9196
*/
@@ -96,6 +101,9 @@ public function __toString(): string
96101
if ($this->type === Type::TYPE_UNKNOWN) {
97102
$ctr--;
98103
return "unknown";
104+
} elseif ($this->type === Type::TYPE_VOID) {
105+
$ctr--;
106+
return 'void';
99107
}
100108
$primitives = self::getPrimitives();
101109
if (isset($primitives[$this->type])) {
@@ -109,10 +117,15 @@ public function __toString(): string
109117
}
110118
$value = '';
111119
if ($this->type === Type::TYPE_UNION) {
112-
$value = implode('|', $this->subTypes);
120+
if ($this->equals(Helper::mixed())) {
121+
$value = 'mixed';
122+
} else {
123+
$value = implode('|', $this->subTypes);
124+
}
113125
} elseif ($this->type === Type::TYPE_INTERSECTION) {
114126
$value = implode('&', $this->subTypes);
115127
} else {
128+
$ctr = 0;
116129
throw new RuntimeException("Assertion failure: unknown type {$this->type}");
117130
}
118131
$ctr--;
@@ -157,22 +170,53 @@ public function simplify(): static
157170
if ($this->type !== Type::TYPE_UNION && $this->type !== Type::TYPE_INTERSECTION) {
158171
return $this;
159172
}
173+
$rerun = false;
160174
$new = [];
175+
161176
foreach ($this->subTypes as $subType) {
162177
$subType = $subType->simplify();
163178
if ($this->type === $subType->type) {
164179
$new = array_merge($new, $subType->subTypes);
180+
// Flattened, re-run simplification
181+
$rerun = true;
165182
} else {
166-
$new[] = $subType->simplify();
183+
$simplifiedSubType = $subType->simplify();
184+
$skip = false;
185+
foreach ($new as $t) {
186+
if ($t->equals($simplifiedSubType)) {
187+
// Skip duplicate types
188+
$skip = true;
189+
}
190+
}
191+
if (!$skip) {
192+
$new[] = $subType->simplify();
193+
}
167194
}
168195
}
169196
// TODO: compute redundant unions
170197
if (count($new) === 1) {
171-
return $new[0];
198+
$type = $new[0];
172199
} elseif (empty($new)) {
173-
return Parser::void();
200+
return Helper::void();
201+
} else {
202+
$type = (new Type($this->type, $new));
174203
}
175-
return (new Type($this->type, $new));
204+
return $rerun ? $type->simplify() : $type;
205+
}
206+
207+
public function resolves(Type $type): bool
208+
{
209+
if ($this->equals($type)) {
210+
return true;
211+
}
212+
if ($this->type !== Type::TYPE_OBJECT || $type->type !== Type::TYPE_OBJECT) {
213+
return false;
214+
}
215+
// check to see if this is a super-set of type
216+
if ($this->userType === null) {
217+
return true;
218+
}
219+
return false;
176220
}
177221

178222
/**
@@ -189,6 +233,12 @@ public function equals(Type $type): bool
189233
return false;
190234
}
191235
if ($type->type === Type::TYPE_OBJECT) {
236+
if ($type->userType === null && $this->userType === null) {
237+
// not reachable since top $type === $this check catches
238+
} elseif ($type->userType !== null XOR $this->userType !== null) {
239+
// One is typed, the other isn't
240+
return false;
241+
}
192242
return strtolower($type->userType) === strtolower($this->userType);
193243
}
194244
// TODO: handle sub types
@@ -225,18 +275,18 @@ public function removeType(Type $type): static
225275
if (!isset(self::HAS_SUBTYPES[$this->type])) {
226276
if ($this->equals($type)) {
227277
// left with an unknown type
228-
return Parser::unknown();
278+
return Helper::unknown();
229279
}
230280
return $this;
231281
}
232282
$new = [];
233283
foreach ($this->subTypes as $key => $st) {
234-
if (!$st->equals($type)) {
284+
if (!$type->resolves($st)) {
235285
$new[] = $st;
236286
}
237287
}
238288
if (empty($new)) {
239-
return Parser::void();
289+
return Helper::void();
240290
} elseif (count($new) === 1) {
241291
return $new[0];
242292
}

0 commit comments

Comments
 (0)