protected function resolveVarOp(Operand $var, Op $op, SplObjectStorage $resolved) { $method = 'resolveOp_' . $op->getType(); if (method_exists($this, $method)) { return call_user_func([$this, $method], $var, $op, $resolved); } switch ($op->getType()) { case 'Expr_InstanceOf': case 'Expr_BinaryOp_Equal': case 'Expr_BinaryOp_NotEqual': case 'Expr_BinaryOp_Greater': case 'Expr_BinaryOp_GreaterOrEqual': case 'Expr_BinaryOp_Identical': case 'Expr_BinaryOp_NotIdentical': case 'Expr_BinaryOp_Smaller': case 'Expr_BinaryOp_SmallerOrEqual': case 'Expr_BinaryOp_LogicalAnd': case 'Expr_BinaryOp_LogicalOr': case 'Expr_BinaryOp_LogicalXor': case 'Expr_BooleanNot': case 'Expr_Cast_Bool': case 'Expr_Empty': case 'Expr_Isset': return [Type::bool()]; case 'Expr_BinaryOp_BitwiseAnd': case 'Expr_BinaryOp_BitwiseOr': case 'Expr_BinaryOp_BitwiseXor': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_STRING, Type::TYPE_STRING]: return [Type::string()]; default: return [Type::int()]; } } return false; case 'Expr_BitwiseNot': if ($resolved->contains($op->expr)) { switch ($resolved[$op->expr]->type) { case Type::TYPE_STRING: return [Type::string()]; default: return [Type::int()]; } } return false; case 'Expr_BinaryOp_Div': case 'Expr_BinaryOp_Plus': case 'Expr_BinaryOp_Minus': case 'Expr_BinaryOp_Mul': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_LONG, Type::TYPE_LONG]: return [Type::int()]; case [Type::TYPE_DOUBLE, TYPE::TYPE_LONG]: case [Type::TYPE_LONG, TYPE::TYPE_DOUBLE]: case [Type::TYPE_DOUBLE, TYPE::TYPE_DOUBLE]: return [Type::float()]; case [Type::TYPE_ARRAY, Type::TYPE_ARRAY]: $sub = $this->computeMergedType(array_merge($resolved[$op->left]->subTypes, $resolved[$op->right]->subTypes)); if ($sub) { return [new Type(Type::TYPE_ARRAY, [$sub])]; } return [new Type(Type::TYPE_ARRAY)]; default: return [Type::mixed()]; throw new \RuntimeException("Math op on unknown types {$resolved[$op->left]} + {$resolved[$op->right]}"); } } return false; case 'Expr_BinaryOp_Concat': case 'Expr_Cast_String': case 'Expr_ConcatList': return [Type::string()]; case 'Expr_BinaryOp_Mod': case 'Expr_BinaryOp_ShiftLeft': case 'Expr_BinaryOp_ShiftRight': case 'Expr_Cast_Int': case 'Expr_Print': return [Type::int()]; case 'Expr_Cast_Double': return [Type::float()]; case 'Expr_UnaryMinus': case 'Expr_UnaryPlus': if ($resolved->contains($op->expr)) { switch ($resolved[$op->expr]->type) { case Type::TYPE_LONG: case Type::TYPE_DOUBLE: return [$resolved[$op->expr]]; } return [Type::numeric()]; } return false; case 'Expr_Eval': return false; case 'Iterator_Key': if ($resolved->contains($op->var)) { // TODO: implement this as well return false; } return false; case 'Expr_Exit': case 'Iterator_Reset': return [Type::null()]; case 'Iterator_Valid': return [Type::bool()]; case 'Iterator_Value': if ($resolved->contains($op->var)) { if ($resolved[$op->var]->subTypes) { return $resolved[$op->var]->subTypes; } return false; } return false; case 'Expr_StaticCall': return $this->resolveMethodCall($op->class, $op->name, $op, $resolved); case 'Expr_MethodCall': return $this->resolveMethodCall($op->var, $op->name, $op, $resolved); case 'Expr_Yield': case 'Expr_Include': // TODO: we may be able to determine these... return false; } throw new \LogicException("Unknown variable op found: " . $op->getType()); }