Inheritance: extends Phan\Language\Element\AddressableElementInterface
示例#1
0
 /**
  * @param FunctionInterface $function
  * Get a list of methods hydrated with type information
  * for the given partial method
  *
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @return Method[]
  * A list of typed methods based on the given method
  */
 private static function functionListFromFunction(FunctionInterface $function, CodeBase $code_base) : array
 {
     // See if we have any type information for this
     // internal function
     $map_list = UnionType::internalFunctionSignatureMapForFQSEN($function->getFQSEN());
     if (!$map_list) {
         return [$function];
     }
     $alternate_id = 0;
     return array_map(function ($map) use($function, &$alternate_id) : FunctionInterface {
         $alternate_function = clone $function;
         $alternate_function->setFQSEN($alternate_function->getFQSEN()->withAlternateId($alternate_id++));
         // Set the return type if one is defined
         if (!empty($map['return_type'])) {
             $alternate_function->setUnionType($map['return_type']);
         }
         // Load properties if defined
         foreach ($map['property_name_type_map'] ?? [] as $parameter_name => $parameter_type) {
             $flags = 0;
             $is_optional = false;
             // Check to see if its a pass-by-reference parameter
             if (strpos($parameter_name, '&') === 0) {
                 $flags |= \ast\flags\PARAM_REF;
                 $parameter_name = substr($parameter_name, 1);
             }
             // Check to see if its variadic
             if (strpos($parameter_name, '...') !== false) {
                 $flags |= \ast\flags\PARAM_VARIADIC;
                 $parameter_name = str_replace('...', '', $parameter_name);
             }
             // Check to see if its an optional parameter
             if (strpos($parameter_name, '=') !== false) {
                 $is_optional = true;
                 $parameter_name = str_replace('=', '', $parameter_name);
             }
             $parameter = new Parameter($function->getContext(), $parameter_name, $parameter_type, $flags);
             if ($is_optional) {
                 $parameter->setDefaultValueType(NullType::instance()->asUnionType());
             }
             // Add the parameter
             $alternate_function->appendParameter($parameter);
         }
         $alternate_function->setNumberOfRequiredParameters(array_reduce($alternate_function->getParameterList(), function (int $carry, Parameter $parameter) : int {
             return $carry + ($parameter->isOptional() ? 0 : 1);
         }, 0));
         $alternate_function->setNumberOfOptionalParameters(count($alternate_function->getParameterList()) - $alternate_function->getNumberOfRequiredParameters());
         if ($alternate_function instanceof Method) {
             if ($alternate_function->getIsMagicCall() || $alternate_function->getIsMagicCallStatic()) {
                 $alternate_function->setNumberOfOptionalParameters(999);
                 $alternate_function->setNumberOfRequiredParameters(0);
             }
         }
         return $alternate_function;
     }, $map_list);
 }
示例#2
0
 /**
  * Check method parameters to make sure they're valid
  *
  * @return null
  */
 public static function analyzeParameterTypes(CodeBase $code_base, FunctionInterface $method)
 {
     // Look at each parameter to make sure their types
     // are valid
     foreach ($method->getParameterList() as $parameter) {
         $union_type = $parameter->getUnionType();
         // Look at each type in the parameter's Union Type
         foreach ($union_type->getTypeSet() as $type) {
             // If its a native type or a reference to
             // self, its OK
             if ($type->isNativeType() || $type->isSelfType()) {
                 continue;
             }
             if ($type instanceof TemplateType) {
                 if ($method instanceof Method) {
                     if ($method->isStatic()) {
                         Issue::maybeEmit($code_base, $method->getContext(), Issue::TemplateTypeStaticMethod, $method->getFileRef()->getLineNumberStart(), (string) $method->getFQSEN());
                     }
                 }
             } else {
                 // Make sure the class exists
                 $type_fqsen = $type->asFQSEN();
                 if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
                     Issue::maybeEmit($code_base, $method->getContext(), Issue::UndeclaredTypeParameter, $method->getFileRef()->getLineNumberStart(), (string) $type_fqsen);
                 }
             }
         }
     }
     if ($method instanceof Method) {
         self::analyzeOverrideSignature($code_base, $method);
     }
 }
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeParameterTypes(CodeBase $code_base, FunctionInterface $method)
 {
     // Look at each method parameter
     foreach ($method->getParameterList() as $parameter) {
         $union_type = $parameter->getUnionType();
         // Look at each type in the parameter's Union Type
         foreach ($union_type->getTypeSet() as $type) {
             // If its a native type or a reference to
             // self, its OK
             if ($type->isNativeType() || $type->isSelfType()) {
                 continue;
             }
             // Otherwise, make sure the class exists
             $type_fqsen = $type->asFQSEN();
             if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
                 Issue::emit(Issue::UndeclaredTypeParameter, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), (string) $type_fqsen);
             }
         }
     }
 }
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateFunction(CodeBase $code_base, FunctionInterface $method)
 {
     $fqsen = $method->getFQSEN();
     if (!$fqsen->isAlternate()) {
         return;
     }
     $original_fqsen = $fqsen->getCanonicalFQSEN();
     if (!$code_base->hasMethod($original_fqsen)) {
         return;
     }
     $original_method = $code_base->getMethod($original_fqsen);
     $method_name = $method->getName();
     if ($original_method->isInternal()) {
         Issue::emit(Issue::RedefineFunctionInternal, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart());
     } else {
         Issue::emit(Issue::RedefineFunction, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $original_method->getFileRef()->getFile(), $original_method->getFileRef()->getLineNumberStart());
     }
 }
示例#5
0
 private function addMethodWithScopeAndName(FunctionInterface $method, string $scope, string $name)
 {
     $this->method_map[$scope][$name] = $method;
     // If we're doing dead code detection, map the name
     // directly to the method so we can quickly look up
     // all methods with that name to add a possible
     // reference
     if (Config::get()->dead_code_detection) {
         $this->method_name_map[strtolower($name)][] = $method;
     }
     // Associate the element with the file it was found in
     $this->getFileByPath($method->getFileRef()->getFile())->addMethodFQSEN($method->getFQSEN());
 }
示例#6
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateFunction(CodeBase $code_base, FunctionInterface $method)
 {
     $fqsen = $method->getFQSEN();
     if (!$fqsen->isAlternate()) {
         return;
     }
     $original_fqsen = $fqsen->getCanonicalFQSEN();
     if ($original_fqsen instanceof FullyQualifiedFunctionName) {
         if (!$code_base->hasFunctionWithFQSEN($original_fqsen)) {
             return;
         }
         $original_method = $code_base->getFunctionByFQSEN($original_fqsen);
     } else {
         if (!$code_base->hasMethodWithFQSEN($original_fqsen)) {
             return;
         }
         $original_method = $code_base->getMethodByFQSEN($original_fqsen);
     }
     $method_name = $method->getName();
     if (!$method->hasSuppressIssue(Issue::RedefineFunction)) {
         if ($original_method->isInternal()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::RedefineFunctionInternal, $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart());
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::RedefineFunction, $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $original_method->getFileRef()->getFile(), $original_method->getFileRef()->getLineNumberStart());
         }
     }
 }
示例#7
0
 /**
  * The return type of the given FunctionInterface to a Generator.
  * Emit an Issue if the documented return type is incompatible with that.
  * @return void
  */
 private function setReturnTypeOfGenerator(FunctionInterface $func, Node $node)
 {
     // Currently, there is no way to describe the types passed to
     // a Generator in phpdoc.
     // So, nothing bothers recording the types beyond \Generator.
     $func->setHasReturn(true);
     // Returns \Generator, technically
     $func->setHasYield(true);
     if ($func->getUnionType()->isEmpty()) {
         $func->setIsReturnTypeUndefined(true);
         $func->getUnionType()->addUnionType(Type::fromNamespaceAndName('\\', 'Generator')->asUnionType());
     }
     if (!$func->isReturnTypeUndefined()) {
         $func_return_type = $func->getUnionType();
         if (!$func_return_type->canCastToExpandedUnionType(Type::fromNamespaceAndName('\\', 'Generator')->asUnionType(), $this->code_base)) {
             // At least one of the documented return types must
             // be Generator, Iterable, or Traversable.
             // Check for the issue here instead of in visitReturn/visitYield so that
             // the check is done exactly once.
             $this->emitIssue(Issue::TypeMismatchReturn, $node->lineno ?? 0, '\\Generator', $func->getName(), (string) $func_return_type);
         }
     }
 }
示例#8
0
 /**
  * Analyze the parameters and arguments for a call
  * to the given method or function
  *
  * @param CodeBase $code_base
  * @param Method $method
  * @param Node $node
  *
  * @return null
  */
 private function analyzeCallToMethod(CodeBase $code_base, FunctionInterface $method, Node $node)
 {
     $method->addReference($this->context);
     // Create variables for any pass-by-reference
     // parameters
     $argument_list = $node->children['args'];
     foreach ($argument_list->children as $i => $argument) {
         if (!is_object($argument)) {
             continue;
         }
         $parameter = $method->getParameterForCaller($i);
         if (!$parameter) {
             continue;
         }
         // If pass-by-reference, make sure the variable exists
         // or create it if it doesn't.
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 // We don't do anything with it; just create it
                 // if it doesn't exist
                 $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
             } elseif ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                 $property_name = $argument->children['prop'];
                 if (is_string($property_name)) {
                     // We don't do anything with it; just create it
                     // if it doesn't exist
                     try {
                         $property = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop']);
                     } catch (IssueException $exception) {
                         Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
                     } catch (\Exception $exception) {
                         // If we can't figure out what kind of a call
                         // this is, don't worry about it
                     }
                 } else {
                     // This is stuff like `Class->$foo`. I'm ignoring
                     // it.
                 }
             }
         }
     }
     // Confirm the argument types are clean
     ArgumentType::analyze($method, $node, $this->context, $this->code_base);
     // Take another pass over pass-by-reference parameters
     // and assign types to passed in variables
     foreach ($argument_list->children as $i => $argument) {
         if (!is_object($argument)) {
             continue;
         }
         $parameter = $method->getParameterForCaller($i);
         if (!$parameter) {
             continue;
         }
         if (Config::get()->dead_code_detection) {
             (new ArgumentVisitor($this->code_base, $this->context))($argument);
         }
         // If the parameter is pass-by-reference and we're
         // passing a variable in, see if we should pass
         // the parameter and variable types to eachother
         $variable = null;
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
             } elseif ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                 $property_name = $argument->children['prop'];
                 if (is_string($property_name)) {
                     // We don't do anything with it; just create it
                     // if it doesn't exist
                     try {
                         $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop']);
                     } catch (IssueException $exception) {
                         Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
                     } catch (\Exception $exception) {
                         // If we can't figure out what kind of a call
                         // this is, don't worry about it
                     }
                 } else {
                     // This is stuff like `Class->$foo`. I'm ignoring
                     // it.
                 }
             }
             if ($variable) {
                 $variable->getUnionType()->addUnionType($parameter->getVariadicElementUnionType());
             }
         }
     }
     // If we're in quick mode, don't retest methods based on
     // parameter types passed in
     if (Config::get()->quick_mode) {
         return;
     }
     // We're going to hunt to see if any of the arguments
     // have a mismatch with the parameters. If so, we'll
     // re-check the method to see how the parameters impact
     // its return type
     $has_argument_parameter_mismatch = false;
     // Now that we've made sure the arguments are sufficient
     // for definitions on the method, we iterate over the
     // arguments again and add their types to the parameter
     // types so we can test the method again
     $argument_list = $node->children['args'];
     // We create a copy of the parameter list so we can switch
     // back to it after
     $original_parameter_list = $method->getParameterList();
     // Create a backup of the method's scope so that we can
     // reset it after f*****g with it below
     $original_method_scope = $method->getInternalScope();
     foreach ($argument_list->children as $i => $argument) {
         // TODO(Issue #376): Support inference on the child in **the set of vargs**, not just the first vararg
         // This is just testing the first vararg.
         // The implementer will also need to restore the original parameter list.
         $parameter = $original_parameter_list[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If the parameter has no type, pass the
         // argument's type to it
         if ($parameter->getVariadicElementUnionType()->isEmpty()) {
             $has_argument_parameter_mismatch = true;
             // If this isn't an internal function or method
             // and it has no type, add the argument's type
             // to it so we can compare it to subsequent
             // calls
             if (!$parameter->isInternal()) {
                 $argument_type = UnionType::fromNode($this->context, $this->code_base, $argument);
                 // Clone the parameter in the original
                 // parameter list so we can reset it
                 // later
                 // TODO: If there are varargs and this is beyond the end, ensure last arg is cloned.
                 $original_parameter_list[$i] = clone $original_parameter_list[$i];
                 // Then set the new type on that parameter based
                 // on the argument's type. We'll use this to
                 // retest the method with the passed in types
                 $parameter->getVariadicElementUnionType()->addUnionType($argument_type);
                 if (!is_object($argument)) {
                     continue;
                 }
                 // If we're passing by reference, get the variable
                 // we're dealing with wrapped up and shoved into
                 // the scope of the method
                 if ($parameter->isPassByReference()) {
                     if ($original_parameter_list[$i]->isVariadic()) {
                         // For now, give up and work on it later.
                         // TODO(Issue #376): It's possible to have a parameter `&...$args`. Analysing that is going to be a problem.
                         // Is it possible to create `PassByReferenceVariableCollection extends Variable` or something similar?
                     } elseif ($argument->kind == \ast\AST_VAR) {
                         // Get the variable
                         $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
                         $pass_by_reference_variable = new PassByReferenceVariable($parameter, $variable);
                         $parameter_list = $method->getParameterList();
                         $parameter_list[$i] = $pass_by_reference_variable;
                         $method->setParameterList($parameter_list);
                         // Add it to the scope of the function wrapped
                         // in a way that makes it addressable as the
                         // parameter its mimicking
                         $method->getInternalScope()->addVariable($pass_by_reference_variable);
                     } else {
                         if ($argument->kind == \ast\AST_STATIC_PROP) {
                             // Get the variable
                             $property = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop'] ?? '');
                             $pass_by_reference_variable = new PassByReferenceVariable($parameter, $property);
                             $parameter_list = $method->getParameterList();
                             $parameter_list[$i] = $pass_by_reference_variable;
                             $method->setParameterList($parameter_list);
                             // Add it to the scope of the function wrapped
                             // in a way that makes it addressable as the
                             // parameter its mimicking
                             $method->getInternalScope()->addVariable($pass_by_reference_variable);
                         }
                     }
                 } else {
                     // Overwrite the method's variable representation
                     // of the parameter with the parameter with the
                     // new type
                     $method->getInternalScope()->addVariable($parameter);
                 }
             }
         }
     }
     // Now that we know something about the parameters used
     // to call the method, we can reanalyze the method with
     // the types of the parameter, making sure we don't get
     // into an infinite loop of checking calls to the current
     // method in scope
     if ($has_argument_parameter_mismatch && !$method->isInternal() && (!$this->context->isInFunctionLikeScope() || $method->getFQSEN() !== $this->context->getFunctionLikeFQSEN())) {
         $method->analyze($method->getContext(), $code_base);
     }
     // Reset to the original parameter list after having
     // tested the parameters with the types passed in
     $method->setParameterList($original_parameter_list);
     // Reset the scope to its original version before we
     // put new parameters in it
     $method->setInternalScope($original_method_scope);
 }
示例#9
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param FunctionInterface $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @param CodeBase $code_base
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arg_check
  * Formerly `function arg_check`
  */
 private static function analyzeInternalArgumentType(FunctionInterface $method, Node $node, Context $context, CodeBase $code_base)
 {
     $arglist = $node->children['args'];
     $argcount = count($arglist->children);
     switch ($method->getName()) {
         case 'join':
         case 'implode':
             // (string glue, array pieces),
             // (array pieces, string glue) or
             // (array pieces)
             if ($argcount == 1) {
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(pieces) is %s but {$method->getFQSEN()}() takes array when passed only 1 arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'pieces', (string) $method->getFQSEN(), 'string', 'array']);
                 });
                 return;
             } elseif ($argcount == 2) {
                 $arg1_type = UnionType::fromNode($context, $code_base, $arglist->children[0]);
                 $arg2_type = UnionType::fromNode($context, $code_base, $arglist->children[1]);
                 if ((string) $arg1_type == 'array') {
                     if (!$arg1_type->canCastToUnionType(StringType::instance()->asUnionType())) {
                         Issue::maybeEmit($code_base, $context, Issue::ParamSpecial1, $context->getLineNumberStart(), 2, 'glue', (string) $arg2_type, (string) $method->getFQSEN(), 'string', 1, 'array');
                     }
                 } elseif ((string) $arg1_type == 'string') {
                     if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                         Issue::maybeEmit($code_base, $context, Issue::ParamSpecial1, $context->getLineNumberStart(), 2, 'pieces', (string) $arg2_type, (string) $method->getFQSEN(), 'array', 1, 'string');
                     }
                 }
                 return;
             }
             // Any other arg counts we will let the regular
             // checks handle
             break;
         case 'array_udiff':
         case 'array_diff_uassoc':
         case 'array_uintersect_assoc':
         case 'array_intersect_ukey':
             if ($argcount < 3) {
                 Issue::maybeEmit($code_base, $context, Issue::ParamTooFewInternal, $context->getLineNumberStart(), $argcount, (string) $method->getFQSEN(), $method->getNumberOfRequiredParameters());
                 return;
             }
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial3)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             for ($i = 0; $i < $argcount - 1; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method, $i) {
                     // "arg#".($i+1)." is %s but {$method->getFQSEN()}() takes array"
                     return Issue::fromType(Issue::ParamTypeMismatch)($context->getFile(), $context->getLineNumberStart(), [$i + 1, (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 });
             }
             return;
         case 'array_diff_uassoc':
         case 'array_uintersect_uassoc':
             if ($argcount < 4) {
                 Issue::maybeEmit($code_base, $context, Issue::ParamTooFewInternal, $context->getLineNumberStart(), $argcount, (string) $method->getFQSEN(), $method->getNumberOfRequiredParameters());
                 return;
             }
             // The last 2 arguments must be a callable and there
             // can be a variable number of arrays before it
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial3)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 2], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The second last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial4)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             for ($i = 0; $i < $argcount - 2; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method, $i) {
                     // "arg#".($i+1)." is %s but {$method->getFQSEN()}() takes array"
                     return Issue::fromType(Issue::ParamTypeMismatch)($context->getFile(), $context->getLineNumberStart(), [$i + 1, (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 });
             }
             return;
         case 'strtok':
             // (string str, string token) or (string token)
             if ($argcount == 1) {
                 // If we have just one arg it must be a string token
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, StringType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'token', (string) $node_type, (string) $method->getFQSEN(), 'string']);
                 });
             }
             // The arginfo check will handle the other case
             break;
         case 'min':
         case 'max':
             if ($argcount == 1) {
                 // If we have just one arg it must be an array
                 if (!self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(values) is %s but {$method->getFQSEN()}() takes array when passed only one arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'values', (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 })) {
                     return;
                 }
             }
             // The arginfo check will handle the other case
             break;
         default:
             break;
     }
 }
示例#10
0
 /**
  * @return array
  * Get a map from column name to row values for
  * this instance
  */
 public function toRow() : array
 {
     return ['scope_name' => $this->primaryKeyValue(), 'fqsen' => (string) $this->method->getFQSEN(), 'name' => (string) $this->method->getName(), 'type' => (string) $this->method->getUnionType(), 'flags' => $this->method->getFlags(), 'context' => base64_encode(serialize($this->method->getContext())), 'is_deprecated' => $this->method->isDeprecated(), 'number_of_required_parameters' => $this->method->getNumberOfRequiredParameters(), 'number_of_optional_parameters' => $this->method->getNumberOfOptionalParameters()];
 }