Python: Model @typing.overload in method resolution

Adds `hasOverloadDecorator` as a predicate on functions. It looks for
decorators called `overload` or `something.overload` (usually
`typing.overload` or `t.overload`). These are then filtered out in the
predicates that (approximate) resolving methods according to the MRO.

As the test introduced in the previous commit shows, this removes the
spurious resolutions we had before.
This commit is contained in:
Taus
2026-03-05 16:56:03 +00:00
parent 0561a63003
commit fa61f6f3df
2 changed files with 23 additions and 5 deletions

View File

@@ -304,6 +304,22 @@ predicate hasContextmanagerDecorator(Function func) {
)
}
/**
* Holds if the function `func` has a `typing.overload` decorator.
* Such functions are type stubs that declare an overload signature but are
* not the actual implementation.
*/
overlay[local]
predicate hasOverloadDecorator(Function func) {
exists(ControlFlowNode overload |
overload.(NameNode).getId() = "overload" and overload.(NameNode).isGlobal()
or
overload.(AttrNode).getObject("overload").(NameNode).isGlobal()
|
func.getADecorator() = overload.getNode()
)
}
// =============================================================================
// Callables
// =============================================================================
@@ -849,7 +865,8 @@ private Class getNextClassInMro(Class cls) {
*/
Function findFunctionAccordingToMro(Class cls, string name) {
result = cls.getAMethod() and
result.getName() = name
result.getName() = name and
not hasOverloadDecorator(result)
or
not class_has_method(cls, name) and
result = findFunctionAccordingToMro(getNextClassInMro(cls), name)
@@ -891,6 +908,7 @@ Class getNextClassInMroKnownStartingClass(Class cls, Class startingClass) {
Function findFunctionAccordingToMroKnownStartingClass(Class cls, Class startingClass, string name) {
result = cls.getAMethod() and
result.getName() = name and
not hasOverloadDecorator(result) and
cls = getADirectSuperclass*(startingClass)
or
not class_has_method(cls, name) and

View File

@@ -11,8 +11,8 @@ class OverloadedInit:
def __init__(self, x, y=None):
pass
OverloadedInit(1) # $ init=OverloadedInit.__init__:11 SPURIOUS: init=OverloadedInit.__init__:6 init=OverloadedInit.__init__:9
OverloadedInit("a", "b") # $ init=OverloadedInit.__init__:11 SPURIOUS: init=OverloadedInit.__init__:6 init=OverloadedInit.__init__:9
OverloadedInit(1) # $ init=OverloadedInit.__init__:11
OverloadedInit("a", "b") # $ init=OverloadedInit.__init__:11
from typing import overload
@@ -28,8 +28,8 @@ class OverloadedInitFromImport:
def __init__(self, x, y=None):
pass
OverloadedInitFromImport(1) # $ init=OverloadedInitFromImport.__init__:28 SPURIOUS: init=OverloadedInitFromImport.__init__:23 init=OverloadedInitFromImport.__init__:26
OverloadedInitFromImport("a", "b") # $ init=OverloadedInitFromImport.__init__:28 SPURIOUS: init=OverloadedInitFromImport.__init__:23 init=OverloadedInitFromImport.__init__:26
OverloadedInitFromImport(1) # $ init=OverloadedInitFromImport.__init__:28
OverloadedInitFromImport("a", "b") # $ init=OverloadedInitFromImport.__init__:28
class NoOverloads: