Exemplo n.º 1
0
 public function getUnionType() : UnionType
 {
     if (!empty($this->future_union_type)) {
         // null out the future_union_type before
         // we compute it to avoid unbounded
         // recursion
         $future_union_type = $this->future_union_type;
         $this->future_union_type = null;
         // Set a default value for my type in case
         // there's some unbounded recursion
         $this->setUnionType(new UnionType([IntType::instance(), FloatType::instance(), StringType::instance(), BoolType::instance()]));
         $this->setUnionType($future_union_type->get());
     }
     return parent::getUnionType();
 }
Exemplo n.º 2
0
 /**
  * Visit a node with kind `\ast\AST_CLASS_CONST`
  *
  * @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 visitClassConst(Node $node) : UnionType
 {
     $constant_name = $node->children['const'];
     // class name fetch
     if ($constant_name == 'class') {
         return StringType::instance()->asUnionType();
     }
     try {
         $constant = (new ContextNode($this->code_base, $this->context, $node))->getClassConst();
         return $constant->getUnionType();
     } catch (CodeBaseException $exception) {
         Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno);
     } catch (NodeException $exception) {
         Log::err(Log::EUNDEF, "Can't access undeclared constant {$class_fqsen}::{$constant_name}", $this->context->getFile(), $node->lineno);
     } catch (UnanalyzableException $exception) {
         // Swallow it. There are some constructs that we
         // just can't figure out.
     } catch (NodeException $exception) {
         // Swallow it. There are some constructs that we
         // just can't figure out.
     }
     return new UnionType();
 }
Exemplo n.º 3
0
 /**
  * Visit a node with kind `\ast\AST_CLASS_CONST`
  *
  * @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
  *
  * @throws IssueException
  * An exception is thrown if we can't find the constant
  */
 public function visitClassConst(Node $node) : UnionType
 {
     $constant_name = $node->children['const'];
     // class name fetch
     if ($constant_name == 'class') {
         return StringType::instance()->asUnionType();
     }
     try {
         $constant = (new ContextNode($this->code_base, $this->context, $node))->getClassConst();
         return $constant->getUnionType();
     } catch (NodeException $exception) {
         $this->emitIssue(Issue::Unanalyzable, $node->lineno ?? 0);
     }
     return new UnionType();
 }
Exemplo n.º 4
0
 /**
  * Visit a node with kind `\ast\AST_METHOD`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitMethod(Decl $node) : Context
 {
     // Bomb out if we're not in a class context
     $class = $this->getContextClass();
     $method_name = (string) $node->name;
     $method_fqsen = FullyQualifiedMethodName::fromStringInContext($method_name, $this->context);
     // Hunt for an available alternate ID if necessary
     $alternate_id = 0;
     while ($this->code_base->hasMethodWithFQSEN($method_fqsen)) {
         $method_fqsen = $method_fqsen->withAlternateId(++$alternate_id);
     }
     $method = Method::fromNode(clone $this->context, $this->code_base, $node, $method_fqsen);
     $class->addMethod($this->code_base, $method, new None());
     if ('__construct' === $method_name) {
         $class->setIsParentConstructorCalled(false);
         if ($class->isGeneric()) {
             // Get the set of template type identifiers defined on
             // the class
             $template_type_identifiers = array_keys($class->getTemplateTypeMap());
             // Get the set of template type identifiers defined
             // across all parameter types
             $parameter_template_type_identifiers = [];
             foreach ($method->getParameterList() as $parameter) {
                 foreach ($parameter->getUnionType()->getTypeSet() as $type) {
                     if ($type instanceof TemplateType) {
                         $parameter_template_type_identifiers[] = $type->getName();
                     }
                 }
             }
             $missing_template_type_identifiers = array_diff($template_type_identifiers, $parameter_template_type_identifiers);
             if ($missing_template_type_identifiers) {
                 $this->emitIssue(Issue::GenericConstructorTypes, $node->lineno ?? 0, implode(',', $missing_template_type_identifiers), (string) $class->getFQSEN());
             }
         }
     } elseif ('__invoke' === $method_name) {
         $class->getUnionType()->addType(CallableType::instance());
     } elseif ('__toString' === $method_name && !$this->context->getIsStrictTypes()) {
         $class->getUnionType()->addType(StringType::instance());
     }
     // Create a new context with a new scope
     return $this->context->withScope($method->getInternalScope());
 }
Exemplo n.º 5
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param Method $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(Method $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(), "arg#1(pieces) is %s but {$method->getFQSEN()}() takes array when passed only 1 arg");
                 return;
             } else {
                 if ($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())) {
                             Log::err(Log::EPARAM, "arg#2(glue) is {$arg2_type} but {$method->getFQSEN()}() takes string when arg#1 is array", $context->getFile(), $context->getLineNumberStart());
                         }
                     } else {
                         if ((string) $arg1_type == 'string') {
                             if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                                 Log::err(Log::EPARAM, "arg#2(pieces) is {$arg2_type} but {$method->getFQSEN()}() takes array when arg#1 is string", $context->getFile(), $context->getLineNumberStart());
                             }
                         }
                     }
                     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) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 return;
             }
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), "The last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 1; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, CallableType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes array");
             }
             return;
         case 'array_diff_uassoc':
         case 'array_uintersect_uassoc':
             if ($argcount < 4) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 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(), "The last argument to {$method->getFQSEN()} must be a callable");
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 2], $context, $code_base, CallableType::instance()->asUnionType(), "The second last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 2; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes 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, ArrayType::instance()->asUnionType(), "arg#1(token) is %s but {$method->getFQSEN()}() takes string when passed only one arg");
             }
             // 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(), "arg#1(values) is %s but {$method->getFQSEN()}() takes array when passed only one arg")) {
                     return;
                 }
             }
             // The arginfo check will handle the other case
             break;
         default:
             break;
     }
 }
Exemplo n.º 6
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]));
 }
Exemplo n.º 7
0
Arquivo: Type.php Projeto: 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);
 }
Exemplo n.º 8
0
 /**
  * Visit a node with kind `\ast\AST_ENCAPS_LIST`
  *
  * @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 visitEncapsList(Node $node) : UnionType
 {
     return StringType::instance()->asUnionType();
 }
Exemplo n.º 9
0
 /**
  * @return Parameter
  * A parameter built from a node
  *
  * @see \Phan\Deprecated\Pass1::node_param
  * Formerly `function node_param`
  */
 public static function fromNode(Context $context, CodeBase $code_base, Node $node) : Parameter
 {
     assert($node instanceof Node, "node was not an \\ast\\Node");
     // Get the type of the parameter
     $union_type = UnionType::fromNode($context, $code_base, $node->children['type']);
     // Create the skeleton parameter from what we know so far
     $parameter = new Parameter($context, (string) $node->children['name'], $union_type, $node->flags ?? 0);
     // If there is a default value, store it and its type
     if (($default_node = $node->children['default']) !== null) {
         // We can't figure out default values during the
         // parsing phase, unfortunately
         if (!$default_node instanceof Node || $default_node->kind == \ast\AST_CONST || $default_node->kind == \ast\AST_UNARY_OP || $default_node->kind == \ast\AST_ARRAY) {
             // Get the type of the default
             $union_type = UnionType::fromNode($context, $code_base, $default_node);
             // Set the default value
             $parameter->setDefaultValueType($union_type);
         } else {
             try {
                 // Get the type of the default
                 $union_type = UnionType::fromNode($context, $code_base, $default_node, false);
             } catch (IssueException $exception) {
                 // If we're in the parsing phase and we
                 // depend on a constant that isn't yet
                 // defined, give up and set it to
                 // bool|float|int|string to avoid having
                 // to handle a future type.
                 $union_type = new UnionType([BoolType::instance(), FloatType::instance(), IntType::instance(), StringType::instance()]);
             }
             // Set the default value
             $parameter->setDefaultValueType($union_type);
         }
     }
     return $parameter;
 }
Exemplo n.º 10
0
 /**
  * Visit a node with kind `\ast\AST_CLASS_CONST`
  *
  * @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 visitClassConst(Node $node) : UnionType
 {
     $constant_name = $node->children['const'];
     // class name fetch
     if ($constant_name == 'class') {
         return StringType::instance()->asUnionType();
     }
     try {
         $class_fqsen = null;
         foreach ($this->classListFromNode($node->children['class']) as $i => $class) {
             $class_fqsen = $class->getFQSEN();
             // Check to see if the class has the constant
             if (!$class->hasConstantWithName($this->code_base, $constant_name)) {
                 continue;
             }
             return $class->getConstantWithName($this->code_base, $constant_name)->getUnionType();
         }
     } catch (CodeBaseException $exception) {
         Log::err(Log::EUNDEF, "Can't access constant {$constant_name} from undeclared class {$exception->getFQSEN()}", $this->context->getFile(), $node->lineno);
     }
     // If no class is found, we'll emit the error elsewhere
     if ($class_fqsen) {
         Log::err(Log::EUNDEF, "Can't access undeclared constant {$class_fqsen}::{$constant_name}", $this->context->getFile(), $node->lineno);
     }
     return new UnionType();
 }
Exemplo n.º 11
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param Method $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(Method $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;
             } else {
                 if ($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::emit(Issue::ParamSpecial1, $context->getFile(), $context->getLineNumberStart(), 2, 'glue', (string) $arg2_type, (string) $method->getFQSEN(), 'string', 1, 'array');
                         }
                     } else {
                         if ((string) $arg1_type == 'string') {
                             if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                                 Issue::emit(Issue::ParamSpecial1, $context->getFile(), $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::emit(Issue::ParamTooFewInternal, $context->getFile(), $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::emit(Issue::ParamTooFewInternal, $context->getFile(), $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, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(token) is %s but {$method->getFQSEN()}() takes string when passed only one arg"
                     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;
     }
 }
Exemplo n.º 12
0
 /**
  * Visit a node with kind `\ast\AST_CLASS_CONST`
  *
  * @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
  *
  * @throws IssueException
  * An exception is thrown if we can't find the constant
  */
 public function visitClassConst(Node $node) : UnionType
 {
     $constant_name = $node->children['const'];
     // class name fetch
     if ($constant_name == 'class') {
         return StringType::instance()->asUnionType();
     }
     try {
         $constant = (new ContextNode($this->code_base, $this->context, $node))->getClassConst();
         return $constant->getUnionType();
     } catch (NodeException $exception) {
         Issue::emit(Issue::Unanalyzable, $this->context->getFile(), $node->lineno ?? 0);
     } catch (UnanalyzableException $exception) {
         // Swallow it. There are some constructs that we
         // just can't figure out.
     }
     return new UnionType();
 }
Exemplo n.º 13
0
Arquivo: Clazz.php Projeto: etsy/phan
 /**
  * This method must be called before analysis
  * begins.
  *
  * @return void
  */
 protected function hydrateOnce(CodeBase $code_base)
 {
     foreach ($this->getAncestorFQSENList($code_base) as $fqsen) {
         if ($code_base->hasClassWithFQSEN($fqsen)) {
             $code_base->getClassByFQSEN($fqsen)->hydrate($code_base);
         }
     }
     // Create the 'class' constant
     $this->addConstant($code_base, new ClassConstant($this->getContext(), 'class', StringType::instance()->asUnionType(), 0, FullyQualifiedClassConstantName::make($this->getFQSEN(), 'class')));
     // Add variable '$this' to the scope
     $this->getInternalScope()->addVariable(new Variable($this->getContext(), 'this', $this->getUnionType(), 0));
     // Load parent methods, properties, constants
     $this->importAncestorClasses($code_base);
 }
Exemplo n.º 14
0
 /**
  * Visit a node with kind `\ast\AST_METHOD`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitMethod(Decl $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     $method_name = (string) $node->name;
     $method_fqsen = FullyQualifiedMethodName::fromStringInContext($method_name, $this->context);
     // Hunt for an available alternate ID if necessary
     $alternate_id = 0;
     while ($this->code_base->hasMethod($method_fqsen)) {
         $method_fqsen = $method_fqsen->withAlternateId(++$alternate_id);
     }
     // Create a new context with a new scope
     $context = $this->context->withScope(new Scope());
     // Add $this to the scope of non-static methods
     if (!($node->flags & \ast\flags\MODIFIER_STATIC)) {
         assert($clazz->getContext()->getScope()->hasVariableWithName('this'), "Classes must have a \$this variable.");
         $context = $context->withScopeVariable($clazz->getContext()->getScope()->getVariableWithName('this'));
     }
     $method = Method::fromNode($context, $this->code_base, $node);
     // Override the FQSEN with the found alternate ID
     $method->setFQSEN($method_fqsen);
     $clazz->addMethod($this->code_base, $method);
     if ('__construct' === $method_name) {
         $clazz->setIsParentConstructorCalled(false);
     } else {
         if ('__invoke' === $method_name) {
             $clazz->getUnionType()->addType(CallableType::instance());
         } else {
             if ('__toString' === $method_name && !$this->context->getIsStrictTypes()) {
                 $clazz->getUnionType()->addType(StringType::instance());
             }
         }
     }
     // Add each method parameter to the scope. We clone it
     // so that changes to the variable don't alter the
     // parameter definition
     foreach ($method->getParameterList() as $parameter) {
         $method->getContext()->addScopeVariable(clone $parameter);
     }
     // Send the context into the method and reset the scope
     $context = $method->getContext()->withMethodFQSEN($method->getFQSEN());
     return $context;
 }
Exemplo n.º 15
0
 /**
  * Visit a node with kind `\ast\AST_METHOD`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitMethod(Decl $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     $method_name = (string) $node->name;
     $method_fqsen = FullyQualifiedMethodName::fromStringInContext($method_name, $this->context);
     // Hunt for an available alternate ID if necessary
     $alternate_id = 0;
     while ($this->code_base->hasMethodWithFQSEN($method_fqsen)) {
         $method_fqsen = $method_fqsen->withAlternateId(++$alternate_id);
     }
     // Create a new context with a new scope
     $context = $this->context->withScope(new Scope());
     $method = Method::fromNode($context, $this->code_base, $node);
     // Override the FQSEN with the found alternate ID
     $method->setFQSEN($method_fqsen);
     $clazz->addMethod($this->code_base, $method);
     if ('__construct' === $method_name) {
         $clazz->setIsParentConstructorCalled(false);
     } elseif ('__invoke' === $method_name) {
         $clazz->getUnionType()->addType(CallableType::instance());
     } elseif ('__toString' === $method_name && !$this->context->getIsStrictTypes()) {
         $clazz->getUnionType()->addType(StringType::instance());
     }
     // Send the context into the method and reset the scope
     $context = $this->context->withMethodFQSEN($method->getFQSEN());
     return $context;
 }
Exemplo n.º 16
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);
 }
Exemplo n.º 17
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);
 }
Exemplo n.º 18
0
Arquivo: Clazz.php Projeto: tpunt/phan
 /**
  * This method must be called before analysis
  * begins.
  *
  * @return void
  */
 protected function hydrateOnce(CodeBase $code_base)
 {
     $constant_fqsen = FullyQualifiedClassConstantName::make($this->getFQSEN(), 'class');
     // Create the 'class' constant
     $constant = new ClassConstant($this->getContext(), 'class', StringType::instance()->asUnionType(), 0, $constant_fqsen);
     $this->addConstant($code_base, $constant);
     // Add variable '$this' to the scope
     $this->getInternalScope()->addVariable(new Variable($this->getContext(), 'this', $this->getUnionType(), 0));
     // Load parent methods, properties, constants
     $this->importAncestorClasses($code_base);
 }