Skip to content

Commit

Permalink
Merge pull request #539 from Fortran-FOSS-Programmers/fix-call-graph-…
Browse files Browse the repository at this point in the history
…boundprocs

Include type-bound procedures in call graphs if binding not visible
  • Loading branch information
ZedThree authored Aug 2, 2023
2 parents fd4a8ed + c1a8317 commit dfb4ab5
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 15 deletions.
35 changes: 24 additions & 11 deletions ford/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import ford.utils

from ford.sourceform import (
ExternalBoundProcedure,
ExternalFunction,
ExternalInterface,
ExternalModule,
Expand Down Expand Up @@ -120,7 +121,8 @@ def is_blockdata(obj):
return isinstance(obj, FortranBlockData)


NodeCollection = Dict[FortranContainer, "BaseNode"]
FortranEntity = Union[FortranContainer, FortranBoundProcedure]
NodeCollection = Dict[FortranEntity, "BaseNode"]


class GraphData:
Expand Down Expand Up @@ -152,7 +154,7 @@ def __init__(self, parent_dir: str, coloured_edges: bool, show_proc_parent: bool
self.show_proc_parent = show_proc_parent

def _get_collection_and_node_type(
self, obj: FortranContainer
self, obj: FortranEntity
) -> Tuple[NodeCollection, Type["BaseNode"]]:
"""Helper function for `register` and `get_node`: get the
appropriate container for ``obj``, and the corresponding node
Expand Down Expand Up @@ -180,7 +182,7 @@ def _get_collection_and_node_type(
)

def register(
self, obj: FortranContainer, hist: Optional[NodeCollection] = None
self, obj: FortranEntity, hist: Optional[NodeCollection] = None
) -> None:
"""Create and store the graph node for ``obj``, if it hasn't
already been registered
Expand All @@ -200,7 +202,7 @@ def register(
collection[obj] = NodeType(obj, self, hist)

def get_node(
self, obj: FortranContainer, hist: Optional[NodeCollection] = None
self, obj: FortranEntity, hist: Optional[NodeCollection] = None
) -> BaseNode:
"""Returns the node corresponding to ``obj``. If does not
already exist then it will create it.
Expand Down Expand Up @@ -253,13 +255,17 @@ def get_type_node(
return cast(TypeNode, self.get_node(type_, hist))


def get_call_nodes(calls, visited=None, result=None):
def get_call_nodes(
calls: List[Union[str, FortranEntity]],
visited: Optional[Set[Union[str, FortranEntity]]] = None,
result: Optional[Set[Union[str, FortranEntity]]] = None,
) -> Set[Union[str, FortranEntity]]:
"""
takes a list of calls, and returns a set of all the calls that should
be nodes in the graph
not all calls are a node, some are not visible, and some are simple
procedure bindings (bindings that bind one procedure to one label)
procedure bindings (bindings that bind one visible procedure to one label)
these should be skipped, and show a call to their descendant instead
"""
Expand All @@ -280,6 +286,7 @@ def get_call_nodes(calls, visited=None, result=None):
isinstance(call, FortranBoundProcedure)
and len(call.bindings) == 1
and not isinstance(call.bindings[0], FortranBoundProcedure)
and (call.deferred or getattr(call.bindings[0], "visible", False))
)

if getattr(call, "visible", True) and not is_simple_binding:
Expand Down Expand Up @@ -310,7 +317,7 @@ class BaseNode:

def __init__(
self,
obj: Union[FortranContainer, str],
obj: Union[FortranEntity, str],
graph_data: GraphData,
hist: Optional[NodeCollection] = None,
):
Expand All @@ -321,6 +328,7 @@ def __init__(
ExternalModule,
ExternalSubmodule,
ExternalType,
ExternalBoundProcedure,
ExternalSubroutine,
ExternalFunction,
ExternalInterface,
Expand All @@ -347,7 +355,7 @@ def __init__(
self.ident = f"{d}~{obj.ident}"
self.name = obj.name
if m := EM_RE.search(self.name):
self.name = "<<i>" + m.group(1).strip() + "</i>>"
self.name = f"<<i>{m.group(1).strip()}</i>>"
self.url = obj.get_url()

self.attribs["label"] = self.name
Expand Down Expand Up @@ -633,6 +641,7 @@ def _dashed_edge(
_subroutine = gd.get_node(ExternalSubroutine("Subroutine"))
_function = gd.get_node(ExternalFunction("Function"))
_interface = gd.get_node(ExternalInterface("Interface"))
_boundproc = gd.get_node(ExternalBoundProcedure("Type Bound Procedure"))
_unknown_proc = ExternalSubroutine("Unknown Procedure Type")
_unknown_proc.proctype = "Unknown"
_unknown = gd.get_node(_unknown_proc)
Expand Down Expand Up @@ -662,7 +671,9 @@ def _make_legend(entities):

mod_svg = _make_legend([_module, _submodule, _subroutine, _function, _program])
type_svg = _make_legend([_type])
call_svg = _make_legend([_subroutine, _function, _interface, _unknown, _program])
call_svg = _make_legend(
[_subroutine, _function, _interface, _boundproc, _unknown, _program]
)
file_svg = _make_legend([_sourcefile])
else:
mod_svg = ""
Expand Down Expand Up @@ -908,9 +919,11 @@ def __str__(self):
);
</script>"""

graph_help_name = f"{self.__class__.__name__}-help-text"

legend_graph = f"""\
<div><a type="button" class="graph-help" data-toggle="modal" href="#graph-help-text">Help</a></div>
<div class="modal fade" id="graph-help-text" tabindex="-1" role="dialog">
<div><a type="button" class="graph-help" data-toggle="modal" href="#{graph_help_name}">Help</a></div>
<div class="modal fade" id="{graph_help_name}" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
Expand Down
11 changes: 7 additions & 4 deletions ford/sourceform.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
NBSP_RE = re.compile(r" (?= )|(?<= ) ")
DIM_RE = re.compile(r"^\w+\s*(\(.*\))\s*$")
PROTO_RE = re.compile(r"(\*|\w+)\s*(?:\((.*)\))?")

CALL_AND_WHITESPACE_RE = re.compile(r"\(\)|\s")

base_url = ""

Expand Down Expand Up @@ -1058,7 +1058,7 @@ def _add_procedure_calls(

# Add call chains to self.calls
for chain_str in call_chains:
call_chain = re.sub("\(\)|\s", "", chain_str).lower().split("%")
call_chain = CALL_AND_WHITESPACE_RE.sub("", chain_str).lower().split("%")

if call_chain[0] in associations:
call_chain[0:1] = associations[call_chain[0]]
Expand Down Expand Up @@ -1937,7 +1937,9 @@ def correlate(self, project):
# Match up generic type-bound procedures to their particular bindings
for proc in self.boundprocs:
for bp in inherited_generic:
if bp.name.lower() == proc.name.lower() and hasattr(bp, "bindings"):
if bp.name.lower() == proc.name.lower() and isinstance(
bp, FortranBoundProcedure
):
proc.bindings = bp.bindings + proc.bindings
break
if proc.generic:
Expand Down Expand Up @@ -2299,7 +2301,7 @@ def _initialize(self, line: re.Match) -> None:
self.proto = line["prototype"]
if self.proto:
self.proto = self.proto[1:-1].strip()
self.bindings = []
self.bindings: List[Union[str, FortranProcedure, FortranBoundProcedure]] = []
if len(split) > 1:
binds = self.SPLIT_RE.split(split[1])
for bind in binds:
Expand Down Expand Up @@ -3043,6 +3045,7 @@ def __init__(self, name: str, url: str = "", parent=None):
self.external_url = url
self.parent = parent
self.obj = "proc"
self.bindings = []


class ExternalType(FortranType):
Expand Down
12 changes: 12 additions & 0 deletions test/test_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,12 @@ def make_project_graphs(tmp_path_factory, request):
procedure :: six
procedure :: ei => eight
procedure :: ni => nine
procedure :: ten
generic :: eight_nine => ei, ni
end type alpha
private :: ten
interface
subroutine defined_elsewhere
end subroutine
Expand Down Expand Up @@ -108,6 +111,9 @@ def make_project_graphs(tmp_path_factory, request):
class(alpha) :: this
integer :: x
end subroutine nine
subroutine ten(this)
class(alpha) :: this
end subroutine then
end module c
submodule (c) c_submod
Expand All @@ -129,6 +135,7 @@ def make_project_graphs(tmp_path_factory, request):
x = y%six()
call y%ei(1.0)
call y%eight_nine(1.0)
call y%ten()
contains
subroutine three
use external_mod
Expand Down Expand Up @@ -196,6 +203,7 @@ def make_project_graphs(tmp_path_factory, request):
"Subroutine",
"Function",
"Interface",
"Type Bound Procedure",
"Unknown Procedure Type",
"Program",
]
Expand Down Expand Up @@ -257,6 +265,7 @@ def make_project_graphs(tmp_path_factory, request):
"c::alpha%eight",
"c::alpha%nine",
"c::alpha%eight_nine",
"c::alpha%ten",
],
[
"proc~three->proc~one",
Expand All @@ -273,6 +282,7 @@ def make_project_graphs(tmp_path_factory, request):
"none~eight_nine->proc~eight",
"none~eight_nine->proc~nine",
"interface~submod_proc->proc~submod_proc",
"program~foo->none~ten",
],
PROC_GRAPH_KEY,
),
Expand All @@ -297,6 +307,7 @@ def make_project_graphs(tmp_path_factory, request):
"c::alpha%eight",
"c::alpha%nine",
"c::alpha%eight_nine",
"c::alpha%ten",
],
[
"proc~three->proc~one",
Expand All @@ -316,6 +327,7 @@ def make_project_graphs(tmp_path_factory, request):
"none~eight_nine->proc~eight",
"none~eight_nine->proc~nine",
"interface~submod_proc->proc~submod_proc",
"program~foo->none~ten",
],
PROC_GRAPH_KEY,
),
Expand Down

0 comments on commit dfb4ab5

Please sign in to comment.