Example #1
0
 /**
  * Make sure signatures line up between methods and the
  * methods they override
  *
  * @see https://en.wikipedia.org/wiki/Liskov_substitution_principle
  */
 private static function analyzeOverrideSignature(CodeBase $code_base, Method $method)
 {
     if (!Config::get()->analyze_signature_compatibility) {
         return;
     }
     // Hydrate the class this method is coming from in
     // order to understand if its an override or not
     $class = $method->getClass($code_base);
     $class->hydrate($code_base);
     // Check to see if the method is an override
     // $method->analyzeOverride($code_base);
     // Make sure we're actually overriding something
     if (!$method->getIsOverride()) {
         return;
     }
     // Dont' worry about signatures lining up on
     // constructors. We just want to make sure that
     // calling a method on a subclass won't cause
     // a runtime error. We usually know what we're
     // constructing at instantiation time, so there
     // is less of a risk.
     if ($method->getName() == '__construct') {
         return;
     }
     // Get the method that is being overridden
     $o_method = $method->getOverriddenMethod($code_base);
     // Get the class that the overridden method lives on
     $o_class = $o_method->getClass($code_base);
     // PHP doesn't complain about signature mismatches
     // with traits, so neither shall we
     if ($o_class->isTrait()) {
         return;
     }
     // Get the parameters for that method
     $o_parameter_list = $o_method->getParameterList();
     // If we have a parent type defined, map the method's
     // return type and parameter types through it
     $type_option = $class->getParentTypeOption();
     // Map overridden method parameter types through any
     // template type parameters we may have
     if ($type_option->isDefined()) {
         $o_parameter_list = array_map(function (Parameter $parameter) use($type_option, $code_base) : Parameter {
             if (!$parameter->getUnionType()->hasTemplateType()) {
                 return $parameter;
             }
             $mapped_parameter = clone $parameter;
             $mapped_parameter->setUnionType($mapped_parameter->getUnionType()->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base)));
             return $mapped_parameter;
         }, $o_parameter_list);
     }
     // Map overridden method return type through any template
     // type parameters we may have
     $o_return_union_type = $o_method->getUnionType();
     if ($type_option->isDefined() && $o_return_union_type->hasTemplateType()) {
         $o_return_union_type = $o_return_union_type->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base));
     }
     // Determine if the signatures match up
     $signatures_match = true;
     // Make sure the count of parameters matches
     if ($method->getNumberOfRequiredParameters() > $o_method->getNumberOfRequiredParameters()) {
         $signatures_match = false;
     } else {
         if ($method->getNumberOfParameters() < $o_method->getNumberOfParameters()) {
             $signatures_match = false;
             // If parameter counts match, check their types
         } else {
             foreach ($method->getParameterList() as $i => $parameter) {
                 if (!isset($o_parameter_list[$i])) {
                     continue;
                 }
                 $o_parameter = $o_parameter_list[$i];
                 // Changing pass by reference is not ok
                 // @see https://3v4l.org/Utuo8
                 if ($parameter->isPassByReference() != $o_parameter->isPassByReference()) {
                     $signatures_match = false;
                     break;
                 }
                 // A stricter type on an overriding method is cool
                 if ($o_parameter->getUnionType()->isEmpty() || $o_parameter->getUnionType()->isType(MixedType::instance())) {
                     continue;
                 }
                 // Its not OK to have a more relaxed type on an
                 // overriding method
                 //
                 // https://3v4l.org/XTm3P
                 if ($parameter->getUnionType()->isEmpty()) {
                     $signatures_match = false;
                     break;
                 }
                 // If we have types, make sure they line up
                 //
                 // TODO: should we be expanding the types on $o_parameter
                 //       via ->asExpandedTypes($code_base)?
                 //
                 //       @see https://3v4l.org/ke3kp
                 if (!$o_parameter->getUnionType()->canCastToUnionType($parameter->getUnionType())) {
                     $signatures_match = false;
                     break;
                 }
             }
         }
     }
     // Return types should be mappable
     if (!$o_return_union_type->isEmpty()) {
         if (!$method->getUnionType()->asExpandedTypes($code_base)->canCastToUnionType($o_return_union_type)) {
             $signatures_match = false;
         }
     }
     // Static or non-static should match
     if ($method->isStatic() != $o_method->isStatic()) {
         if ($o_method->isStatic()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessStaticToNonStatic, $method->getFileRef()->getLineNumberStart(), $o_method->getFQSEN());
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessNonStaticToStatic, $method->getFileRef()->getLineNumberStart(), $o_method->getFQSEN());
         }
     }
     if ($o_method->returnsRef() && !$method->returnsRef()) {
         $signatures_match = false;
     }
     if (!$signatures_match) {
         if ($o_method->isInternal()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::ParamSignatureMismatchInternal, $method->getFileRef()->getLineNumberStart(), $method, $o_method);
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::ParamSignatureMismatch, $method->getFileRef()->getLineNumberStart(), $method, $o_method, $o_method->getFileRef()->getFile(), $o_method->getFileRef()->getLineNumberStart());
         }
     }
     // Access must be compatible
     if ($o_method->isProtected() && $method->isPrivate() || $o_method->isPublic() && !$method->isPublic()) {
         if ($o_method->isInternal()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessSignatureMismatchInternal, $method->getFileRef()->getLineNumberStart(), $method, $o_method);
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessSignatureMismatch, $method->getFileRef()->getLineNumberStart(), $method, $o_method, $o_method->getFileRef()->getFile(), $o_method->getFileRef()->getLineNumberStart());
         }
     }
 }
Example #2
0
 /**
  * @param string $string
  * A string representing a type
  *
  * @param Context $context
  * The context in which the type string was
  * found
  *
  * @return Type
  * Parse a type from the given string
  */
 public static function fromStringInContext(string $string, Context $context) : Type
 {
     assert($string !== '', "Type cannot be empty in {$context}");
     $namespace = null;
     // Extract the namespace if the type string is
     // fully-qualified
     if ('\\' === $string[0]) {
         list($namespace, $string) = self::namespaceAndTypeFromString($string);
     }
     $type_name = $string;
     // @var bool
     // True if this type name if of the form 'C[]'
     $is_generic_array_type = self::isGenericArrayString($type_name);
     // If this is a generic array type, get the name of
     // the type of each element
     $non_generic_array_type_name = $type_name;
     if ($is_generic_array_type && false !== ($pos = strpos($type_name, '[]'))) {
         $non_generic_array_type_name = substr($type_name, 0, $pos);
     }
     // Check to see if the type name is mapped via
     // a using clause.
     //
     // Gotta check this before checking for native types
     // because there are monsters out there that will
     // remap the names via things like `use \Foo\String`.
     if ($context->hasNamespaceMapFor(T_CLASS, $non_generic_array_type_name)) {
         $fqsen = $context->getNamespaceMapFor(T_CLASS, $non_generic_array_type_name);
         if ($is_generic_array_type) {
             return GenericArrayType::fromElementType(Type::make($fqsen->getNamespace(), $fqsen->getName()));
         }
         return Type::make($fqsen->getNamespace(), $fqsen->getName());
     }
     // If this was a fully qualified type, we're all
     // set
     if (!empty($namespace)) {
         return self::fromNamespaceAndName($namespace, $type_name);
     }
     if ($is_generic_array_type && self::isNativeTypeString($type_name)) {
         return self::fromInternalTypeName($type_name);
     } else {
         // Check to see if its a builtin type
         switch (self::canonicalNameFromName($type_name)) {
             case 'array':
                 return \Phan\Language\Type\ArrayType::instance();
             case 'bool':
                 return \Phan\Language\Type\BoolType::instance();
             case 'callable':
                 return \Phan\Language\Type\CallableType::instance();
             case 'float':
                 return \Phan\Language\Type\FloatType::instance();
             case 'int':
                 return \Phan\Language\Type\IntType::instance();
             case 'mixed':
                 return \Phan\Language\Type\MixedType::instance();
             case 'null':
                 return \Phan\Language\Type\NullType::instance();
             case 'object':
                 return \Phan\Language\Type\ObjectType::instance();
             case 'resource':
                 return \Phan\Language\Type\ResourceType::instance();
             case 'string':
                 return \Phan\Language\Type\StringType::instance();
             case 'void':
                 return \Phan\Language\Type\VoidType::instance();
         }
     }
     // Things like `self[]` or `$this[]`
     if ($is_generic_array_type && self::isSelfTypeString($non_generic_array_type_name) && $context->isInClassScope()) {
         // Callers of this method should be checking on their own
         // to see if this type is a reference to 'parent' and
         // dealing with it there. We don't want to have this
         // method be dependent on the code base
         assert('parent' !== $non_generic_array_type_name, __METHOD__ . " does not know how to handle the type name 'parent' in {$context}");
         return GenericArrayType::fromElementType(static::fromFullyQualifiedString((string) $context->getClassFQSEN()));
     }
     // If this is a type referencing the current class
     // in scope such as 'self' or 'static', return that.
     if (self::isSelfTypeString($type_name) && $context->isInClassScope()) {
         // Callers of this method should be checking on their own
         // to see if this type is a reference to 'parent' and
         // dealing with it there. We don't want to have this
         // method be dependent on the code base
         assert('parent' !== $type_name, __METHOD__ . " does not know how to handle the type name 'parent' in {$context}");
         return static::fromFullyQualifiedString((string) $context->getClassFQSEN());
     }
     // Attach the context's namespace to the type name
     return self::fromNamespaceAndName($context->getNamespace() ?: '\\', $type_name);
 }
Example #3
0
 /**
  * @param Node|string $method_name
  * Either then name of the method or a node that
  * produces the name of the method.
  *
  * @param bool $is_static
  * Set to true if this is a static method call
  *
  * @return Method
  * A method with the given name on the class referenced
  * from the given node
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws CodeBaseExtension
  * An exception is thrown if we can't find the given
  * method
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  *
  * @throws IssueException
  */
 public function getMethod($method_name, bool $is_static) : Method
 {
     if ($method_name instanceof Node) {
         // The method_name turned out to be a variable.
         // There isn't much we can do to figure out what
         // it's referring to.
         throw new NodeException($method_name, "Unexpected method node");
     }
     assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList();
     } catch (CodeBaseException $exception) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredClassMethod)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $exception->getFQSEN()]));
     }
     // If there were no classes on the left-type, figure
     // out what we were trying to call the method on
     // and send out an error.
     if (empty($class_list)) {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']);
         if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasAnyType([MixedType::instance(), ObjectType::instance(), StringType::instance()]) && !(Config::get()->null_casts_as_any_type && $union_type->hasType(NullType::instance()))) {
             throw new IssueException(Issue::fromType(Issue::NonClassMethodCall)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $union_type]));
         }
         throw new NodeException($this->node, "Can't figure out method call for {$method_name}");
     }
     // Hunt to see if any of them have the method we're
     // looking for
     foreach ($class_list as $i => $class) {
         if ($class->hasMethodWithName($this->code_base, $method_name)) {
             return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context);
         } else {
             if ($class->hasMethodWithName($this->code_base, '__call')) {
                 return $class->getMethodByNameInContext($this->code_base, '__call', $this->context);
             }
         }
     }
     // Figure out an FQSEN for the method we couldn't find
     $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name);
     if ($is_static) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredStaticMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen]));
     }
     throw new IssueException(Issue::fromType(Issue::UndeclaredMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen]));
 }
Example #4
0
 /**
  * Visit a node with kind `\ast\AST_VAR`
  *
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return UnionType
  * The set of types that are possibly produced by the
  * given node
  */
 public function visitVar(Node $node) : UnionType
 {
     // $$var or ${...} (whose idea was that anyway?)
     if ($node->children['name'] instanceof Node && ($node->children['name']->kind == \ast\AST_VAR || $node->children['name']->kind == \ast\AST_BINARY_OP)) {
         return MixedType::instance()->asUnionType();
     }
     // This is nonsense. Give up.
     if ($node->children['name'] instanceof Node) {
         return new UnionType();
     }
     $variable_name = $node->children['name'];
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         if (!Variable::isSuperglobalVariableWithName($variable_name)) {
             Log::err(Log::EVAR, "Variable \${$variable_name} is not defined", $this->context->getFile(), $node->lineno ?? 0);
         }
     } else {
         $variable = $this->context->getScope()->getVariableWithName($variable_name);
         return $variable->getUnionType();
     }
     return new UnionType();
 }
Example #5
0
 /**
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return string
  * The class name represented by the given call
  */
 public function visitVar(Node $node) : string
 {
     // $$var->method()
     if ($node->children['name'] instanceof Node) {
         return '';
     }
     // $this->method()
     if ($node->children['name'] == 'this') {
         if (!$this->context->isInClassScope()) {
             Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
             return '';
         }
         return (string) $this->context->getClassFQSEN();
     }
     $variable_name = $node->children['name'];
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         // Got lost, couldn't find the variable in the current scope
         // If it really isn't defined, it will be caught by the
         // undefined var error
         return '';
     }
     $variable = $this->context->getScope()->getVariableWithName($variable_name);
     $union_type = $variable->getUnionType()->nonNativeTypes()->nonGenericArrayTypes();
     // If there are no candidate classes, we'll emit whatever
     // we have so that we can differentiate between
     // no-known-type and a shitty type
     if ($union_type->isEmpty()) {
         if (!$variable->getUnionType()->isEmpty() && !$variable->getUnionType()->hasType(MixedType::instance()) && !$variable->getUnionType()->hasType(ArrayType::instance()) && !$variable->getUnionType()->hasType(ObjectType::instance())) {
             $type = (string) $variable->getUnionType();
             throw new TypeException("Calling method on non-class type {$type}");
         }
         // No viable class types for the variable.
         return '';
     }
     $class_fqsen = $this->chooseSingleFQSEN(array_map(function (Type $type) {
         return $type->asFQSEN();
     }, $union_type->getTypeList()));
     if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
         return (string) $class_fqsen;
     }
     // We couldn't find any viable classes
     return '';
 }
Example #6
0
File: Type.php Project: etsy/phan
 /**
  * @param string $string
  * A string representing a type
  *
  * @param Context $context
  * The context in which the type string was
  * found
  *
  * @return Type
  * Parse a type from the given string
  */
 public static function fromStringInContext(string $string, Context $context) : Type
 {
     assert($string !== '', "Type cannot be empty");
     // Extract the namespace, type and parameter type name list
     $tuple = self::typeStringComponents($string);
     $namespace = $tuple->_0;
     $type_name = $tuple->_1;
     $template_parameter_type_name_list = $tuple->_2;
     // Map the names of the types to actual types in the
     // template parameter type list
     $template_parameter_type_list = array_map(function (string $type_name) use($context) {
         return Type::fromStringInContext($type_name, $context)->asUnionType();
     }, $template_parameter_type_name_list);
     // @var bool
     // True if this type name if of the form 'C[]'
     $is_generic_array_type = self::isGenericArrayString($type_name);
     // If this is a generic array type, get the name of
     // the type of each element
     $non_generic_array_type_name = $type_name;
     if ($is_generic_array_type && false !== ($pos = strrpos($type_name, '[]'))) {
         $non_generic_array_type_name = substr($type_name, 0, $pos);
     }
     // Check to see if the type name is mapped via
     // a using clause.
     //
     // Gotta check this before checking for native types
     // because there are monsters out there that will
     // remap the names via things like `use \Foo\String`.
     $non_generic_partially_qualified_array_type_name = $non_generic_array_type_name;
     if ($namespace) {
         $non_generic_partially_qualified_array_type_name = $namespace . '\\' . $non_generic_partially_qualified_array_type_name;
     }
     if ($context->hasNamespaceMapFor(\ast\flags\USE_NORMAL, $non_generic_partially_qualified_array_type_name)) {
         $fqsen = $context->getNamespaceMapFor(\ast\flags\USE_NORMAL, $non_generic_partially_qualified_array_type_name);
         if ($is_generic_array_type) {
             return GenericArrayType::fromElementType(Type::make($fqsen->getNamespace(), $fqsen->getName(), $template_parameter_type_list));
         }
         return Type::make($fqsen->getNamespace(), $fqsen->getName(), $template_parameter_type_list);
     }
     // If this was a fully qualified type, we're all
     // set
     if (!empty($namespace) && 0 === strpos($namespace, '\\')) {
         return self::make($namespace, $type_name, $template_parameter_type_list);
     }
     if ($is_generic_array_type && self::isNativeTypeString($type_name)) {
         return self::fromInternalTypeName($type_name);
     } else {
         // Check to see if its a builtin type
         switch (strtolower(self::canonicalNameFromName($type_name))) {
             case 'array':
                 return \Phan\Language\Type\ArrayType::instance();
             case 'bool':
                 return \Phan\Language\Type\BoolType::instance();
             case 'callable':
                 return \Phan\Language\Type\CallableType::instance();
             case 'float':
                 return \Phan\Language\Type\FloatType::instance();
             case 'int':
                 return \Phan\Language\Type\IntType::instance();
             case 'mixed':
                 return \Phan\Language\Type\MixedType::instance();
             case 'null':
                 return \Phan\Language\Type\NullType::instance();
             case 'object':
                 return \Phan\Language\Type\ObjectType::instance();
             case 'resource':
                 return \Phan\Language\Type\ResourceType::instance();
             case 'string':
                 return \Phan\Language\Type\StringType::instance();
             case 'void':
                 return \Phan\Language\Type\VoidType::instance();
             case 'static':
                 return \Phan\Language\Type\StaticType::instance();
         }
     }
     // Things like `self[]` or `$this[]`
     if ($is_generic_array_type && self::isSelfTypeString($non_generic_array_type_name) && $context->isInClassScope()) {
         // Callers of this method should be checking on their own
         // to see if this type is a reference to 'parent' and
         // dealing with it there. We don't want to have this
         // method be dependent on the code base
         assert('parent' !== $non_generic_array_type_name, __METHOD__ . " does not know how to handle the type name 'parent'");
         return GenericArrayType::fromElementType(static::fromFullyQualifiedString((string) $context->getClassFQSEN()));
     }
     // If this is a type referencing the current class
     // in scope such as 'self' or 'static', return that.
     if (self::isSelfTypeString($type_name) && $context->isInClassScope()) {
         // Callers of this method should be checking on their own
         // to see if this type is a reference to 'parent' and
         // dealing with it there. We don't want to have this
         // method be dependent on the code base
         assert('parent' !== $type_name, __METHOD__ . " does not know how to handle the type name 'parent'");
         return static::fromFullyQualifiedString((string) $context->getClassFQSEN());
     }
     // Merge the current namespace with the given relative
     // namespace
     if (!empty($context->getNamespace()) && !empty($namespace)) {
         $namespace = $context->getNamespace() . '\\' . $namespace;
     } else {
         if (!empty($context->getNamespace())) {
             $namespace = $context->getNamespace();
         } else {
             $namespace = '\\' . $namespace;
         }
     }
     // Attach the context's namespace to the type name
     return self::make($namespace, $type_name, $template_parameter_type_list);
 }
Example #7
0
 /**
  * @param string $string
  * A string representing a type
  *
  * @param Context $context
  * The context in which the type string was
  * found
  *
  * @return Type
  * Parse a type from the given string
  */
 public static function fromStringInContext(string $string, Context $context) : Type
 {
     assert($string !== '', "Type cannot be empty in {$context}");
     $namespace = null;
     // Extract the namespace if the type string is
     // fully-qualified
     if ('\\' === $string[0]) {
         list($namespace, $string) = self::namespaceAndTypeFromString($string);
     }
     $type_name = strtolower($string);
     // Check to see if the type name is mapped via
     // a using clause.
     //
     // Gotta check this before checking for native types
     // because there are monsters out there that will
     // remap the names via things like `use \Foo\String`.
     if ($context->hasNamespaceMapFor(T_CLASS, $type_name)) {
         $fqsen = $context->getNamespaceMapFor(T_CLASS, $type_name);
         return new Type($fqsen->getNamespace(), $fqsen->getName());
     }
     // If this was a fully qualified type, we're all
     // set
     if (!empty($namespace)) {
         return self::fromNamespaceAndName($namespace, $type_name);
     }
     // Check to see if its a builtin type
     switch (self::canonicalNameFromName($type_name)) {
         case 'array':
             return \Phan\Language\Type\ArrayType::instance();
         case 'bool':
             return \Phan\Language\Type\BoolType::instance();
         case 'callable':
             return \Phan\Language\Type\CallableType::instance();
         case 'float':
             return \Phan\Language\Type\FloatType::instance();
         case 'int':
             return \Phan\Language\Type\IntType::instance();
         case 'mixed':
             return \Phan\Language\Type\MixedType::instance();
         case 'null':
             return \Phan\Language\Type\NullType::instance();
         case 'object':
             return \Phan\Language\Type\ObjectType::instance();
         case 'resource':
             return \Phan\Language\Type\ResourceType::instance();
         case 'string':
             return \Phan\Language\Type\StringType::instance();
         case 'void':
             return \Phan\Language\Type\VoidType::instance();
     }
     // If this is a type referencing the current class
     // in scope such as 'self' or 'static', return that.
     if (self::isSelfTypeString($type_name) && $context->isInClassScope()) {
         return static::fromFullyQualifiedString((string) $context->getClassFQSEN());
     }
     // Attach the context's namespace to the type name
     return self::fromNamespaceAndName($context->getNamespace() ?: '\\', $type_name);
 }
Example #8
0
 /**
  * Takes "a|b[]|c|d[]|e" and returns "b|d"
  *
  * @return UnionType
  * The subset of types in this
  */
 public function genericArrayElementTypes() : UnionType
 {
     // If array is in there, then it can be any type
     // Same for mixed
     if ($this->hasType(ArrayType::instance()) || $this->hasType(MixedType::instance())) {
         return MixedType::instance()->asUnionType();
     }
     if ($this->hasType(ArrayType::instance())) {
         return NullType::instance()->asUnionType();
     }
     return new UnionType(array_filter(array_map(function (Type $type) {
         if (!$type->isGenericArray()) {
             return null;
         }
         return $type->genericArrayElementType();
     }, $this->getTypeList())));
 }
Example #9
0
 /**
  * Visit a node with kind `\ast\AST_VAR`
  *
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return UnionType
  * The set of types that are possibly produced by the
  * given node
  */
 public function visitVar(Node $node) : UnionType
 {
     // $$var or ${...} (whose idea was that anyway?)
     if ($node->children['name'] instanceof Node && ($node->children['name']->kind == \ast\AST_VAR || $node->children['name']->kind == \ast\AST_BINARY_OP)) {
         return MixedType::instance()->asUnionType();
     }
     // This is nonsense. Give up.
     if ($node->children['name'] instanceof Node) {
         return new UnionType();
     }
     $variable_name = $node->children['name'];
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         if (!Variable::isSuperglobalVariableWithName($variable_name) && (!Config::get()->ignore_undeclared_variables_in_global_scope || !$this->context->isInGlobalScope())) {
             throw new IssueException(Issue::fromType(Issue::UndeclaredVariable)($this->context->getFile(), $node->lineno ?? 0, [$variable_name]));
         }
     } else {
         $variable = $this->context->getScope()->getVariableByName($variable_name);
         return $variable->getUnionType();
     }
     return new UnionType();
 }
Example #10
0
 /**
  * Takes "a|b[]|c|d[]|e" and returns "b|d"
  *
  * @return UnionType
  * The subset of types in this
  */
 public function genericArrayElementTypes() : UnionType
 {
     // If array is in there, then it can be any type
     // Same for mixed
     if ($this->hasType(ArrayType::instance()) || $this->hasType(MixedType::instance())) {
         return MixedType::instance()->asUnionType();
     }
     if ($this->hasType(ArrayType::instance())) {
         return NullType::instance()->asUnionType();
     }
     return new UnionType($this->type_set->filter(function (Type $type) : bool {
         return $type->isGenericArray();
     })->map(function (Type $type) : Type {
         return $type->genericArrayElementType();
     }));
 }
Example #11
0
 /**
  * @param Node|string $method_name
  * Either then name of the method or a node that
  * produces the name of the method.
  *
  * @param bool $is_static
  * Set to true if this is a static method call
  *
  * @return Method
  * A method with the given name on the class referenced
  * from the given node
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws CodeBaseExtension
  * An exception is thrown if we can't find the given
  * method
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public function getMethod($method_name, bool $is_static) : Method
 {
     if ($method_name instanceof Node) {
         // The method_name turned out to be a variable.
         // There isn't much we can do to figure out what
         // it's referring to.
         throw new NodeException($method_name, "Unexpected method node");
     }
     assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList();
     } catch (CodeBaseException $exception) {
         // We can give a more explicit message
         throw new CodeBaseException($exception->getFQSEN(), "Can't access method {$method_name} from undeclared class {$exception->getFQSEN()}");
     }
     // If there were no classes on the left-type, figure
     // out what we were trying to call the method on
     // and send out an error.
     if (empty($class_list)) {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']);
         if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasType(MixedType::instance())) {
             throw new TypeException("Calling method on non-class type {$union_type}");
         }
         throw new NodeException($this->node, "Can't figure out method call for {$method_name}");
     }
     // Hunt to see if any of them have the method we're
     // looking for
     foreach ($class_list as $i => $class) {
         if ($class->hasMethodWithName($this->code_base, $method_name)) {
             return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context);
         }
     }
     // Figure out an FQSEN for the method we couldn't find
     $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name);
     if ($is_static) {
         throw new CodeBaseException($method_fqsen, "static call to undeclared method {$method_fqsen}");
     }
     throw new CodeBaseException($method_fqsen, "call to undeclared method {$method_fqsen}");
 }