1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Pinq\Analysis; |
4
|
|
|
|
5
|
|
|
use Pinq\Expressions as O; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Implementation of the expression type analyser. |
9
|
|
|
* |
10
|
|
|
* @author Elliot Levin <[email protected]> |
11
|
|
|
*/ |
12
|
|
|
class ExpressionAnalyser extends O\ExpressionVisitor implements IExpressionAnalyser |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* @var ITypeSystem |
16
|
|
|
*/ |
17
|
|
|
protected $typeSystem; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var IAnalysisContext |
21
|
|
|
*/ |
22
|
|
|
protected $analysisContext; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var \SplObjectStorage|IType[] |
26
|
|
|
*/ |
27
|
|
|
protected $analysis; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var \SplObjectStorage |
31
|
|
|
*/ |
32
|
|
|
protected $metadata; |
33
|
|
|
|
34
|
|
|
public function __construct(ITypeSystem $typeSystem) |
35
|
|
|
{ |
36
|
|
|
$this->typeSystem = $typeSystem; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
public function getTypeSystem() |
40
|
|
|
{ |
41
|
|
|
return $this->typeSystem; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
public function createAnalysisContext(O\IEvaluationContext $evaluationContext) |
45
|
|
|
{ |
46
|
|
|
return new AnalysisContext($this->typeSystem, $evaluationContext); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
public function analyse(IAnalysisContext $analysisContext, O\Expression $expression) |
50
|
|
|
{ |
51
|
|
|
$this->analysisContext = $analysisContext; |
52
|
|
|
$this->analysis = new \SplObjectStorage(); |
53
|
|
|
$this->metadata = new \SplObjectStorage(); |
54
|
|
|
|
55
|
|
|
$this->walk($expression); |
56
|
|
|
|
57
|
|
|
return new TypeAnalysis($this->typeSystem, $expression, $this->analysis, $this->metadata); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
public function visitArray(O\ArrayExpression $expression) |
61
|
|
|
{ |
62
|
|
|
$this->walkAll($expression->getItems()); |
63
|
|
|
$this->analysis[$expression] = $this->typeSystem->getNativeType(INativeType::TYPE_ARRAY); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
public function visitArrayItem(O\ArrayItemExpression $expression) |
67
|
|
|
{ |
68
|
|
|
$this->walk($expression->getKey()); |
69
|
|
|
$this->walk($expression->getValue()); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
public function visitAssignment(O\AssignmentExpression $expression) |
73
|
|
|
{ |
74
|
|
|
$assignTo = $expression->getAssignTo(); |
75
|
|
|
$assignmentValue = $expression->getAssignmentValue(); |
76
|
|
|
|
77
|
|
|
$this->walk($assignmentValue); |
78
|
|
|
|
79
|
|
|
$operator = $expression->getOperator(); |
80
|
|
|
if ($operator === O\Operators\Assignment::EQUAL) { |
81
|
|
|
$this->analysisContext->setExpressionType($assignTo, $this->analysis[$assignmentValue]); |
82
|
|
|
$this->analysis[$expression] = $this->analysis[$assignmentValue]; |
83
|
|
|
} elseif ($operator === O\Operators\Assignment::EQUAL_REFERENCE) { |
84
|
|
|
$this->analysisContext->removeExpressionType($assignTo); |
85
|
|
|
$this->analysisContext->setExpressionType($assignTo, $this->analysis[$assignmentValue]); |
86
|
|
|
$this->analysisContext->createReference($assignTo, $assignmentValue); |
87
|
|
|
$this->analysis[$expression] = $this->analysis[$assignmentValue]; |
88
|
|
|
} else { |
89
|
|
|
$this->walk($assignTo); |
90
|
|
|
$binaryOperation = $this->typeSystem->getBinaryOperation( |
91
|
|
|
$this->analysis[$assignTo], |
92
|
|
|
O\Operators\Assignment::toBinaryOperator($operator), |
93
|
|
|
$this->analysis[$assignmentValue] |
94
|
|
|
); |
95
|
|
|
$this->analysis[$expression] = $binaryOperation->getReturnType(); |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
public function visitBinaryOperation(O\BinaryOperationExpression $expression) |
100
|
|
|
{ |
101
|
|
|
$this->walk($expression->getLeftOperand()); |
102
|
|
|
$this->walk($expression->getRightOperand()); |
103
|
|
|
|
104
|
|
|
$binaryOperation = $this->typeSystem->getBinaryOperation( |
105
|
|
|
$this->analysis[$expression->getLeftOperand()], |
106
|
|
|
$expression->getOperator(), |
107
|
|
|
$this->analysis[$expression->getRightOperand()] |
108
|
|
|
); |
109
|
|
|
$this->metadata[$expression] = $binaryOperation; |
110
|
|
|
$this->analysis[$expression] = $binaryOperation->getReturnType(); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
protected function addTypeOperation(O\Expression $expression, ITypeOperation $typeOperation) |
114
|
|
|
{ |
115
|
|
|
$this->metadata[$expression] = $typeOperation; |
116
|
|
|
$this->analysis[$expression] = $typeOperation->getReturnType(); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
public function visitUnaryOperation(O\UnaryOperationExpression $expression) |
120
|
|
|
{ |
121
|
|
|
$this->walk($expression->getOperand()); |
122
|
|
|
$this->addTypeOperation( |
123
|
|
|
$expression, |
124
|
|
|
$this->analysis[$expression->getOperand()]->getUnaryOperation($expression) |
125
|
|
|
); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
public function visitCast(O\CastExpression $expression) |
129
|
|
|
{ |
130
|
|
|
$this->walk($expression->getCastValue()); |
131
|
|
|
$this->addTypeOperation( |
132
|
|
|
$expression, |
133
|
|
|
$this->analysis[$expression->getCastValue()]->getCast($expression) |
134
|
|
|
); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
public function visitConstant(O\ConstantExpression $expression) |
138
|
|
|
{ |
139
|
|
|
$this->verifyConstantDefined($expression->getName()); |
140
|
|
|
|
141
|
|
|
$this->analysis[$expression] = $this->typeSystem->getTypeFromValue($expression->evaluate($this->analysisContext->getEvaluationContext())); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
public function visitClassConstant(O\ClassConstantExpression $expression) |
145
|
|
|
{ |
146
|
|
|
$this->validateStaticClassName($expression->getClass(), 'class constant'); |
147
|
|
|
$this->verifyConstantDefined($expression->getClass()->getValue() . '::' . $expression->getName()); |
148
|
|
|
|
149
|
|
|
$this->analysis[$expression] = $this->typeSystem->getTypeFromValue($expression->evaluate($this->analysisContext->getEvaluationContext())); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
private function verifyConstantDefined($constantName) |
153
|
|
|
{ |
154
|
|
|
if (!defined($constantName)) { |
155
|
|
|
throw new TypeException('Cannot get type from constant %s: constant is not defined', $constantName); |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
public function visitEmpty(O\EmptyExpression $expression) |
160
|
|
|
{ |
161
|
|
|
$this->walk($expression->getValue()); |
162
|
|
|
$this->analysis[$expression] = $this->typeSystem->getNativeType(INativeType::TYPE_BOOL); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
public function visitIsset(O\IssetExpression $expression) |
166
|
|
|
{ |
167
|
|
|
$this->walkAll($expression->getValues()); |
168
|
|
|
$this->analysis[$expression] = $this->typeSystem->getNativeType(INativeType::TYPE_BOOL); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function visitUnset(O\UnsetExpression $expression) |
172
|
|
|
{ |
173
|
|
|
$this->walkAll($expression->getValues()); |
174
|
|
|
$this->analysis[$expression] = $this->typeSystem->getType(INativeType::TYPE_NULL); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
public function visitField(O\FieldExpression $expression) |
178
|
|
|
{ |
179
|
|
|
$this->walk($expression->getValue()); |
180
|
|
|
$this->walk($expression->getName()); |
181
|
|
|
|
182
|
|
|
$this->addTypeOperation( |
183
|
|
|
$expression, |
184
|
|
|
$this->analysis[$expression->getValue()]->getField($expression) |
185
|
|
|
); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
public function visitMethodCall(O\MethodCallExpression $expression) |
189
|
|
|
{ |
190
|
|
|
$this->walk($expression->getValue()); |
191
|
|
|
$this->walk($expression->getName()); |
192
|
|
|
$this->walkAll($expression->getArguments()); |
193
|
|
|
|
194
|
|
|
$this->addTypeOperation( |
195
|
|
|
$expression, |
196
|
|
|
$this->analysis[$expression->getValue()]->getMethod($expression) |
197
|
|
|
); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
public function visitIndex(O\IndexExpression $expression) |
201
|
|
|
{ |
202
|
|
|
$this->walk($expression->getValue()); |
203
|
|
|
$this->walk($expression->getIndex()); |
204
|
|
|
|
205
|
|
|
$this->addTypeOperation( |
206
|
|
|
$expression, |
207
|
|
|
$this->analysis[$expression->getValue()]->getIndex($expression) |
208
|
|
|
); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
public function visitInvocation(O\InvocationExpression $expression) |
212
|
|
|
{ |
213
|
|
|
$this->walk($expression->getValue()); |
214
|
|
|
$this->walkAll($expression->getArguments()); |
215
|
|
|
|
216
|
|
|
$this->addTypeOperation( |
217
|
|
|
$expression, |
218
|
|
|
$this->analysis[$expression->getValue()]->getInvocation($expression) |
219
|
|
|
); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
public function visitFunctionCall(O\FunctionCallExpression $expression) |
223
|
|
|
{ |
224
|
|
|
$nameExpression = $expression->getName(); |
225
|
|
|
$this->walk($nameExpression); |
226
|
|
|
$this->walkAll($expression->getArguments()); |
227
|
|
|
|
228
|
|
|
if ($nameExpression instanceof O\ValueExpression) { |
229
|
|
|
$function = $this->typeSystem->getFunction($nameExpression->getValue()); |
230
|
|
|
$this->metadata[$expression] = $function; |
231
|
|
|
$this->analysis[$expression] = $function->getReturnType(); |
232
|
|
|
} else { |
233
|
|
|
throw new TypeException('Invalid function expression: dynamic function calls are not allowed'); |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
protected function validateStaticClassName(O\Expression $expression, $type) |
238
|
|
|
{ |
239
|
|
|
if ($expression instanceof O\ValueExpression) { |
240
|
|
|
return $expression->getValue(); |
241
|
|
|
} else { |
242
|
|
|
throw new TypeException('Invalid %s expression: dynamic class types are not supported', $type); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
View Code Duplication |
public function visitStaticMethodCall(O\StaticMethodCallExpression $expression) |
|
|
|
|
247
|
|
|
{ |
248
|
|
|
$classExpression = $expression->getClass(); |
249
|
|
|
$this->walk($classExpression); |
250
|
|
|
$this->walk($expression->getName()); |
251
|
|
|
$this->walkAll($expression->getArguments()); |
252
|
|
|
|
253
|
|
|
$class = $this->validateStaticClassName($classExpression, 'static method call'); |
254
|
|
|
$this->addTypeOperation( |
255
|
|
|
$expression, |
256
|
|
|
$this->typeSystem->getObjectType($class)->getStaticMethod($expression) |
257
|
|
|
); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
View Code Duplication |
public function visitStaticField(O\StaticFieldExpression $expression) |
|
|
|
|
261
|
|
|
{ |
262
|
|
|
$classExpression = $expression->getClass(); |
263
|
|
|
$this->walk($classExpression); |
264
|
|
|
$this->walk($expression->getName()); |
265
|
|
|
|
266
|
|
|
$class = $this->validateStaticClassName($classExpression, 'static field'); |
267
|
|
|
|
268
|
|
|
$this->addTypeOperation( |
269
|
|
|
$expression, |
270
|
|
|
$this->typeSystem->getObjectType($class)->getStaticField($expression) |
271
|
|
|
); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
View Code Duplication |
public function visitNew(O\NewExpression $expression) |
|
|
|
|
275
|
|
|
{ |
276
|
|
|
$classExpression = $expression->getClass(); |
277
|
|
|
$this->walk($classExpression); |
278
|
|
|
$this->walkAll($expression->getArguments()); |
279
|
|
|
|
280
|
|
|
$class = $this->validateStaticClassName($classExpression, 'new'); |
281
|
|
|
$this->addTypeOperation( |
282
|
|
|
$expression, |
283
|
|
|
$this->typeSystem->getObjectType($class)->getConstructor($expression) |
284
|
|
|
); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
public function visitTernary(O\TernaryExpression $expression) |
288
|
|
|
{ |
289
|
|
|
$this->walk($expression->getCondition()); |
290
|
|
|
$this->walk($expression->getIfTrue()); |
291
|
|
|
$this->walk($expression->getIfFalse()); |
292
|
|
|
|
293
|
|
|
$this->analysis[$expression] = $this->typeSystem->getCommonAncestorType( |
294
|
|
|
$this->analysis[$expression->hasIfTrue() ? $expression->getIfTrue() : $expression->getCondition()], |
295
|
|
|
$this->analysis[$expression->getIfFalse()] |
296
|
|
|
); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
public function visitVariable(O\VariableExpression $expression) |
300
|
|
|
{ |
301
|
|
|
$nameExpression = $expression->getName(); |
302
|
|
|
$this->walk($nameExpression); |
303
|
|
|
|
304
|
|
|
$type = $this->analysisContext->getExpressionType($expression); |
305
|
|
|
if ($type === null) { |
306
|
|
|
throw new TypeException( |
307
|
|
|
'Invalid variable expression: \'%s\' type is unknown', |
308
|
|
|
$nameExpression->compileDebug()); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$this->analysis[$expression] = $type; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
public function visitValue(O\ValueExpression $expression) |
315
|
|
|
{ |
316
|
|
|
$this->analysis[$expression] = $this->typeSystem->getTypeFromValue($expression->getValue()); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
public function visitClosure(O\ClosureExpression $expression) |
320
|
|
|
{ |
321
|
|
|
$originalContext = $this->analysisContext; |
322
|
|
|
$this->analysisContext = $originalContext->inNewScope(); |
323
|
|
|
|
324
|
|
|
foreach ($expression->getParameters() as $parameter) { |
325
|
|
|
$this->walk($parameter); |
326
|
|
|
$typeHintType = $this->typeSystem->getTypeFromTypeHint($parameter->getTypeHint()); |
327
|
|
|
if (!$parameter->hasDefaultValue() |
328
|
|
|
|| $this->analysis[$parameter->getDefaultValue()]->isEqualTo($typeHintType) |
329
|
|
|
) { |
330
|
|
|
$this->analysisContext->setExpressionType($parameter->asVariable(), $typeHintType); |
331
|
|
|
} else { |
332
|
|
|
$this->analysisContext->setExpressionType( |
333
|
|
|
$parameter->asVariable(), |
334
|
|
|
$this->typeSystem->getNativeType(INativeType::TYPE_MIXED) |
335
|
|
|
); |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
foreach ($expression->getUsedVariables() as $usedVariable) { |
340
|
|
|
$variable = $usedVariable->asVariable(); |
341
|
|
|
//TODO: handle references with used variables. Probably impossible though. |
342
|
|
|
$this->analysisContext->setExpressionType($variable, $originalContext->getExpressionType($variable)); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
$this->walkAll($expression->getBodyExpressions()); |
346
|
|
|
$this->analysis[$expression] = $this->typeSystem->getObjectType('Closure'); |
347
|
|
|
$this->analysisContext = $originalContext; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
public function visitReturn(O\ReturnExpression $expression) |
351
|
|
|
{ |
352
|
|
|
$this->walk($expression->getValue()); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
public function visitThrow(O\ThrowExpression $expression) |
356
|
|
|
{ |
357
|
|
|
$this->walk($expression->getException()); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
public function visitParameter(O\ParameterExpression $expression) |
361
|
|
|
{ |
362
|
|
|
$this->walk($expression->getDefaultValue()); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
public function visitArgument(O\ArgumentExpression $expression) |
366
|
|
|
{ |
367
|
|
|
$this->walk($expression->getValue()); |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.