canCastToExpandedUnionType() public method

public canCastToExpandedUnionType ( UnionType $target, CodeBase $code_base ) : boolean
$target UnionType The type we'd like to see if this type can cast to
$code_base Phan\CodeBase The code base used to expand types
return boolean Test to see if this type can be cast to the given type after expanding both union types to include all ancestor types
Example #1
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitProp(Node $node) : Context
 {
     $property_name = $node->children['prop'];
     // Things like $foo->$bar
     if (!is_string($property_name)) {
         return $this->context;
     }
     assert(is_string($property_name), "Property must be string in context {$this->context}");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList();
     } catch (CodeBaseException $exception) {
         // This really shouldn't happen since the code
         // parsed cleanly. This should fatal.
         // throw $exception;
         return $this->context;
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a class
         // this is, don't worry about it
         return $this->context;
     }
     foreach ($class_list as $clazz) {
         // Check to see if this class has the property or
         // a setter
         if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
             if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
                 continue;
             }
         }
         try {
             $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
         } catch (IssueException $exception) {
             $exception->getIssueInstance()();
             return $this->context;
         }
         if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
             Issue::emit(Issue::TypeMismatchProperty, $this->context->getFile(), $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
             return $this->context;
         }
         // After having checked it, add this type to it
         $property->getUnionType()->addUnionType($this->right_type);
         return $this->context;
     }
     if (Config::get()->allow_missing_properties) {
         try {
             // Create the property
             (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
         } catch (\Exception $exception) {
             // swallow it
         }
     } else {
         if (!empty($class_list)) {
             Issue::emit(Issue::UndeclaredProperty, $this->context->getFile(), $node->lineno ?? 0, $property_name);
         } else {
             // If we hit this part, we couldn't figure out
             // the class, so we ignore the issue
         }
     }
     return $this->context;
 }
Example #2
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitProp(Node $node) : Context
 {
     $property_name = $node->children['prop'];
     // Things like $foo->$bar
     if (!is_string($property_name)) {
         return $this->context;
     }
     assert(is_string($property_name), "Property must be string in context {$this->context}");
     try {
         $clazz = (new ContextNode($this->code_base, $this->context, $node))->getClass();
     } catch (CodeBaseException $exception) {
         Log::err(Log::EFATAL, $exception->getMessage(), $this->context->getFile(), $node->lineno);
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a class
         // this is, don't worry about it
         return $this->context;
     }
     if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
         // Check to see if the class has a __set method
         if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
             if (Config::get()->allow_missing_properties) {
                 try {
                     // Create the property
                     (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
                 } catch (\Exception $exception) {
                     // swallow it
                 }
             } else {
                 Log::err(Log::EAVAIL, "Missing property with name '{$property_name}'", $this->context->getFile(), $node->lineno);
             }
         }
         return $this->context;
     }
     try {
         $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
     } catch (AccessException $exception) {
         Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
         return $this->context;
     }
     if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
         Log::err(Log::ETYPE, "assigning {$this->right_type} to property but {$clazz->getFQSEN()}::{$property->getName()} is {$property->getUnionType()}", $this->context->getFile(), $node->lineno);
         return $this->context;
     }
     // After having checked it, add this type to it
     $property->getUnionType()->addUnionType($this->right_type);
     return $this->context;
 }
Example #3
0
 /**
  * Check to see if signatures match
  *
  * @return void
  */
 public static function analyzeComposition(CodeBase $code_base, Clazz $class)
 {
     // Get the Class's FQSEN
     $fqsen = $class->getFQSEN();
     // Get the list of all inherited classes.
     $inherited_class_list = $class->getInheritedClassList($code_base);
     // No chance of failed composition if we don't inherit from
     // lots of stuff.
     if (count($inherited_class_list) < 2) {
         return;
     }
     // For each property, find out every inherited class that defines it
     // and check to see if the types line up.
     foreach ($class->getPropertyList($code_base) as $property) {
         try {
             $property_union_type = $property->getUnionType();
         } catch (IssueException $exception) {
             $property_union_type = new UnionType();
         }
         // Check for that property on each inherited
         // class/trait/interface
         foreach ($inherited_class_list as $inherited_class) {
             // Skip any classes/traits/interfaces not defining that
             // property
             if (!$inherited_class->hasPropertyWithName($code_base, $property->getName())) {
                 continue;
             }
             // We don't call `getProperty` because that will create
             // them in some circumstances.
             $inherited_property_map = $inherited_class->getPropertyMap($code_base);
             if (!isset($inherited_property_map[$property->getName()])) {
                 continue;
             }
             // Get the inherited property
             $inherited_property = $inherited_property_map[$property->getName()];
             // Figure out if this property type can cast to the
             // inherited definition's type.
             $can_cast = $property_union_type->canCastToExpandedUnionType($inherited_property->getUnionType(), $code_base);
             if ($can_cast) {
                 continue;
             }
             // Don't emit an issue if the property suppresses the issue
             if ($property->hasSuppressIssue(Issue::IncompatibleCompositionProp)) {
                 continue;
             }
             Issue::maybeEmit($code_base, $property->getContext(), Issue::IncompatibleCompositionProp, $property->getFileRef()->getLineNumberStart(), (string) $class->getFQSEN(), (string) $inherited_class->getFQSEN(), $property->getName(), (string) $class->getFQSEN(), $class->getFileRef()->getFile(), $class->getFileRef()->getLineNumberStart());
         }
     }
     // TODO: This has too much overlap with PhanParamSignatureMismatch
     //       and we should figure out how to merge it.
     /*
     $method_map =
         $code_base->getMethodMapByFullyQualifiedClassName($fqsen);
     
     // For each method, find out every inherited class that defines it
     // and check to see if the types line up.
     foreach ($method_map as $i => $method) {
     
         $method_union_type = $method->getUnionType();
     
         // We don't need to analyze constructors for signature
         // compatibility
         if ($method->getName() == '__construct') {
             continue;
         }
     
         // Get the method parameter list
     
         // Check for that method on each inherited
         // class/trait/interface
         foreach ($inherited_class_list as $inherited_class) {
     
             // Skip anything that doesn't define this method
             if (!$inherited_class->hasMethodWithName($code_base, $method->getName())) {
                 continue;
             }
     
             $inherited_method =
                 $inherited_class->getMethodByName($code_base, $method->getName());
     
             if ($method == $inherited_method) {
                 continue;
             }
     
             // Figure out if this method return type can cast to the
             // inherited definition's return type.
             $is_compatible =
                 $method_union_type->canCastToExpandedUnionType(
                     $inherited_method->getUnionType(),
                     $code_base
                 );
     
             $inherited_method_parameter_map =
                 $inherited_method->getParameterList();
     
             // Figure out if all of the parameter types line up
             foreach ($method->getParameterList() as $i => $parameter) {
                 $is_compatible = (
                     $is_compatible
                     && isset($inherited_method_parameter_map[$i])
                     && $parameter->getUnionType()->canCastToExpandedUnionType(
                         ($inherited_method_parameter_map[$i])->getUnionType(),
                         $code_base
                     )
                 );
             }
     
             if ($is_compatible) {
                 continue;
             }
     
             // Don't emit an issue if the method suppresses the issue
             if ($method->hasSuppressIssue(Issue::IncompatibleCompositionMethod)) {
                 continue;
             }
     
             Issue::maybeEmit(
                 $code_base,
                 $method->getContext(),
                 Issue::IncompatibleCompositionMethod,
                 $method->getFileRef()->getLineNumberStart(),
                 (string)$method,
                 (string)$inherited_method,
                 $inherited_method->getFileRef()->getFile(),
                 $inherited_method->getFileRef()->getLineNumberStart()
             );
         }
     }
     */
 }
Example #4
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitProp(Node $node) : Context
 {
     $property_name = $node->children['prop'];
     // Things like $foo->$bar
     if (!is_string($property_name)) {
         return $this->context;
     }
     assert(is_string($property_name), "Property must be string");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList();
     } catch (CodeBaseException $exception) {
         // This really shouldn't happen since the code
         // parsed cleanly. This should fatal.
         // throw $exception;
         return $this->context;
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a class
         // this is, don't worry about it
         return $this->context;
     }
     foreach ($class_list as $clazz) {
         // Check to see if this class has the property or
         // a setter
         if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
             if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
                 continue;
             }
         }
         try {
             $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
         } catch (IssueException $exception) {
             Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
             return $this->context;
         }
         if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
             $this->emitIssue(Issue::TypeMismatchProperty, $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
             return $this->context;
         } else {
             // If we're assigning to an array element then we don't
             // know what the constitutation of the parameter is
             // outside of the scope of this assignment, so we add to
             // its union type rather than replace it.
             if ($this->is_dim_assignment) {
                 $property->getUnionType()->addUnionType($this->right_type);
             }
         }
         // After having checked it, add this type to it
         $property->getUnionType()->addUnionType($this->right_type);
         return $this->context;
     }
     $std_class_fqsen = FullyQualifiedClassName::getStdClassFQSEN();
     if (Config::get()->allow_missing_properties || !empty($class_list) && $class_list[0]->getFQSEN() == $std_class_fqsen) {
         try {
             // Create the property
             $property = (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
             $property->getUnionType()->addUnionType($this->right_type);
         } catch (\Exception $exception) {
             // swallow it
         }
     } elseif (!empty($class_list)) {
         $this->emitIssue(Issue::UndeclaredProperty, $node->lineno ?? 0, "{$class_list[0]->getFQSEN()}->{$property_name}");
     } else {
         // If we hit this part, we couldn't figure out
         // the class, so we ignore the issue
     }
     return $this->context;
 }