Created
May 17, 2023 21:22
-
-
Save dasl-/6175cad40fac8e55ade19405e0d9ed79 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/Phan/AST/ContextNode.php b/src/Phan/AST/ContextNode.php | |
index 240d20795..7742316ef 100644 | |
--- a/src/Phan/AST/ContextNode.php | |
+++ b/src/Phan/AST/ContextNode.php | |
@@ -856,6 +856,197 @@ class ContextNode | |
); | |
} | |
+ public function getMethods( | |
+ $method_name, | |
+ bool $is_static, | |
+ bool $is_direct = false, | |
+ bool $is_new_expression = false | |
+ ): array { | |
+ | |
+ if ($method_name instanceof Node) { | |
+ $method_name_type = UnionTypeVisitor::unionTypeFromNode( | |
+ $this->code_base, | |
+ $this->context, | |
+ $method_name | |
+ ); | |
+ foreach ($method_name_type->getTypeSet() as $type) { | |
+ if ($type instanceof LiteralStringType) { | |
+ // TODO: Warn about nullable? | |
+ return [$this->getMethod($type->getValue(), $is_static, $is_direct, $is_new_expression)]; | |
+ } | |
+ } | |
+ // 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" | |
+ ); | |
+ } | |
+ | |
+ if (!\is_string($method_name)) { | |
+ throw new AssertionError("Method name must be a string. Found non-string in context."); | |
+ } | |
+ | |
+ $node = $this->node; | |
+ if (!($node instanceof Node)) { | |
+ throw new AssertionError('$node must be a node'); | |
+ } | |
+ | |
+ try { | |
+ // Fetch the list of valid classes, and warn about any undefined classes. | |
+ // (We have more specific issue types such as PhanNonClassMethodCall below, don't emit PhanTypeExpected*) | |
+ $class_list = (new ContextNode( | |
+ $this->code_base, | |
+ $this->context, | |
+ $node->children['expr'] | |
+ ?? $node->children['class'] | |
+ ))->getClassList( | |
+ false, | |
+ $is_static || $is_new_expression ? self::CLASS_LIST_ACCEPT_OBJECT_OR_CLASS_NAME : self::CLASS_LIST_ACCEPT_OBJECT, | |
+ null, | |
+ $is_new_expression // emit warnings about the class if this is for `new $className` | |
+ ); | |
+ } catch (CodeBaseException $exception) { | |
+ $exception_fqsen = $exception->getFQSEN(); | |
+ throw new IssueException( | |
+ Issue::fromType(Issue::UndeclaredClassMethod)( | |
+ $this->context->getFile(), | |
+ $node->lineno, | |
+ [$method_name, (string)$exception_fqsen], | |
+ ($exception_fqsen instanceof FullyQualifiedClassName | |
+ ? IssueFixSuggester::suggestSimilarClassForMethod($this->code_base, $this->context, $exception_fqsen, $method_name, $is_static) | |
+ : null) | |
+ ) | |
+ ); | |
+ } | |
+ | |
+ // 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 (\count($class_list) === 0) { | |
+ try { | |
+ $union_type = UnionTypeVisitor::unionTypeFromClassNode( | |
+ $this->code_base, | |
+ $this->context, | |
+ $node->children['expr'] | |
+ ?? $node->children['class'] | |
+ ); | |
+ } catch (FQSENException $e) { | |
+ throw new IssueException( | |
+ Issue::fromType($e instanceof EmptyFQSENException ? Issue::EmptyFQSENInClasslike : Issue::InvalidFQSENInClasslike)( | |
+ $this->context->getFile(), | |
+ $node->lineno, | |
+ [$e->getFQSEN()] | |
+ ) | |
+ ); | |
+ } | |
+ | |
+ if ($union_type->isDefinitelyUndefined() | |
+ || (!$union_type->isEmpty() | |
+ && $union_type->isNativeType() | |
+ && !$union_type->hasTypeMatchingCallback(static function (Type $type): bool { | |
+ return !$type->isNullableLabeled() && ($type instanceof MixedType || $type instanceof ObjectType); | |
+ }) | |
+ // reject `$stringVar->method()` but not `$stringVar::method()` and not (`new $stringVar()` | |
+ && !(($is_static || $is_new_expression) && $union_type->hasNonNullStringType()) | |
+ && !( | |
+ Config::get_null_casts_as_any_type() | |
+ && $union_type->hasType(NullType::instance(false)) | |
+ )) | |
+ ) { | |
+ throw new IssueException( | |
+ Issue::fromType(Issue::NonClassMethodCall)( | |
+ $this->context->getFile(), | |
+ $node->lineno, | |
+ [ $method_name, (string)$union_type ] | |
+ ) | |
+ ); | |
+ } | |
+ | |
+ throw new NodeException( | |
+ $node, | |
+ "Can't figure out method call for $method_name" | |
+ ); | |
+ } | |
+ $class_without_method = null; | |
+ $method = null; | |
+ $methods = []; | |
+ $call_method = null; | |
+ | |
+ // Hunt to see if any of them have the method we're | |
+ // looking for | |
+ foreach ($class_list as $class) { | |
+ if ($class->hasMethodWithName($this->code_base, $method_name, $is_direct)) { | |
+ $method = $class->getMethodByName($this->code_base, $method_name); | |
+ if ($method->hasTemplateType()) { | |
+ try { | |
+ $method = $method->resolveTemplateType( | |
+ $this->code_base, | |
+ UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $node->children['expr'] ?? $node->children['class']) | |
+ ); | |
+ } catch (RecursionDepthException $_) { | |
+ } | |
+ } | |
+ $methods[] = $method; | |
+ } elseif (!$is_static && $class->allowsCallingUndeclaredInstanceMethod($this->code_base)) { | |
+ $call_method = $class->getCallMethod($this->code_base); | |
+ } elseif ($is_static && $class->allowsCallingUndeclaredStaticMethod($this->code_base)) { | |
+ $call_method = $class->getCallStaticMethod($this->code_base); | |
+ } else { | |
+ $class_without_method = $class->getFQSEN(); | |
+ } | |
+ } | |
+ if (!$method || ($is_direct && $method->isFakeConstructor())) { | |
+ $method = $call_method; | |
+ if ($method !== null) { | |
+ $methods = [$call_method]; | |
+ } | |
+ } | |
+ if ($methods) { | |
+ if ($class_without_method && Config::get_strict_method_checking() && !$this->isDefinitelyPossiblyUndeclaredMethod($node, $method_name, $is_direct)) { | |
+ $this->emitIssue( | |
+ Issue::PossiblyUndeclaredMethod, | |
+ $node->lineno, | |
+ $method_name, | |
+ implode('|', \array_map(static function (Clazz $class): string { | |
+ return $class->getFQSEN()->__toString(); | |
+ }, $class_list)), | |
+ $class_without_method | |
+ ); | |
+ } | |
+ return $methods; | |
+ } | |
+ | |
+ $first_class = $class_list[0]; | |
+ | |
+ // Figure out an FQSEN for the method we couldn't find | |
+ $method_fqsen = FullyQualifiedMethodName::make( | |
+ $first_class->getFQSEN(), | |
+ $method_name | |
+ ); | |
+ | |
+ if ($is_static) { | |
+ throw new IssueException( | |
+ Issue::fromType(Issue::UndeclaredStaticMethod)( | |
+ $this->context->getFile(), | |
+ $node->lineno, | |
+ [ (string)$method_fqsen ], | |
+ IssueFixSuggester::suggestSimilarMethod($this->code_base, $this->context, $first_class, $method_name, $is_static) | |
+ ) | |
+ ); | |
+ } | |
+ | |
+ throw new IssueException( | |
+ Issue::fromType(Issue::UndeclaredMethod)( | |
+ $this->context->getFile(), | |
+ $node->lineno, | |
+ [ (string)$method_fqsen ], | |
+ IssueFixSuggester::suggestSimilarMethod($this->code_base, $this->context, $first_class, $method_name, $is_static) | |
+ ) | |
+ ); | |
+ } | |
+ | |
/** | |
* @throws IssueException | |
*/ | |
diff --git a/src/Phan/Analysis/PostOrderAnalysisVisitor.php b/src/Phan/Analysis/PostOrderAnalysisVisitor.php | |
index a7557675d..1d51b9c63 100644 | |
--- a/src/Phan/Analysis/PostOrderAnalysisVisitor.php | |
+++ b/src/Phan/Analysis/PostOrderAnalysisVisitor.php | |
@@ -1633,7 +1633,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor | |
// 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->isReturnTypeUndefined() || true // see config: allow_overriding_vague_return_types | |
) { | |
if (!$is_trait) { | |
$method->setIsReturnTypeUndefined(true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment