예제 #1
0
 /**
  * @param Node $node
  * A node to check types on
  *
  * @return UnionType
  * The resulting type(s) of the binary operation
  */
 public function visitBinaryAdd(Node $node) : UnionType
 {
     $left = UnionType::fromNode($this->context, $this->code_base, $node->children['left']);
     $right = UnionType::fromNode($this->context, $this->code_base, $node->children['right']);
     // fast-track common cases
     if ($left->isType(IntType::instance()) && $right->isType(IntType::instance())) {
         return IntType::instance()->asUnionType();
     }
     // If both left and right are arrays, then this is array
     // concatenation.
     if ($left->isGenericArray() && $right->isGenericArray()) {
         if ($left->isEqualTo($right)) {
             return $left;
         }
         return ArrayType::instance()->asUnionType();
     }
     if (($left->isType(IntType::instance()) || $left->isType(FloatType::instance())) && ($right->isType(IntType::instance()) || $right->isType(FloatType::instance()))) {
         return FloatType::instance()->asUnionType();
     }
     $left_is_array = !empty($left->genericArrayElementTypes()) && empty($left->nonGenericArrayTypes()) || $left->isType(ArrayType::instance());
     $right_is_array = !empty($right->genericArrayElementTypes()) && empty($right->nonGenericArrayTypes()) || $right->isType(ArrayType::instance());
     if ($left_is_array && !$right->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidRightOperand, $node->lineno ?? 0);
         return new UnionType();
     } elseif ($right_is_array && !$left->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidLeftOperand, $node->lineno ?? 0);
         return new UnionType();
     } elseif ($left_is_array || $right_is_array) {
         // If it is a '+' and we know one side is an array
         // and the other is unknown, assume array
         return ArrayType::instance()->asUnionType();
     }
     return new UnionType([IntType::instance(), FloatType::instance()]);
 }
예제 #2
0
 /**
  * @param Node $node
  * A node to check types on
  *
  * @return UnionType
  * The resulting type(s) of the binary operation
  */
 public function visitBinaryAdd(Node $node) : UnionType
 {
     $left = UnionType::fromNode($this->context, $this->code_base, $node->children['left']);
     $right = UnionType::fromNode($this->context, $this->code_base, $node->children['right']);
     // fast-track common cases
     if ($left->isType(IntType::instance()) && $right->isType(IntType::instance())) {
         return IntType::instance()->asUnionType();
     }
     if (($left->isType(IntType::instance()) || $left->isType(FloatType::instance())) && ($right->isType(IntType::instance()) || $right->isType(FloatType::instance()))) {
         return FloatType::instance()->asUnionType();
     }
     $left_is_array = !empty($left->genericArrayElementTypes()) && empty($left->nonGenericArrayTypes());
     $right_is_array = !empty($right->genericArrayElementTypes()) && empty($right->nonGenericArrayTypes());
     if ($left_is_array && !$right->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $this->context->getFile(), $node->lineno);
         return new UnionType();
     } else {
         if ($right_is_array && !$left->canCastToUnionType(ArrayType::instance()->asUnionType())) {
             Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $this->context->getFile(), $node->lineno);
             return new UnionType();
         } else {
             if ($left_is_array || $right_is_array) {
                 // If it is a '+' and we know one side is an array
                 // and the other is unknown, assume array
                 return ArrayType::instance()->asUnionType();
             }
         }
     }
     return new UnionType([IntType::instance(), FloatType::instance()]);
 }
예제 #3
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())));
 }
예제 #4
0
파일: Type.php 프로젝트: nikkisnow/phan
 /**
  * @return Type
  * Get a new type which is the generic array version of
  * this type. For instance, 'int' will produce 'int[]'.
  */
 public function asGenericArrayType() : Type
 {
     if ($this->name == 'array' || $this->name == 'mixed' || strpos($this->name, '[]') !== false) {
         return ArrayType::instance();
     }
     return new \Phan\Language\Type\GenericArrayType($this);
 }
예제 #5
0
 /**
  * Visit a node with kind `\ast\AST_CAST`
  *
  * @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 visitCast(Node $node) : UnionType
 {
     switch ($node->flags) {
         case \ast\flags\TYPE_NULL:
             return NullType::instance()->asUnionType();
         case \ast\flags\TYPE_BOOL:
             return BoolType::instance()->asUnionType();
         case \ast\flags\TYPE_LONG:
             return IntType::instance()->asUnionType();
         case \ast\flags\TYPE_DOUBLE:
             return FloatType::instance()->asUnionType();
         case \ast\flags\TYPE_STRING:
             return StringType::instance()->asUnionType();
         case \ast\flags\TYPE_ARRAY:
             return ArrayType::instance()->asUnionType();
         case \ast\flags\TYPE_OBJECT:
             return ObjectType::instance()->asUnionType();
         default:
             Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast");
     }
 }
예제 #6
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;
     }
 }