Skip to content

(🎁) Support overloads in type converters #4642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions atest/robot/keywords/type_conversion/overloads.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*** Settings ***
Suite Setup Run Tests ${EMPTY} keywords/type_conversion/overloads.robot
Resource atest_resource.robot

*** Test Cases ***
Annotated
Check Test Case ${TESTNAME}

Unannotated
Check Test Case ${TESTNAME}
27 changes: 27 additions & 0 deletions atest/testdata/keywords/type_conversion/overloads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Union

from typing_extensions import overload


@overload
def foo(argument: int, expected: object): ...


@overload
def foo(argument: None, expected: object): ...


def foo(argument: Union[int, None], expected: object):
assert argument == expected


@overload
def bar(argument: int, expected: object): ...


@overload
def bar(argument: None, expected: object): ...


def bar(argument, expected):
assert argument == expected
23 changes: 23 additions & 0 deletions atest/testdata/keywords/type_conversion/overloads.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
*** Settings ***
Library overloads.py
Resource conversion.resource

*** Test Cases ***
Annotated
foo 1 ${1}
foo None ${None}
TRY
foo a a
EXCEPT ValueError: Couldn't convert to any overload:\n \ Argument 'argument' got value 'a' that cannot be converted to integer.\n \ Argument 'argument' got value 'a' that cannot be converted to None.
No Operation
END


Unannotated
bar 1 ${1}
bar None ${None}
TRY
foo a a
EXCEPT ValueError: Couldn't convert to any overload:\n \ Argument 'argument' got value 'a' that cannot be converted to integer.\n \ Argument 'argument' got value 'a' that cannot be converted to None.
No Operation
END
19 changes: 17 additions & 2 deletions src/robot/running/arguments/argumentparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
from robot.variables import is_assign, is_scalar_assign

from .argumentspec import ArgumentSpec
try:
from typing import get_overloads
except ImportError:
try:
from typing_extensions import get_overloads
except ImportError:
get_overloads = None


class _ArgumentParser:
Expand All @@ -41,8 +48,16 @@ def _report_error(self, error):

class PythonArgumentParser(_ArgumentParser):

def parse(self, handler, name=None):
spec = ArgumentSpec(name, self._type)
def parse(self, handler, name=None, parse_overloads=True):
try:
overloads = get_overloads(handler)
except Exception: # < 3.10 doesn't work with `functools.partial` objects
overloads = []
spec_overloads = (
tuple(self.parse(overload, name, False) for overload in overloads)
if get_overloads and parse_overloads else ()
)
spec = ArgumentSpec(name, self._type, overloads=spec_overloads)
self._set_args(spec, handler)
self._set_types(spec, handler)
return spec
Expand Down
3 changes: 2 additions & 1 deletion src/robot/running/arguments/argumentspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ArgumentSpec:

def __init__(self, name=None, type='Keyword', positional_only=None,
positional_or_named=None, var_positional=None, named_only=None,
var_named=None, defaults=None, types=None):
var_named=None, defaults=None, types=None, overloads=()):
self.name = name
self.type = type
self.positional_only = positional_only or []
Expand All @@ -38,6 +38,7 @@ def __init__(self, name=None, type='Keyword', positional_only=None,
self.var_named = var_named
self.defaults = defaults or {}
self.types = types
self.overloads = overloads

@setter
def types(self, types):
Expand Down
15 changes: 13 additions & 2 deletions src/robot/running/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,19 @@ def _get_initial_handler(self, library, name, method):
return None

def resolve_arguments(self, args, variables=None, languages=None):
return self.arguments.resolve(args, variables, self.library.converters,
languages=languages)
if not self.arguments.overloads:
return self.arguments.resolve(args, variables, self.library.converters,
languages=languages)
errors = []
for spec in self.arguments.overloads:
try:
return spec.resolve(args, variables, self.library.converters,
languages=languages)
except ValueError as e:
errors.append(str(e))
message = '\n '.join(errors)
raise ValueError(f"Couldn't convert to any overload:\n {message}")


@property
def doc(self):
Expand Down
2 changes: 1 addition & 1 deletion utest/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# External Python modules required by unit tests.
docutils >= 0.10
jsonschema
typing_extensions; python_version <= '3.8'
typing_extensions # needed for backported overloads