public static fromNamespaceAndName ( string $namespace, string $type_name ) : |
||
$namespace | string | A fully qualified namespace |
$type_name | string | The name of the type |
리턴 | A type representing the given namespace and type name. |
/** * Visit a node with kind `\ast\AST_DIM` * * @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 visitDim(Node $node) : UnionType { $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node->children['expr']); if ($union_type->isEmpty()) { return $union_type; } // Figure out what the types of accessed array // elements would be $generic_types = $union_type->genericArrayElementTypes(); // If we have generics, we're all set if (!$generic_types->isEmpty()) { return $generic_types; } // If the only type is null, we don't know what // accessed items will be if ($union_type->isType(NullType::instance())) { return new UnionType(); } $element_types = new UnionType(); // You can access string characters via array index, // so we'll add the string type to the result if we're // indexing something that could be a string if ($union_type->isType(StringType::instance()) || $union_type->canCastToUnionType(StringType::instance()->asUnionType())) { $element_types->addType(StringType::instance()); } // array offsets work on strings, unfortunately // Double check that any classes in the type don't // have ArrayAccess $array_access_type = Type::fromNamespaceAndName('\\', 'ArrayAccess'); // Hunt for any types that are viable class names and // see if they inherit from ArrayAccess foreach ($union_type->getTypeList() as $type) { if ($type->isNativeType()) { continue; } $class_fqsen = FullyQualifiedClassName::fromType($type); // If we can't find the class, the type probably // wasn't a class. if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) { continue; } $class = $this->code_base->getClassByFQSEN($class_fqsen); // If the class has type ArrayAccess, it can be indexed // as if it were an array. That being said, we still don't // know the types of the elements, but at least we don't // error out. if ($class->getUnionType()->hasType($array_access_type)) { return $element_types; } } if ($element_types->isEmpty()) { Log::err(Log::ETYPE, "Suspicious array access to {$union_type}", $this->context->getFile(), $node->lineno); } return $element_types; }
/** * 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); } } }
/** * Visit a node with kind `\ast\AST_DIM` * * @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 visitDim(Node $node) : UnionType { $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node->children['expr']); if ($union_type->isEmpty()) { return $union_type; } // Figure out what the types of accessed array // elements would be $generic_types = $union_type->genericArrayElementTypes(); // If we have generics, we're all set if (!$generic_types->isEmpty()) { return $generic_types; } // If the only type is null, we don't know what // accessed items will be if ($union_type->isType(NullType::instance())) { return new UnionType(); } $element_types = new UnionType(); // You can access string characters via array index, // so we'll add the string type to the result if we're // indexing something that could be a string if ($union_type->isType(StringType::instance()) || $union_type->canCastToUnionType(StringType::instance()->asUnionType())) { $element_types->addType(StringType::instance()); } // array offsets work on strings, unfortunately // Double check that any classes in the type don't // have ArrayAccess $array_access_type = Type::fromNamespaceAndName('\\', 'ArrayAccess'); // Hunt for any types that are viable class names and // see if they inherit from ArrayAccess try { foreach ($union_type->asClassList($this->code_base) as $class) { if ($class->getUnionType()->hasType($array_access_type)) { return $element_types; } } } catch (CodeBaseException $exception) { // Swallow it } if ($element_types->isEmpty()) { $this->emitIssue(Issue::TypeArraySuspicious, $node->lineno ?? 0, (string) $union_type); } return $element_types; }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitReturn(Node $node) : Context { // Don't check return types in traits if ($this->context->isInClassScope()) { $clazz = $this->context->getClassInScope($this->code_base); if ($clazz->isTrait()) { return $this->context; } } // Make sure we're actually returning from a method. if (!$this->context->isInFunctionLikeScope()) { return $this->context; } // Get the method/function/closure we're in $method = $this->context->getFunctionLikeInScope($this->code_base); assert(!empty($method), "We're supposed to be in either method or closure scope."); // Figure out what we intend to return $method_return_type = $method->getUnionType(); // Figure out what is actually being returned $expression_type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']); if ($expression_type->hasStaticType()) { $expression_type = $expression_type->withStaticResolvedInContext($this->context); } // If there is no declared type, see if we can deduce // what it should be based on the return type if ($method_return_type->isEmpty() || $method->isReturnTypeUndefined()) { $method->setIsReturnTypeUndefined(true); // Set the inferred type of the method based // on what we're returning $method->getUnionType()->addUnionType($expression_type); // No point in comparing this type to the // type we just set return $this->context; } if (!$method->isReturnTypeUndefined() && !$expression_type->canCastToExpandedUnionType($method_return_type, $this->code_base) && !$method->getUnionType()->canCastToExpandedUnionType(Type::fromNamespaceAndName('\\', 'Generator')->asUnionType(), $this->code_base)) { $this->emitIssue(Issue::TypeMismatchReturn, $node->lineno ?? 0, (string) $expression_type, $method->getName(), (string) $method_return_type); } if ($method->isReturnTypeUndefined()) { // Add the new type to the set of values returned by the // method $method->getUnionType()->addUnionType($expression_type); } // Mark the method as returning something $method->setHasReturn(($node->children['expr'] ?? null) !== null); return $this->context; }