Skip to content

Instantly share code, notes, and snippets.

@dasl-
Created May 17, 2023 21:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dasl-/6175cad40fac8e55ade19405e0d9ed79 to your computer and use it in GitHub Desktop.
Save dasl-/6175cad40fac8e55ade19405e0d9ed79 to your computer and use it in GitHub Desktop.
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