Skip to content

Commit fa61f6f

Browse files
committed
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.
1 parent 0561a63 commit fa61f6f

File tree

2 files changed

+23
-5
lines changed

2 files changed

+23
-5
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,22 @@ predicate hasContextmanagerDecorator(Function func) {
304304
)
305305
}
306306

307+
/**
308+
* Holds if the function `func` has a `typing.overload` decorator.
309+
* Such functions are type stubs that declare an overload signature but are
310+
* not the actual implementation.
311+
*/
312+
overlay[local]
313+
predicate hasOverloadDecorator(Function func) {
314+
exists(ControlFlowNode overload |
315+
overload.(NameNode).getId() = "overload" and overload.(NameNode).isGlobal()
316+
or
317+
overload.(AttrNode).getObject("overload").(NameNode).isGlobal()
318+
|
319+
func.getADecorator() = overload.getNode()
320+
)
321+
}
322+
307323
// =============================================================================
308324
// Callables
309325
// =============================================================================
@@ -849,7 +865,8 @@ private Class getNextClassInMro(Class cls) {
849865
*/
850866
Function findFunctionAccordingToMro(Class cls, string name) {
851867
result = cls.getAMethod() and
852-
result.getName() = name
868+
result.getName() = name and
869+
not hasOverloadDecorator(result)
853870
or
854871
not class_has_method(cls, name) and
855872
result = findFunctionAccordingToMro(getNextClassInMro(cls), name)
@@ -891,6 +908,7 @@ Class getNextClassInMroKnownStartingClass(Class cls, Class startingClass) {
891908
Function findFunctionAccordingToMroKnownStartingClass(Class cls, Class startingClass, string name) {
892909
result = cls.getAMethod() and
893910
result.getName() = name and
911+
not hasOverloadDecorator(result) and
894912
cls = getADirectSuperclass*(startingClass)
895913
or
896914
not class_has_method(cls, name) and

python/ql/test/library-tests/dataflow/calls-overload/test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ def __init__(self, x: str, y: str) -> None: ...
1111
def __init__(self, x, y=None):
1212
pass
1313

14-
OverloadedInit(1) # $ init=OverloadedInit.__init__:11 SPURIOUS: init=OverloadedInit.__init__:6 init=OverloadedInit.__init__:9
15-
OverloadedInit("a", "b") # $ init=OverloadedInit.__init__:11 SPURIOUS: init=OverloadedInit.__init__:6 init=OverloadedInit.__init__:9
14+
OverloadedInit(1) # $ init=OverloadedInit.__init__:11
15+
OverloadedInit("a", "b") # $ init=OverloadedInit.__init__:11
1616

1717

1818
from typing import overload
@@ -28,8 +28,8 @@ def __init__(self, x: str, y: str) -> None: ...
2828
def __init__(self, x, y=None):
2929
pass
3030

31-
OverloadedInitFromImport(1) # $ init=OverloadedInitFromImport.__init__:28 SPURIOUS: init=OverloadedInitFromImport.__init__:23 init=OverloadedInitFromImport.__init__:26
32-
OverloadedInitFromImport("a", "b") # $ init=OverloadedInitFromImport.__init__:28 SPURIOUS: init=OverloadedInitFromImport.__init__:23 init=OverloadedInitFromImport.__init__:26
31+
OverloadedInitFromImport(1) # $ init=OverloadedInitFromImport.__init__:28
32+
OverloadedInitFromImport("a", "b") # $ init=OverloadedInitFromImport.__init__:28
3333

3434

3535
class NoOverloads:

0 commit comments

Comments
 (0)