Beispiel #1
0
 /**
  * Compiles a method call
  *
  * @param Expression $expr
  * @param CompilationContext $compilationContext
  */
 public function compile(Expression $expr, CompilationContext $compilationContext)
 {
     $expression = $expr->getExpression();
     $exprVariable = new Expression($expression['variable']);
     $exprVariable->setReadOnly(true);
     $exprCompiledVariable = $exprVariable->compile($compilationContext);
     $builtInType = false;
     switch ($exprCompiledVariable->getType()) {
         case 'variable':
             $variableVariable = $compilationContext->symbolTable->getVariableForRead($exprCompiledVariable->getCode(), $compilationContext, $expression);
             switch ($variableVariable->getType()) {
                 case 'variable':
                     $caller = $variableVariable;
                     break;
                 default:
                     /* Check if there is a built-in type optimizer available */
                     $builtInTypeClass = 'Zephir\\Types\\' . ucfirst($variableVariable->getType()) . 'Type';
                     if (class_exists($builtInTypeClass)) {
                         /**
                          * @var $builtInType \Zephir\Types\AbstractType
                          */
                         $builtInType = new $builtInTypeClass();
                         $caller = $exprCompiledVariable;
                     } else {
                         throw new CompilerException("Methods cannot be called on variable type: " . $variableVariable->getType(), $expression);
                     }
             }
             break;
         default:
             /* Check if there is a built-in type optimizer available */
             $builtInTypeClass = 'Zephir\\Types\\' . ucfirst($exprCompiledVariable->getType()) . 'Type';
             if (class_exists($builtInTypeClass)) {
                 $builtInType = new $builtInTypeClass();
                 $caller = $exprCompiledVariable;
             } else {
                 throw new CompilerException("Cannot use expression: " . $exprCompiledVariable->getType() . " as method caller", $expression['variable']);
             }
     }
     $codePrinter = $compilationContext->codePrinter;
     $type = $expression['call-type'];
     /**
      * In normal method calls and dynamic string method calls we just use the name given by the user
      */
     if ($type == self::CALL_NORMAL || $type == self::CALL_DYNAMIC_STRING) {
         $methodName = strtolower($expression['name']);
     } else {
         $variableMethod = $compilationContext->symbolTable->getVariableForRead($expression['name'], $compilationContext, $expression);
         if (is_object($builtInType)) {
             throw new CompilerException("Dynamic method invocation for type: " . $variableMethod->getType() . " is not supported", $expression);
         }
         if ($variableMethod->isNotVariableAndString()) {
             throw new CompilerException("Cannot use variable type: " . $variableMethod->getType() . " as dynamic method name", $expression);
         }
     }
     $symbolVariable = null;
     /**
      * Create temporary variable if needed
      */
     $mustInit = false;
     $isExpecting = $expr->isExpectingReturn();
     if ($isExpecting) {
         $symbolVariable = $expr->getExpectingVariable();
         if (is_object($symbolVariable)) {
             $readDetector = new ReadDetector($expression);
             if ($caller == $symbolVariable || $readDetector->detect($symbolVariable->getName(), $expression)) {
                 $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression);
             } else {
                 $mustInit = true;
             }
         } else {
             $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression);
         }
     }
     /**
      * Method calls only return zvals so we need to validate the target variable is also a zval
      */
     if (!$builtInType) {
         if ($isExpecting) {
             if (!$symbolVariable->isVariable()) {
                 throw new CompilerException("Returned values by functions can only be assigned to variant variables", $expression);
             }
             /**
              * At this point, we don't know the exact dynamic type returned by the method call
              */
             $symbolVariable->setDynamicTypes('undefined');
         }
     } else {
         return $builtInType->invokeMethod($methodName, $caller, $compilationContext, $this, $expression);
     }
     $check = true;
     if (isset($expression['check'])) {
         $check = $expression['check'];
     }
     /**
      * Try to check if the method exist in the callee, only when method call is self::CALL_NORMAL
      */
     if ($type == self::CALL_NORMAL) {
         if ($variableVariable->getRealName() == 'this') {
             $classDefinition = $compilationContext->classDefinition;
             if (!$classDefinition->hasMethod($methodName)) {
                 if ($check) {
                     $found = false;
                     $interfaces = $classDefinition->isAbstract() ? $classDefinition->getImplementedInterfaces() : null;
                     if (is_array($interfaces)) {
                         $compiler = $compilationContext->compiler;
                         foreach ($interfaces as $interface) {
                             $classInterfaceDefinition = $compiler->getClassDefinition($interface);
                             if ($classInterfaceDefinition->hasMethod($methodName)) {
                                 $found = true;
                                 $classMethod = $classInterfaceDefinition->getMethod($methodName);
                                 break;
                             }
                         }
                     }
                     if (!$found) {
                         $possibleMethod = $classDefinition->getPossibleMethodName($expression['name']);
                         if ($possibleMethod && $expression['name'] != $possibleMethod) {
                             throw new CompilerException("Class '" . $classDefinition->getCompleteName() . "' does not implement method: '" . $expression['name'] . "'. Did you mean '" . $possibleMethod . "'?", $expression);
                         } else {
                             throw new CompilerException("Class '" . $classDefinition->getCompleteName() . "' does not implement method: '" . $expression['name'] . "'", $expression);
                         }
                     }
                 }
             } else {
                 if ($check) {
                     $classMethod = $classDefinition->getMethod($methodName);
                 }
             }
             if ($check) {
                 /**
                  * Private methods must be called in their declaration scope
                  */
                 if ($classMethod->isPrivate()) {
                     if ($classMethod->getClassDefinition() != $classDefinition) {
                         throw new CompilerException("Cannot call private method '" . $expression['name'] . "' out of its scope", $expression);
                     }
                 }
                 /**
                  * Try to produce an exception if method is called with a wrong number of parameters
                  */
                 if (isset($expression['parameters'])) {
                     $callNumberParameters = count($expression['parameters']);
                 } else {
                     $callNumberParameters = 0;
                 }
                 $expectedNumberParameters = $classMethod->getNumberOfRequiredParameters();
                 if (!$expectedNumberParameters && $callNumberParameters > 0) {
                     $numberParameters = $classMethod->getNumberOfParameters();
                     if ($callNumberParameters > $numberParameters) {
                         throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression);
                     }
                 }
                 if ($callNumberParameters < $expectedNumberParameters) {
                     throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression);
                 }
                 $method = $classMethod;
             }
         } else {
             /**
              * Variables whose dynamic type is 'object' can be used
              * to determine method existence in compile time
              */
             if ($check && $variableVariable->hasAnyDynamicType('object')) {
                 $classTypes = $variableVariable->getClassTypes();
                 if (count($classTypes)) {
                     $numberImplemented = 0;
                     $compiler = $compilationContext->compiler;
                     foreach ($classTypes as $classType) {
                         if ($compiler->isClass($classType) || $compiler->isInterface($classType) || $compiler->isBundledClass($classType) || $compiler->isBundledInterface($classType)) {
                             if ($compiler->isClass($classType) || $compiler->isInterface($classType)) {
                                 $classDefinition = $compiler->getClassDefinition($classType);
                             } else {
                                 $classDefinition = $compiler->getInternalClassDefinition($classType);
                             }
                             if (!$classDefinition) {
                                 throw new CompilerException("Cannot locate class definition for class " . $classType, $expression);
                             }
                             if (!$classDefinition->hasMethod($methodName)) {
                                 if (!$classDefinition->isInterface()) {
                                     if (count($classTypes) == 1) {
                                         throw new CompilerException("Class '" . $classType . "' does not implement method: '" . $expression['name'] . "'", $expression);
                                     }
                                 }
                                 continue;
                             }
                             $method = $classDefinition->getMethod($methodName);
                             /**
                              * Private methods must be called in their declaration scope
                              */
                             if ($method->isPrivate()) {
                                 if ($method->getClassDefinition() != $classDefinition) {
                                     throw new CompilerException("Cannot call private method '" . $expression['name'] . "' out of its scope", $expression);
                                 }
                             }
                             /**
                              * Check visibility for protected methods
                              */
                             if ($method->isProtected() && $method->getClassDefinition() != $classDefinition && $method->getClassDefinition() != $classDefinition->getExtendsClass()) {
                                 throw new CompilerException("Cannot call protected method '" . $expression['name'] . "' out of its scope", $expression);
                             }
                             /**
                              * Try to produce an exception if a method is called with a wrong number of parameters
                              * We only check extension parameters if methods are extension methods
                              * Internal methods may have invalid Reflection information
                              */
                             if ($method instanceof ClassMethod && !$method->isBundled()) {
                                 if (isset($expression['parameters'])) {
                                     $callNumberParameters = count($expression['parameters']);
                                 } else {
                                     $callNumberParameters = 0;
                                 }
                                 $classMethod = $classDefinition->getMethod($methodName);
                                 $expectedNumberParameters = $classMethod->getNumberOfRequiredParameters();
                                 if (!$expectedNumberParameters && $callNumberParameters > 0) {
                                     $numberParameters = $classMethod->getNumberOfParameters();
                                     if ($callNumberParameters > $numberParameters) {
                                         $className = $classDefinition->getCompleteName();
                                         throw new CompilerException("Method '" . $className . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression);
                                     }
                                 }
                                 if ($callNumberParameters < $expectedNumberParameters) {
                                     throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression);
                                 }
                             }
                             /**
                              * The method is checked in the first class that implements the method
                              * We could probably have collisions here
                              */
                             $numberImplemented++;
                             break;
                         } else {
                             $numberImplemented++;
                             $compilationContext->logger->warning("Class \"" . $classType . "\" does not exist at compile time", "nonexistent-class", $expression);
                         }
                     }
                     if ($numberImplemented == 0) {
                         if (!$classDefinition->isInterface()) {
                             if (count($classTypes) > 1) {
                                 throw new CompilerException("None of classes: '" . join(' or ', $classTypes) . "' implement method: '" . $expression['name'] . "'", $expression);
                             } else {
                                 throw new CompilerException("Class '" . $classTypes[0] . "' does not implement method: '" . $expression['name'] . "'", $expression);
                             }
                         } else {
                             // @TODO, raise an exception here?
                         }
                     }
                 }
             }
         }
     }
     if (isset($method)) {
         $this->_reflection = $method;
     }
     /**
      * Transfer the return type-hint to the returned variable
      */
     if ($isExpecting) {
         if (isset($method)) {
             if ($method instanceof ClassMethod) {
                 if ($method->isVoid()) {
                     throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' is marked as 'void' and it does not return anything", $expression);
                 }
                 $returnClassTypes = $method->getReturnClassTypes();
                 if ($returnClassTypes !== null) {
                     $symbolVariable->setDynamicTypes('object');
                     foreach ($returnClassTypes as &$returnClassType) {
                         $returnClassType = $compilationContext->getFullName($returnClassType);
                     }
                     $symbolVariable->setClassTypes($returnClassTypes);
                 }
                 $returnTypes = $method->getReturnTypes();
                 if ($returnTypes !== null) {
                     foreach ($returnTypes as $dataType => $returnType) {
                         $symbolVariable->setDynamicTypes($dataType);
                     }
                 }
             }
         }
     }
     /**
      * Some parameters in internal methods receive parameters as references
      */
     if (isset($expression['parameters'])) {
         $references = array();
         if ($type == self::CALL_NORMAL || $type == self::CALL_DYNAMIC_STRING) {
             if (isset($method)) {
                 if ($method instanceof \ReflectionMethod) {
                     $position = 0;
                     foreach ($method->getParameters() as $parameter) {
                         if ($parameter->isPassedByReference()) {
                             $references[$position] = true;
                         }
                         $position++;
                     }
                 }
             }
         }
     }
     /**
      * Include fcall header
      */
     $compilationContext->headersManager->add('kernel/fcall');
     /**
      * Call methods must grown the stack
      */
     $compilationContext->symbolTable->mustGrownStack(true);
     /**
      * Mark references
      */
     if (isset($expression['parameters'])) {
         $params = $this->getResolvedParams($expression['parameters'], $compilationContext, $expression, isset($method) ? $method : null);
         if (count($references)) {
             foreach ($params as $position => $param) {
                 if (isset($references[$position])) {
                     $compilationContext->codePrinter->output('Z_SET_ISREF_P(' . $param . ');');
                 }
             }
         }
         // We check here if a correct parameter type is passed to the called method
         if ($type == self::CALL_NORMAL) {
             if (isset($method) && $method instanceof ClassMethod && isset($expression['parameters'])) {
                 $resolvedTypes = $this->getResolvedTypes();
                 $resolvedDynamicTypes = $this->getResolvedDynamicTypes();
                 //$typeInference = $method->getStaticTypeInferencePass();
                 foreach ($method->getParameters() as $n => $parameter) {
                     if (isset($parameter['data-type'])) {
                         if (!isset($resolvedTypes[$n])) {
                             continue;
                         }
                         /**
                          * If the passed parameter is different to the expected type we show a warning
                          */
                         if ($resolvedTypes[$n] != $parameter['data-type']) {
                             switch ($resolvedTypes[$n]) {
                                 case 'bool':
                                 case 'boolean':
                                     switch ($parameter['data-type']) {
                                         /* compatible types */
                                         case 'bool':
                                         case 'boolean':
                                         case 'variable':
                                             break;
                                         default:
                                             $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression);
                                             break;
                                     }
                                     break;
                                 case 'array':
                                     switch ($parameter['data-type']) {
                                         /* compatible types */
                                         case 'array':
                                         case 'variable':
                                             break;
                                         case 'callable':
                                             /**
                                              * Array can be a callable type, example: [$this, "method"]
                                              *
                                              * @todo we need to check this array if can...
                                              */
                                             break;
                                         default:
                                             $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression);
                                             break;
                                     }
                                     break;
                                 case 'callable':
                                     switch ($parameter['data-type']) {
                                         /* compatible types */
                                         case 'callable':
                                         case 'variable':
                                             break;
                                         default:
                                             $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression);
                                             break;
                                     }
                                     break;
                                 case 'string':
                                     switch ($parameter['data-type']) {
                                         /* compatible types */
                                         case 'string':
                                         case 'variable':
                                             break;
                                         default:
                                             $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression);
                                             break;
                                     }
                                     break;
                                     /**
                                      * Passing polymorphic variables to static typed parameters
                                      * could lead to potential unexpected type coercions
                                      */
                                 /**
                                  * Passing polymorphic variables to static typed parameters
                                  * could lead to potential unexpected type coercions
                                  */
                                 case 'variable':
                                     if ($resolvedDynamicTypes[$n] != $parameter['data-type']) {
                                         if ($resolvedDynamicTypes[$n] == 'undefined') {
                                             $compilationContext->logger->warning("Passing possible incorrect type to parameter: " . $classDefinition->getCompleteName() . '::' . $parameter[$n]['name'] . ', passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter[$n]['data-type'], "possible-wrong-parameter-undefined", $expression);
                                         }
                                         //echo '1: ', $resolvedTypes[$n], ' ', $resolvedDynamicTypes[$n], ' ', $parameter[0]['data-type'], ' ', PHP_EOL;
                                     }
                                     break;
                             }
                         }
                     }
                 }
             }
         }
     } else {
         $params = array();
     }
     // Add the last call status to the current symbol table
     $this->addCallStatusFlag($compilationContext);
     // Initialize non-temporary variables
     if ($mustInit) {
         $symbolVariable->setMustInitNull(true);
         $symbolVariable->trackVariant($compilationContext);
     }
     // Generate the code according to the call type
     if ($type == self::CALL_NORMAL || $type == self::CALL_DYNAMIC_STRING) {
         $realMethod = $this->getRealCalledMethod($compilationContext, $variableVariable, $methodName);
         $isInternal = false;
         if (is_object($realMethod[1])) {
             $isInternal = $realMethod[1]->isInternal();
             if ($isInternal && $realMethod[0] > 1) {
                 throw new CompilerException("Cannot resolve method: '" . $expression['name'] . "' in polymorphic variable", $expression);
             }
         }
         if (!$isInternal) {
             // Check if the method call can have an inline cache
             $methodCache = $compilationContext->cacheManager->getMethodCache();
             $cachePointer = $methodCache->get($compilationContext, $methodName, $variableVariable);
             if (!count($params)) {
                 if ($isExpecting) {
                     if ($symbolVariable->getName() == 'return_value') {
                         $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD(' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ');');
                     } else {
                         $codePrinter->output('ZEPHIR_CALL_METHOD(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ');');
                     }
                 } else {
                     $codePrinter->output('ZEPHIR_CALL_METHOD(NULL, ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ');');
                 }
             } else {
                 if ($isExpecting) {
                     if ($symbolVariable->getName() == 'return_value') {
                         $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD(' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ', ' . join(', ', $params) . ');');
                     } else {
                         $codePrinter->output('ZEPHIR_CALL_METHOD(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ', ' . join(', ', $params) . ');');
                     }
                 } else {
                     $codePrinter->output('ZEPHIR_CALL_METHOD(NULL, ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ', ' . join(', ', $params) . ');');
                 }
             }
         } else {
             if (!count($params)) {
                 if ($isExpecting) {
                     if ($symbolVariable->getName() == 'return_value') {
                         $codePrinter->output('ZEPHIR_RETURN_CALL_INTERNAL_METHOD_P0(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ');');
                     } else {
                         $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_P0(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $method->getInternalName() . ');');
                     }
                 } else {
                     $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_NORETURN_P0(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ');');
                 }
             } else {
                 if ($isExpecting) {
                     if ($symbolVariable->getName() == 'return_value') {
                         $codePrinter->output('ZEPHIR_RETURN_CALL_INTERNAL_METHOD_P' . count($params) . '(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ', ' . join(', ', $params) . ');');
                     } else {
                         $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_P' . count($params) . '(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $method->getInternalName() . ', ' . join(', ', $params) . ');');
                     }
                 } else {
                     $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_NORETURN_P' . count($params) . '(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ', ' . join(', ', $params) . ');');
                 }
             }
         }
     } else {
         if ($type == self::CALL_DYNAMIC) {
             switch ($variableMethod->getType()) {
                 case 'string':
                 case 'variable':
                     break;
                 default:
                     throw new Exception('Cannot use variable type: ' . $variableMethod->getType() . ' as method caller');
             }
             $cachePointer = 'NULL, 0';
             if (!count($params)) {
                 if ($isExpecting) {
                     if ($symbolVariable->getName() == 'return_value') {
                         $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD_ZVAL(' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ');');
                     } else {
                         $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ',  ' . $cachePointer . ');');
                     }
                 } else {
                     $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(NULL, ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ');');
                 }
             } else {
                 if ($isExpecting) {
                     if ($symbolVariable->getName() == 'return_value') {
                         $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD_ZVAL(' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ', ' . join(', ', $params) . ');');
                     } else {
                         $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ', ' . join(', ', $params) . ');');
                     }
                 } else {
                     $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(NULL, ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ', ' . join(', ', $params) . ');');
                 }
             }
         }
     }
     // Temporary variables must be copied if they have more than one reference
     foreach ($this->getMustCheckForCopyVariables() as $checkVariable) {
         $codePrinter->output('zephir_check_temp_parameter(' . $checkVariable . ');');
     }
     // We can mark temporary variables generated as idle
     foreach ($this->getTemporalVariables() as $tempVariable) {
         $tempVariable->setIdle(true);
     }
     // Release parameters marked as references
     if (isset($expression['parameters'])) {
         if (count($references)) {
             foreach ($params as $position => $param) {
                 if (isset($references[$position])) {
                     $compilationContext->codePrinter->output('Z_UNSET_ISREF_P(' . $param . ');');
                 }
             }
         }
     }
     $this->addCallStatusOrJump($compilationContext);
     if ($isExpecting) {
         return new CompiledExpression('variable', $symbolVariable->getRealName(), $expression);
     }
     return new CompiledExpression('null', null, $expression);
 }
Beispiel #2
0
 /**
  * Compiles a static method call
  *
  * @param array $expr
  * @param CompilationContext $compilationContext
  */
 public function compile(Expression $expr, CompilationContext $compilationContext)
 {
     $expression = $expr->getExpression();
     $methodName = strtolower($expression['name']);
     if (isset($expression['dynamic'])) {
         $dynamicMethod = $expression['dynamic'];
     } else {
         $dynamicMethod = false;
     }
     $symbolVariable = null;
     /**
      * Create temporary variable if needed
      */
     $mustInit = false;
     $isExpecting = $expr->isExpectingReturn();
     if ($isExpecting) {
         $symbolVariable = $expr->getExpectingVariable();
         if (is_object($symbolVariable)) {
             $readDetector = new ReadDetector($expression);
             if ($readDetector->detect($symbolVariable->getName(), $expression)) {
                 $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression);
             } else {
                 $mustInit = true;
             }
         } else {
             $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression);
         }
     }
     /**
      * Method calls only return zvals so we need to validate the target variable is also a zval
      */
     if ($isExpecting) {
         /**
          * At this point, we don't know the exact dynamic type returned by the static method call
          */
         $symbolVariable->setDynamicTypes('undefined');
         if ($symbolVariable->getType() != 'variable') {
             throw new CompilerException("Returned values by functions can only be assigned to variant variables", $expression);
         }
     }
     /**
      * Include fcall header
      */
     $compilationContext->headersManager->add('kernel/fcall');
     $compiler = $compilationContext->compiler;
     $dynamicClass = $expression['dynamic-class'];
     if (!$dynamicClass) {
         $className = $expression['class'];
         $classDefinition = false;
         if ($className != 'self' && $className != 'parent') {
             if (is_string($className)) {
                 $className = $compilationContext->getFullName($className);
                 if ($compiler->isClass($className)) {
                     $classDefinition = $compiler->getClassDefinition($className);
                 } else {
                     if ($compiler->isInternalClass($className)) {
                         $classDefinition = $compiler->getInternalClassDefinition($className);
                     } else {
                         throw new CompilerException("Class name: " . $className . " does not exist", $expression);
                     }
                 }
             } else {
                 foreach ($className as $singleClass) {
                     $className = $compilationContext->getFullName($singleClass);
                     if ($compiler->isClass($singleClass)) {
                         $classDefinition = $compiler->getClassDefinition($singleClass);
                     } else {
                         throw new CompilerException("Class name: " . $className . " does not exist", $expression);
                     }
                 }
             }
         } else {
             if ($className == 'self') {
                 $classDefinition = $compilationContext->classDefinition;
             } else {
                 if ($className == 'parent') {
                     $classDefinition = $compilationContext->classDefinition;
                     $extendsClass = $classDefinition->getExtendsClass();
                     if (!$extendsClass) {
                         throw new CompilerException('Cannot call method "' . $methodName . '" on parent because class ' . $classDefinition->getCompleteName() . ' does not extend any class', $expression);
                     }
                     $currentClassDefinition = $classDefinition;
                     $classDefinition = $classDefinition->getExtendsClassDefinition();
                 }
             }
         }
     }
     /**
      * Check if the class implements the method
      */
     if (!$dynamicMethod && !$dynamicClass) {
         if (!$classDefinition->hasMethod($methodName)) {
             throw new CompilerException("Class '" . $classDefinition->getCompleteName() . "' does not implement static method: '" . $expression['name'] . "'", $expression);
         } else {
             $method = $classDefinition->getMethod($methodName);
             if ($method->isPrivate() && $method->getClassDefinition() != $compilationContext->classDefinition) {
                 throw new CompilerException("Cannot call private method '" . $methodName . "' out of its scope", $expression);
             }
             if ($className != 'parent' && $className != 'self') {
                 if (!$method->isStatic()) {
                     throw new CompilerException("Cannot call non-static method '" . $methodName . "' in a static way", $expression);
                 }
             }
             if (!$classDefinition->hasMethod("__callStatic")) {
                 if ($method instanceof ClassMethod && !$method->isInternal()) {
                     /**
                      * Try to produce an exception if method is called with a wrong number of parameters
                      */
                     if (isset($expression['parameters'])) {
                         $callNumberParameters = count($expression['parameters']);
                     } else {
                         $callNumberParameters = 0;
                     }
                     $classMethod = $classDefinition->getMethod($methodName);
                     $expectedNumberParameters = $classMethod->getNumberOfRequiredParameters();
                     if (!$expectedNumberParameters && $callNumberParameters > 0) {
                         $numberParameters = $classMethod->getNumberOfParameters();
                         if ($callNumberParameters > $numberParameters) {
                             throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression);
                         }
                     }
                     if ($callNumberParameters < $expectedNumberParameters) {
                         throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression);
                     }
                 }
             } else {
                 if (!isset($method)) {
                     $method = $classDefinition->getMethod("__callStatic");
                     if ($method->isPrivate() && $method->getClassDefinition() != $compilationContext->classDefinition) {
                         throw new CompilerException("Cannot call private magic method '__call' out of its scope", $expression);
                     }
                 }
             }
         }
     }
     /**
      * Call static methods in the same class, use the special context 'self'
      * Call static methods in the 'self' context
      */
     if (!$dynamicMethod) {
         if ($dynamicClass) {
             $this->callFromDynamicClass($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $compilationContext);
         } else {
             if ($className == 'self' || $classDefinition == $compilationContext->classDefinition) {
                 $this->callSelf($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $classDefinition, $compilationContext, isset($method) ? $method : null);
             } else {
                 if ($className == 'parent') {
                     $this->callParent($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $currentClassDefinition, $compilationContext, isset($method) ? $method : null);
                 } else {
                     $this->callFromClass($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $classDefinition, $compilationContext, isset($method) ? $method : null);
                 }
             }
         }
     } else {
         if ($dynamicClass) {
             $this->callFromDynamicClassDynamicMethod($expression, $symbolVariable, $mustInit, $isExpecting, $compilationContext);
         }
     }
     /**
      * Add the last call status to the current symbol table
      */
     $this->addCallStatusFlag($compilationContext);
     /**
      * Transfer the return type-hint to the returned variable
      */
     if ($isExpecting) {
         if (isset($method)) {
             if ($method instanceof ClassMethod) {
                 $returnClassTypes = $method->getReturnClassTypes();
                 if ($returnClassTypes !== null) {
                     $symbolVariable->setDynamicTypes('object');
                     foreach ($returnClassTypes as $classType) {
                         $symbolVariable->setClassTypes($compilationContext->getFullName($classType));
                     }
                 }
                 $returnTypes = $method->getReturnTypes();
                 if ($returnTypes !== null) {
                     foreach ($returnTypes as $dataType => $returnType) {
                         $symbolVariable->setDynamicTypes($dataType);
                     }
                 }
             }
         }
     }
     /**
      * We can mark temporary variables generated as idle here
      */
     foreach ($this->getTemporalVariables() as $tempVariable) {
         $tempVariable->setIdle(true);
     }
     if ($isExpecting) {
         return new CompiledExpression('variable', $symbolVariable->getRealName(), $expression);
     }
     return new CompiledExpression('null', null, $expression);
 }