/** * @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()]); }
/** * @param UnionType $target * A type to check to see if this can cast to it * * @return bool * True if this type is allowed to cast to the given type * i.e. int->float is allowed while float->int is not. * * @see \Phan\Deprecated\Pass2::type_check * Formerly 'function type_check' */ public function canCastToUnionType(UnionType $target) : bool { // Fast-track most common cases first // If either type is unknown, we can't call it // a success if ($this->isEmpty() || $target->isEmpty()) { return true; } // T === T if ($this->isEqualTo($target)) { return true; } if (Config::get()->null_casts_as_any_type) { // null <-> null if ($this->isType(NullType::instance()) || $target->isType(NullType::instance())) { return true; } } // mixed <-> mixed if ($target->hasType(MixedType::instance()) || $this->hasType(MixedType::instance())) { return true; } // int -> float if ($this->isType(IntType::instance()) && $target->isType(FloatType::instance())) { return true; } // Check conversion on the cross product of all // type combinations and see if any can cast to // any. foreach ($this->getTypeList() as $source_type) { if (empty($source_type)) { continue; } foreach ($target->getTypeList() as $target_type) { if (empty($target_type)) { continue; } if ($source_type->canCastToType($target_type)) { return true; } } } // Only if no source types can be cast to any target // types do we say that we cannot perform the cast return false; }
/** * @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()]); }
/** * 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"); } }