Skip to content

Commit 0db132d

Browse files
SONARPY-942 Serialize unanalyzed overloaded items when regular ones are missing (#1032)
1 parent 7d8a44b commit 0db132d

File tree

5 files changed

+70
-15
lines changed

5 files changed

+70
-15
lines changed

python-frontend/src/main/java/org/sonar/python/semantic/ClassSymbolImpl.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,6 @@ public ClassSymbolImpl(SymbolsProtos.ClassSymbol classSymbolProto) {
146146
.forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullname(), d -> new HashSet<>()).add(proto));
147147
for (Map.Entry<String, Set<Object>> entry : descriptorsByFqn.entrySet()) {
148148
Set<Symbol> symbols = symbolsFromDescriptor(entry.getValue(), true);
149-
// FIXME: SONARPY-942
150-
if (symbols.isEmpty()) {
151-
continue;
152-
}
153149
methods.add(symbols.size() > 1 ? AmbiguousSymbolImpl.create(symbols) : symbols.iterator().next());
154150
}
155151
addMembers(methods);

python-frontend/src/main/java/org/sonar/python/types/TypeShed.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,8 @@ public static Set<Symbol> symbolsFromDescriptor(Set<Object> descriptors, boolean
453453
symbols.add(new FunctionSymbolImpl(((SymbolsProtos.FunctionSymbol) descriptor), isInsideClass));
454454
}
455455
if (descriptor instanceof OverloadedFunctionSymbol) {
456-
// FIXME: SONARPY-942
457456
if (((OverloadedFunctionSymbol) descriptor).getDefinitionsList().size() < 2) {
458-
LOG.error("Overloaded function symbols should have at least two definitions");
459-
continue;
457+
throw new IllegalStateException("Overloaded function symbols should have at least two definitions.");
460458
}
461459
symbols.add(fromOverloadedFunction(((OverloadedFunctionSymbol) descriptor), isInsideClass));
462460
}

python-frontend/src/main/resources/org/sonar/python/types/protobuf/__builtin__.protobuf

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ ValuesViewtyping.ValuesView"typing.MappingView"typing.Iterable*
857857
__trunc__$__builtin__._SupportsTrunc.__trunc__""
858858
__builtin__.int"__builtin__.int*B
859859
self8
860-
__builtin__._SupportsTrunc"__builtin__._SupportsTruncz27Xj27�
860+
__builtin__._SupportsTrunc"__builtin__._SupportsTruncz27Xj27�
861861
object__builtin__.object"builtins.object*i
862862
__init____builtin__.object.__init__"
863863
None*2
@@ -940,8 +940,20 @@ __reduce____builtin__.object.__reduce__"
940940
self(
941941
__builtin__.object"__builtin__.object*0
942942
protocol"
943-
__builtin__.int"__builtin__.intz272-
944-
__class____builtin__.object.__class__"27j27�
943+
__builtin__.int"__builtin__.intz272�
944+
__class____builtin__.object.__class__�
945+
__class____builtin__.object.__class__",
946+
Type[__builtin__._T]
947+
__builtin__._T*
948+
self
949+
__builtin__._T0:property�
950+
__class____builtin__.object.__class__"
951+
None*2
952+
self(
953+
__builtin__.object"__builtin__.object*P
954+
__typeF
955+
Type[__builtin__.object](
956+
__builtin__.object"__builtin__.object0:__class__.setter"27j27�
945957
staticmethod__builtin__.staticmethod"__builtin__.object*�
946958
__init__!__builtin__.staticmethod.__init__"
947959
None*>

python-frontend/typeshed_serializer/serializer/symbols.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
2020

21+
import logging
2122
import os
2223
from enum import Enum
2324
from typing import List, Union
@@ -29,6 +30,8 @@
2930

3031
CURRENT_PATH = os.path.dirname(__file__)
3132

33+
logger = logging.getLogger(__name__)
34+
3235

3336
class ParamKind(Enum):
3437
POSITIONAL_ONLY = 0
@@ -173,11 +176,23 @@ def __init__(self, overloaded_func_def: mpn.OverloadedFuncDef):
173176
self.fullname = overloaded_func_def.fullname
174177
self.definitions = []
175178
for item in overloaded_func_def.items:
176-
if isinstance(item, mpn.FuncDef):
177-
# Should not happen?
178-
self.definitions.append(FunctionSymbol(item))
179-
if isinstance(item, mpn.Decorator):
180-
self.definitions.append(FunctionSymbol(item.func, decorators=item.original_decorators))
179+
self.add_overloaded_func_definition(item)
180+
if len(self.definitions) < 2:
181+
# Consider unanalyzed items if analyzed definitions are missing
182+
if len(overloaded_func_def.unanalyzed_items) > 0:
183+
logger.warning(f'Overloaded function definitions of '
184+
f'"{overloaded_func_def.fullname}" are missing: falling back on unanalyzed items.')
185+
for item in overloaded_func_def.unanalyzed_items:
186+
self.add_overloaded_func_definition(item)
187+
if len(self.definitions) < 2:
188+
raise RuntimeError("Overloaded function symbol should contain at least 2 definitions.")
189+
190+
def add_overloaded_func_definition(self, item):
191+
if isinstance(item, mpn.FuncDef):
192+
# Should not happen?
193+
self.definitions.append(FunctionSymbol(item))
194+
if isinstance(item, mpn.Decorator):
195+
self.definitions.append(FunctionSymbol(item.func, decorators=item.original_decorators))
181196

182197
def __eq__(self, other):
183198
return isinstance(other, OverloadedFunctionSymbol) and self.to_proto() == other.to_proto()

python-frontend/typeshed_serializer/tests/test_symbols.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
from unittest import mock
2222

23+
import pytest
24+
2325
from serializer import symbols
2426
import mypy.nodes as mpn
2527

@@ -112,3 +114,35 @@ def test_python2_exception():
112114
other_symbol = symbols.MergedModuleSymbol("other", {}, {}, {})
113115
assert symbols.is_python_2_only_exception(queue_symbol) is True
114116
assert symbols.is_python_2_only_exception(other_symbol) is False
117+
118+
119+
def mock_add_definition(self, arg):
120+
assert isinstance(arg, mpn.Decorator)
121+
self.definitions.append(arg)
122+
123+
124+
def test_fallback_unanalyzed_items_when_items_are_missing():
125+
overloaded_func_mock = mock.Mock(mpn.OverloadedFuncDef)
126+
overloaded_func_mock.items = []
127+
overloaded_func_mock.fullname = "overloaded_func_mock_fullname"
128+
func_def_mock1 = mock.Mock(mpn.Decorator)
129+
func_def_mock2 = mock.Mock(mpn.Decorator)
130+
overloaded_func_mock.unanalyzed_items = [func_def_mock1, func_def_mock2]
131+
132+
with mock.patch('serializer.symbols.OverloadedFunctionSymbol.add_overloaded_func_definition', mock_add_definition), \
133+
mock.patch('logging.Logger.warning') as log_mock:
134+
overloaded_func_symbol = symbols.OverloadedFunctionSymbol(overloaded_func_mock)
135+
assert len(overloaded_func_symbol.definitions) == 2
136+
log_mock.assert_called_with('Overloaded function definitions of "overloaded_func_mock_fullname" are missing: '
137+
'falling back on unanalyzed items.')
138+
139+
140+
def test_error_when_overloaded_definitions_are_missing():
141+
overloaded_func_mock = mock.Mock(mpn.OverloadedFuncDef)
142+
overloaded_func_mock.items = []
143+
overloaded_func_mock.unanalyzed_items = []
144+
145+
with mock.patch('serializer.symbols.OverloadedFunctionSymbol.add_overloaded_func_definition', mock_add_definition):
146+
with pytest.raises(RuntimeError) as raised:
147+
symbols.OverloadedFunctionSymbol(overloaded_func_mock)
148+
assert raised.value.args[0] == 'Overloaded function symbol should contain at least 2 definitions.'

0 commit comments

Comments
 (0)