Skip to content
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

Added get_callee() which determines the right routine of the return values of get_callees() #2775

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ gnu_opt_report.txt
*.MOD
.cache
.coverage
**/*.swp
*.swp
doc/*/_build
doc/*/*/_build
doc/reference_guide/source/autogenerated/
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ cloning this repository and using:

$ pip install .

or in developer mode using
or in developer (editable) mode using

$ pip install -e .

Expand Down
89 changes: 50 additions & 39 deletions src/psyclone/psyir/nodes/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
from typing import List


class CallMatchingArgumentsNotFound(BaseException):
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
"""Exception to signal that matching arguments have not been found
for this routine
"""


class Call(Statement, DataNode):
''' Node representing a Call. This can be found as a standalone statement
or an expression.
Expand Down Expand Up @@ -258,7 +264,8 @@
raise ValueError(
f"The value of the existing_name argument ({existing_name}) "
f"in 'replace_named_arg' in the 'Call' node was not found "
f"in the existing arguments.")
f"in the existing arguments."
)
# The n'th argument is placed at the n'th+1 children position
# because the 1st child is the routine reference
self.children[index + 1] = arg
Expand Down Expand Up @@ -335,7 +342,7 @@
return None

@property
def arguments(self) -> DataNode:
def arguments(self) -> List[DataNode]:
'''
:returns: the children of this node that represent its arguments.
:rtype: list[py:class:`psyclone.psyir.nodes.DataNode`]
Expand Down Expand Up @@ -427,6 +434,7 @@
:returns: a copy of this node and its children.
:rtype: :py:class:`psyclone.psyir.node.Node`
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
'''

# ensure _argument_names is consistent with actual arguments
# before copying.
self._reconcile()
Expand Down Expand Up @@ -594,40 +602,29 @@
f"{_location_txt(root_node)}. This is normally because the routine"
f" is within a CodeBlock.")

class MatchingArgumentsNotFound(BaseException):
"""Excepction to signal that matching arguments have not been found
for this routine
"""

def get_argument_routine_match(self, routine: Routine):
"""Return a list of integers giving for each argument of the call
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
the index of the argument in argument_list (typically of a routine)

:param argument_list: List of arguments
:type argument_list: Arguments
:return: None if no match was found, otherwise list of integers
referring to matching arguments.
referring to matching arguments.
:rtype: None|List[int]
"""

# Create a copy of the list
# Create a copy of the list of actual arguments to the routine.
# Once an argument has been successfully matched, set it to 'None'
routine_argument_list: List[DataNode] = \
routine.symbol_table.argument_list[:]

# Find matching argument list
# if len(self.arguments) != len(routine_argument_list):
# return None

if len(self.arguments) > len(routine.symbol_table.argument_list):
raise self.MatchingArgumentsNotFound(
f"More arguments in callee (call '{self.routine.name}')"
f" than caller (routine '{routine.name}')"
raise CallMatchingArgumentsNotFound(
f"More arguments in call ('{self.debug_string()}')"
f" than callee (routine '{routine.name}')"
)

assert len(self.arguments) == len(self.argument_names)
schreiberx marked this conversation as resolved.
Show resolved Hide resolved

# Iterate over all arguments
# Iterate over all arguments to the call
ret_arg_idx_list = []
for call_arg_idx, call_arg in enumerate(self.arguments):
call_arg_idx: int
Expand All @@ -643,12 +640,13 @@
#
# TODO #759: If optional is used, it's an unsupported Fortran
# type and we need to use the following workaround
# Once this issue is resolved, simply remove this if branch
# Once this issue is resolved, simply remove this if branch.
# Optional arguments are processed further down.
if not isinstance(
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
routine_arg.datatype,
UnsupportedFortranType):
if call_arg.datatype != routine_arg.datatype:
raise self.MatchingArgumentsNotFound(
raise CallMatchingArgumentsNotFound(
f"Argument type mismatch of call argument "
f"'{call_arg}' and routine argument "
f"'{routine_arg}'"
Expand All @@ -662,8 +660,8 @@
# Next, we handle all named arguments
#
arg_name = self.argument_names[call_arg_idx]
named_arg_found = False
routine_arg_idx = None

for routine_arg_idx, routine_arg in enumerate(
routine_argument_list):
routine_arg: DataSymbol
Expand All @@ -675,27 +673,27 @@
if arg_name == routine_arg.name:
# TODO #759: If optional is used, it's an unsupported
# Fortran type and we need to use the following workaround
# Once this issue is resolved, simply remove this if branch
# Once this issue is resolved, simply remove this if
# branch.
# Optional arguments are processed further down.
if not isinstance(
routine_arg.datatype,
UnsupportedFortranType):
if call_arg.datatype != routine_arg.datatype:
raise self.MatchingArgumentsNotFound(
raise CallMatchingArgumentsNotFound(
f"Argument type mismatch of call argument "
f"'{call_arg}' and routine argument "
f"'{routine_arg}'"
)

ret_arg_idx_list.append(routine_arg_idx)
named_arg_found = True
break

if not named_arg_found:
else:
# It doesn't match => Raise exception
raise self.MatchingArgumentsNotFound

if routine_arg_idx is None:
raise IndexError("Internal error")
raise CallMatchingArgumentsNotFound(
f"Named argument '{arg_name}' not found"
)

routine_argument_list[routine_arg_idx] = None

Expand All @@ -713,8 +711,8 @@
if ", OPTIONAL" in str(routine_arg.datatype):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do a bit better here with not much additional work. The PSyIR type system has the concept of a partial_datatype which we can use to capture all the parts of an UnsupportedFortranType that the PSyIR does support. It should be a small change to the frontend (fparser2.py) to ensure this is populated for OPTIONAL arguments. I can take a look at this if you like?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, if you would like to do this, I'd make the other changes again in call.py.

continue

raise self.MatchingArgumentsNotFound(
f"Argument '{routine_arg}' in subroutine"
raise CallMatchingArgumentsNotFound(
f"Argument '{routine_arg.name}' in subroutine"
f" '{routine.name}' not handled"
)

Expand All @@ -724,31 +722,38 @@
self,
ret_arg_match_list: List[int] = None,
check_matching_arguments: bool = True):
'''
"""
Searches for the implementation(s) of the target routine for this Call
including argument checks.
schreiberx marked this conversation as resolved.
Show resolved Hide resolved

:param ret_arg_match_list: List in which the matching argument
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
indices will be returned
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
:type ret_arg_match_list: List[int]
:param check_matching_arguments: Also check argument types to match.
:type ret_arg_match_list: bool

:returns: the Routine(s) that this call targets.
:rtype: list[:py:class:`psyclone.psyir.nodes.Routine`]
:returns: The routine that this call targets.
:rtype: psyclone.psyir.nodes.Routine

:raises NotImplementedError: if the routine is not local and not found
in any containers in scope at the call site.
'''
"""

routine_list = self.get_callees()

error: Exception = None

# Search for the routine matching the right arguments
for routine in routine_list:
routine: Routine

try:
arg_match_list = self.get_argument_routine_match(routine)
except self.MatchingArgumentsNotFound:
except CallMatchingArgumentsNotFound as err:
error = err
continue

# Provide list of indices of matching arguments if requested
if ret_arg_match_list is not None:
ret_arg_match_list[:] = arg_match_list

Expand All @@ -759,7 +764,13 @@
# This is handy for the transition phase until optional argument
# matching is supported.
if not check_matching_arguments:
return routine_list[0]

Check warning on line 767 in src/psyclone/psyir/nodes/call.py

View check run for this annotation

Codecov / codecov/patch

src/psyclone/psyir/nodes/call.py#L767

Added line #L767 was not covered by tests

raise NotImplementedError(f"No matching routine for call "
f"'{self.routine.name}' found")
if error is not None:
raise CallMatchingArgumentsNotFound(
f"No matching routine found for '{self.debug_string()}'"
) from error
else:
raise NotImplementedError(

Check warning on line 774 in src/psyclone/psyir/nodes/call.py

View check run for this annotation

Codecov / codecov/patch

src/psyclone/psyir/nodes/call.py#L774

Added line #L774 was not covered by tests
f"No matching routine found for " f"'{self.routine.name}'"
)
91 changes: 90 additions & 1 deletion src/psyclone/tests/psyir/nodes/call_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from psyclone.psyir.symbols import (
ArrayType, INTEGER_TYPE, DataSymbol, NoType, RoutineSymbol, REAL_TYPE,
SymbolError, UnsupportedFortranType)
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
from psyclone.psyir.nodes.call import CallMatchingArgumentsNotFound
from psyclone.errors import GenerationError


Expand Down Expand Up @@ -741,7 +742,9 @@ def test_call_get_callee_3_trigger_error(fortran_reader):

try:
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
call_foo.get_callee(ret_arg_match_list=arg_idx_list)
except Exception:

except CallMatchingArgumentsNotFound as err:
assert "No matching routine found for" in str(err)
print("Success! Exception triggered (as expected)")
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
return

Expand Down Expand Up @@ -1073,6 +1076,92 @@ def test_call_get_callee_6_interfaces(fortran_reader):
print(" - Passed subtest foo_c[2]")


def test_call_get_callee_7_matching_arguments_not_found(fortran_reader):
"""
Trigger error that matching arguments were not found
"""
code = """
module some_mod
implicit none
contains

subroutine main()
integer :: e, f, g
! Use name 'd' which doesn't exist
call foo(e, f, d=g)
end subroutine

! Matching routine
subroutine foo(a, b, c)
integer :: a, b, c
end subroutine

end module some_mod"""

psyir = fortran_reader.psyir_from_source(code)

routine_main: Routine = psyir.walk(Routine)[0]
assert routine_main.name == "main"

call_foo: Call = routine_main.walk(Call)[0]

try:
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
call_foo.get_callee()

except CallMatchingArgumentsNotFound as err:
assert "No matching routine found for 'call foo(e, f, d=g)" in str(err)
print("Success! Exception triggered (as expected)")
return

assert False, (
"This should have triggered an error since there"
"are more arguments in the call than in the routine"
)


def test_call_get_callee_8_arguments_not_handled(fortran_reader):
"""
Trigger error that matching arguments were not found
"""
code = """
module some_mod
implicit none
contains

subroutine main()
integer :: e, f
! Use name 'd' which doesn't exist
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
call foo(e, f)
end subroutine

! Matching routine
subroutine foo(a, b, c)
integer :: a, b, c
end subroutine

end module some_mod"""

psyir = fortran_reader.psyir_from_source(code)

routine_main: Routine = psyir.walk(Routine)[0]
assert routine_main.name == "main"

call_foo: Call = routine_main.walk(Call)[0]

try:
schreiberx marked this conversation as resolved.
Show resolved Hide resolved
call_foo.get_callee()

except CallMatchingArgumentsNotFound as err:
assert "No matching routine found for 'call foo(e, f)" in str(err)
print("Success! Exception triggered (as expected)")
return

assert False, (
"This should have triggered an error since there"
"are more arguments in the call than in the routine"
)


@pytest.mark.usefixtures("clear_module_manager_instance")
def test_call_get_callees_unresolved(fortran_reader, tmpdir, monkeypatch):
'''
Expand Down
31 changes: 24 additions & 7 deletions bin_git/run_flake8.sh → utils/run_flake8.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,27 @@
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
cd "$SCRIPTPATH/.."

# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.

#
# Hint for vscode:
# ===========================
#
# Install black-formatter
#
# In settings:
# - Activate 'Code Actions On Save'
# - Set 'Format on Save Mode' to 'modifications'
#

#
# GIT related information:
# ===========================
#
# This script can be also used as a git hook in .git/hooks/pre-push
# It then ensures that the whole of PSyclone is linted successfully
# before the push is executed.
#
# If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
Expand All @@ -14,13 +32,12 @@ cd "$SCRIPTPATH/.."
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
# Information about the commits which are being pushed is supplied as lines
# to the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This script ensures that the whole of PSyclone is linted successfully
# before the push is executed.


remote="$1"
url="$2"
Expand Down
Loading
Loading