Skip to content

Commit bf10f62

Browse files
committed
Fix type conversion if annotated argument has None as default.
Another fix to #2890.
1 parent 6beaf22 commit bf10f62

File tree

7 files changed

+55
-8
lines changed

7 files changed

+55
-8
lines changed

atest/robot/keywords/type_conversion/annotations.robot

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ String None is converted to None object
175175
Return value annotation causes no error
176176
Check Test Case ${TESTNAME}
177177

178+
None as default
179+
Check Test Case ${TESTNAME}
180+
178181
Forward references
179182
[Tags] require-py3.5
180183
Check Test Case ${TESTNAME}

atest/robot/keywords/type_conversion/annotations_with_typing.robot

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,8 @@ Set with params
4949
Invalid Set
5050
Check Test Case ${TESTNAME}
5151

52+
None as default
53+
Check Test Case ${TESTNAME}
54+
5255
Forward references
5356
Check Test Case ${TESTNAME}

atest/testdata/keywords/type_conversion/Annotations.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ def kwonly(*, argument: float, expected=None):
145145
_validate_type(argument, expected)
146146

147147

148+
def none_as_default(argument: list = None, expected=None):
149+
_validate_type(argument, expected)
150+
151+
148152
def forward_referenced_concrete_type(argument: 'int', expected=None):
149153
_validate_type(argument, expected)
150154

atest/testdata/keywords/type_conversion/AnnotationsWithTyping.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ def mutable_set_with_params(argument: MutableSet[bool], expected=None):
6767
_validate_type(argument, expected)
6868

6969

70+
def none_as_default(argument: List = None, expected=None):
71+
_validate_type(argument, expected)
72+
73+
7074
def forward_reference(argument: 'List', expected=None):
7175
_validate_type(argument, expected)
7276

atest/testdata/keywords/type_conversion/annotations.robot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ String None is converted to None object
384384
Return value annotation causes no error
385385
Return value annotation 42 42
386386

387+
None as default
388+
None as default
389+
None as default [] []
390+
387391
Forward references
388392
[Tags] require-py3.5
389393
Forward referenced concrete type 42 42

atest/testdata/keywords/type_conversion/annotations_with_typing.robot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ Invalid Set
8686
Set {} error=Value is dictionary, not set.
8787
Set ooops error=Invalid expression.
8888

89+
None as default
90+
None as default
91+
None as default [1, 2, 3, 4] [1, 2, 3, 4]
92+
8993
Forward references
9094
Forward reference [1, 2, 3, 4] [1, 2, 3, 4]
9195
Forward ref with params [1, 2, 3, 4] [1, 2, 3, 4]

src/robot/running/arguments/argumentparser.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ def parse(self, handler, name=None):
5454
= getfullargspec(handler)
5555
if ismethod(handler) or handler.__name__ == '__init__':
5656
args = args[1:] # drop 'self'
57+
defaults = self._get_defaults(args, defaults, kwonlydefaults)
5758
return ArgumentSpec(
5859
name, self._type,
5960
positional=args,
6061
varargs=varargs,
6162
kwargs=kwargs,
6263
kwonlyargs=kwonly,
63-
defaults=self._get_defaults(args, defaults, kwonlydefaults),
64-
types=self._get_types(handler, annotations)
64+
defaults=defaults,
65+
types=self._get_types(handler, annotations, defaults)
6566
)
6667

6768
def _get_defaults(self, args, default_values, kwonlydefaults):
@@ -73,17 +74,41 @@ def _get_defaults(self, args, default_values, kwonlydefaults):
7374
defaults.update(kwonlydefaults)
7475
return defaults
7576

76-
def _get_types(self, handler, annotations):
77+
def _get_types(self, handler, annotations, defaults):
7778
types = getattr(handler, 'robot_types', None)
7879
if types is not None:
7980
return types
80-
if typing:
81-
try:
82-
return typing.get_type_hints(handler)
83-
except Exception: # Can raise pretty much anything
84-
pass
81+
types = self._get_type_hints(handler, defaults)
82+
if types is not None:
83+
return types
8584
return annotations
8685

86+
def _get_type_hints(self, handler, defaults):
87+
if not typing:
88+
return None
89+
try:
90+
type_hints = typing.get_type_hints(handler)
91+
except Exception: # Can raise pretty much anything
92+
return None
93+
return self._remove_optional_none(type_hints, defaults)
94+
95+
def _remove_optional_none(self, type_hints, defaults):
96+
# If argument has None as a default, `typing.get_type_hints` adds
97+
# optional None to the information it returns. We don't want that.
98+
for arg in defaults:
99+
if defaults[arg] is None and arg in type_hints:
100+
type_ = type_hints[arg]
101+
if self._is_union(type_):
102+
types = type_.__args__
103+
if len(types) == 2 and types[1] is type(None):
104+
type_hints[arg] = types[0]
105+
return type_hints
106+
107+
def _is_union(self, type_):
108+
if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'):
109+
type_ = type_.__origin__
110+
return isinstance(type_, type(typing.Union))
111+
87112

88113
class JavaArgumentParser(_ArgumentParser):
89114

0 commit comments

Comments
 (0)