From ec6248b2a0128cf0e7b07af200ea4ed83cda47bf Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 13 Oct 2023 13:40:24 +0100 Subject: [PATCH 1/8] CSS: Fix search bar not right-aligned --- ford/templates/base.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ford/templates/base.html b/ford/templates/base.html index 11b46614..606935d3 100644 --- a/ford/templates/base.html +++ b/ford/templates/base.html @@ -114,11 +114,11 @@ {% endif %} {% if search %} - +
+ +
{% endif %} From 511387fad1c22a18c96cbcdb0e2a7e9b8a019ec2 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 13 Oct 2023 14:28:59 +0100 Subject: [PATCH 2/8] CSS: Fix spacing of type-bound procedures --- ford/templates/type_page.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ford/templates/type_page.html b/ford/templates/type_page.html index 457d6b0d..67ffb6d3 100644 --- a/ford/templates/type_page.html +++ b/ford/templates/type_page.html @@ -84,7 +84,9 @@

Finalization Procedures

Type-Bound Procedures

{% for bp in dtype.boundprocs %} - {{ macros.bound_info(bp) }} +
+ {{ macros.bound_info(bp) }} +
{% endfor %}
{% endif %} From 3a2d91a6ef77cb058a1619676aace18bd9b8ff93 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 13 Oct 2023 14:29:46 +0100 Subject: [PATCH 3/8] Small refactor of bound procedure bindings initialisation --- ford/sourceform.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ford/sourceform.py b/ford/sourceform.py index 7bf301ce..9f9fd9e2 100644 --- a/ford/sourceform.py +++ b/ford/sourceform.py @@ -2393,13 +2393,11 @@ def _initialize(self, line: re.Match) -> None: self.proto = line["prototype"] if self.proto: self.proto = self.proto[1:-1].strip() - self.bindings: List[Union[str, FortranProcedure, FortranBoundProcedure]] = [] - if len(split) > 1: - binds = self.SPLIT_RE.split(split[1]) - for bind in binds: - self.bindings.append(bind.strip()) - else: - self.bindings.append(self.name) + + binds = self.SPLIT_RE.split(split[1]) if len(split) > 1 else (self.name,) + self.bindings: List[Union[str, FortranProcedure, FortranBoundProcedure]] = [ + bind.strip() for bind in binds + ] def correlate(self, project): self.all_procs = self.parent.all_procs From c5372f5e81ec0d5cc908c2058b9f5e7dce647081 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 13 Oct 2023 14:30:19 +0100 Subject: [PATCH 4/8] Add `binding_type` to bound procedures Gives nice representation of `generic/procedure` --- ford/sourceform.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ford/sourceform.py b/ford/sourceform.py index 9f9fd9e2..5421202f 100644 --- a/ford/sourceform.py +++ b/ford/sourceform.py @@ -2375,6 +2375,9 @@ class FortranBoundProcedure(FortranBase): def _initialize(self, line: re.Match) -> None: self.attribs: List[str] = [] self.deferred = False + """Is a deferred procedure""" + self.protomatch = False + """Prototype has been matched to procedure""" for attribute in ford.utils.paren_split(",", line["attributes"] or ""): attribute = attribute.strip() @@ -2399,9 +2402,23 @@ def _initialize(self, line: re.Match) -> None: bind.strip() for bind in binds ] + @property + def binding_type(self) -> str: + """String representation of binding type""" + if self.generic: + return "generic" + + if not self.proto: + return "procedure" + + if self.protomatch and not self.proto.visible: + return f"procedure({self.proto.name})" + + return f"procedure({self.proto})" + def correlate(self, project): self.all_procs = self.parent.all_procs - self.protomatch = False + if self.proto: proto_lower = self.proto.lower() if proto_lower in self.all_procs: From 92cd9717d94661ab9623fbd9f5c417e601028873 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 13 Oct 2023 15:31:10 +0100 Subject: [PATCH 5/8] Fix some formatting of type-bound procedures --- example/src/ford_example_type.f90 | 33 +++++++++++++++++++++ ford/sourceform.py | 24 +++++++++++---- ford/templates/macros.html | 39 ++++++++++++------------ test/test_example.py | 2 +- test/test_sourceform.py | 49 +++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 28 deletions(-) diff --git a/example/src/ford_example_type.f90 b/example/src/ford_example_type.f90 index 5bca119b..835e55e3 100644 --- a/example/src/ford_example_type.f90 +++ b/example/src/ford_example_type.f90 @@ -5,6 +5,14 @@ module ford_example_type_mod implicit none private + public :: say_interface + + type, abstract, public :: say_type_base + !! An abstract base type that has a deferred `say` function + contains + procedure(say_interface), deferred :: say + !! Deferred type-bound procedure with a procedure prototype + end type say_type_base type, public :: example_type !! Some type that can say hello. @@ -26,6 +34,11 @@ module ford_example_type_mod !! rendered on the [[example_type]] page. !! !! This binding has more documentation beyond the summary + procedure, private, nopass :: say_int + procedure, private, nopass :: say_real + generic :: say_number => say_int, say_real + !! This generic type-bound procedure has two bindings + final :: example_type_finalise !! This is the finaliser, called when the instance is destroyed. !! @@ -40,6 +53,14 @@ module ford_example_type_mod module procedure make_new_type end interface example_type + interface + subroutine say_interface(self) + !! Abstract interface for 'say' functions + import say_type_base + class(say_type_base), intent(inout) :: self + end subroutine say_interface + end interface + contains subroutine example_type_say(self) @@ -69,4 +90,16 @@ subroutine example_type_finalise(self) write(*, '(a, " says goodbye")') self%name end subroutine example_type_finalise + subroutine say_int(int) + !! Prints an integer + integer, intent(in) :: int + write(*, '(i0)') int + end subroutine say_int + + subroutine say_real(r) + !! Prints a real + real, intent(in) :: r + write(*, '(g0)') r + end subroutine say_real + end module ford_example_type_mod diff --git a/ford/sourceform.py b/ford/sourceform.py index 5421202f..2a79834c 100644 --- a/ford/sourceform.py +++ b/ford/sourceform.py @@ -2380,12 +2380,13 @@ def _initialize(self, line: re.Match) -> None: """Prototype has been matched to procedure""" for attribute in ford.utils.paren_split(",", line["attributes"] or ""): - attribute = attribute.strip() - # Preserve original capitalisation -- TODO: needed? - attribute_lower = attribute.lower() - if attribute_lower in ["public", "private"]: - self.permission = attribute_lower - elif attribute_lower == "deferred": + attribute = attribute.strip().lower() + if not attribute: + continue + + if attribute in ["public", "private"]: + self.permission = attribute + elif attribute == "deferred": self.deferred = True else: self.attribs.append(attribute) @@ -2416,6 +2417,17 @@ def binding_type(self) -> str: return f"procedure({self.proto})" + @property + def full_declaration(self) -> str: + """String representation of the full declaration line""" + + attribute_parts = copy.copy(self.attribs) + attribute_parts.insert(0, self.permission) + if self.deferred: + attribute_parts.insert(1, "deferred") + attributes = ", ".join(attribute_parts) + return f"{self.binding_type}, {attributes}" + def correlate(self, project): self.all_procs = self.parent.all_procs diff --git a/ford/templates/macros.html b/ford/templates/macros.html index 6d8d12cd..d12d4320 100644 --- a/ford/templates/macros.html +++ b/ford/templates/macros.html @@ -320,11 +320,25 @@

Contents

{% endif %} {% endmacro %} + +{% macro bound_declaration(tb, link_name=False) %} + {# Type-bound procedure declaration and bindings #} + {{ tb.full_declaration }} :: + {% if link_name %}{{ tb }}{% else %}{{ tb.name }}{% endif %} + {%- if tb.generic or (tb.name != tb.bindings[0].name and tb.name != tb.bindings[0]) %} => {{ tb.bindings | join(", ") }}{% endif %} + {% if tb.binding|length == 1 %}{{ tb.bindings[0].proctype }}{% endif %} +{% endmacro %} + + {% macro bound_info(tb) %} -
-

{% if tb.generic -%}generic,{% else %}procedure{% if tb.proto -%}({% if not tb.protomatch or tb.proto.visible -%}{{ tb.proto }}{% else %}{{ tb.proto.name }}{%- endif %}){%- endif %},{%- endif %} {{ tb.permission }}{% if tb.deferred -%}, deferred{%- endif %}{% if tb.attribs -%}, {% for attrib in tb.attribs -%}{{ attrib }}{% if not loop.last -%}, {% endif %}{%- endfor %}{%- endif %} :: {{ tb.name }} {% if tb.generic or (tb.name != tb.bindings[0].name and tb.name != tb.bindings[0]) %} => {% for bind in tb.bindings -%}{% if not bind.parent or bind.visible %}{{ bind }}{% else %}{{ bind.name }}{% endif %}{% if not loop.last -%}, {% endif %}{%- endfor %}{% endif %} {% if tb.binding|length == 1 %}{{ tb.bindings[0].proctype }}{% endif %} +
+
+ +

+ {{ bound_declaration(tb) }} {{ deprecated(tb) }} -

+

+
{% if tb.doc or meta_list(tb.meta)|trim|length is more_than_one %}
{{ meta_list(tb.meta) }} @@ -552,24 +566,7 @@

Type-Bound Procedures

{% for tb in dtype.boundprocs %} - - {% if tb.generic -%} - generic, - {% else %} - procedure - {% if tb.proto -%} - ({% if not tb.protomatch -%}{{ tb.proto }}{% else %}{{ tb.proto.name }}{%- endif %}) - {%- endif %} - , - {%- endif %} - {{ tb.permission }} - {% if tb.attribs -%}, {{ tb.attribs|join(", ") }}{%- endif %} - :: {{ tb }} - {% if tb.generic or (tb.name != tb.bindings[0].name and tb.name != tb.bindings[0]) %} => - {{ tb.bindings|join(", ") }} - {% endif %} - {% if tb.bindings|length == 1 %}{{ tb.bindings[0].proctype }}{% endif %} - + {{ bound_declaration(tb, link_name=True) }} {{ tb | meta('summary') }} {% endfor %} diff --git a/test/test_example.py b/test/test_example.py index ecbc577c..4e80df99 100644 --- a/test/test_example.py +++ b/test/test_example.py @@ -177,7 +177,7 @@ def test_types_links(example_index): types_box = index.find(ANY_TEXT, string="Derived Types").parent types_list = sorted([f.text for f in types_box("li")]) - assert types_list == sorted(["bar", "foo", "example_type"]) + assert types_list == sorted(["bar", "foo", "example_type", "say_type_base"]) def test_types_type_bound_procedure(example_project): diff --git a/test/test_sourceform.py b/test/test_sourceform.py index 6fe4c219..3a0f4025 100644 --- a/test/test_sourceform.py +++ b/test/test_sourceform.py @@ -2131,3 +2131,52 @@ def test_generic_source(tmp_path): ] assert source.doc_list == expected_docs + + +def test_type_bound_procedure_formatting(parse_fortran_file): + data = """\ + module ford_example_type_mod + type, abstract, public :: example_type + contains + procedure :: say_hello => example_type_say + procedure, private, nopass :: say_int + procedure, private, nopass :: say_real + generic :: say_number => say_int, say_real + procedure(say_interface), deferred :: say + final :: example_type_finalise + end type example_type + + interface + subroutine say_interface(self) + import say_type_base + class(say_type_base), intent(inout) :: self + end subroutine say_interface + end interface + + contains + + subroutine example_type_say(self) + class(example_type), intent(inout) :: self + end subroutine example_type_say + + subroutine example_type_finalise(self) + type(example_type), intent(inout) :: self + end subroutine example_type_finalise + + subroutine say_int(int) + integer, intent(in) :: int + end subroutine say_int + + subroutine say_real(r) + real, intent(in) :: r + end subroutine say_real + end module ford_example_type_mod + """ + + example_type = parse_fortran_file(data).modules[0].types[0] + + assert example_type.boundprocs[0].full_declaration == "procedure, public" + assert example_type.boundprocs[1].full_declaration == "procedure, private, nopass" + assert example_type.boundprocs[2].full_declaration == "procedure, private, nopass" + assert example_type.boundprocs[3].full_declaration == "generic, public" + assert example_type.boundprocs[4].full_declaration == "procedure(say_interface), public, deferred" From 2fb89c7721509c8faf6f0ed31375188f05b0a412 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 13 Oct 2023 15:39:10 +0100 Subject: [PATCH 6/8] Fix showing docstring summary for visible bound procedures --- ford/templates/macros.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ford/templates/macros.html b/ford/templates/macros.html index d12d4320..dbe14d8f 100644 --- a/ford/templates/macros.html +++ b/ford/templates/macros.html @@ -303,7 +303,8 @@

Contents

{% if proc.parent and proc.visible %} {{ meta_list(proc.meta) }} {% endif %} - {{ proc.doc }} + + {{ docstring(proc, False) }} {{ header(4, small) }}Arguments{{ close_header(4, small) }} {% if proc.args|length %} @@ -342,7 +343,7 @@

{% if tb.doc or meta_list(tb.meta)|trim|length is more_than_one %}
{{ meta_list(tb.meta) }} - {{ tb.doc }} + {{ docstring(tb) }}
{% endif %}
    @@ -474,11 +475,11 @@

    final :: {{ proc.name }} {{ deprecated(proc) }}

{{ meta_list(proc.meta) }} - {{ proc.doc }} + {{ docstring(proc) }}
  • - {{ proc_summary(proc.procedure, title=True, full_docstring=True) }} + {{ proc_summary(proc.procedure, title=True, full_docstring=False) }}
From 84b52f3419f0b1da61a95b12f434823aafa0e30d Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 13 Oct 2023 16:07:23 +0100 Subject: [PATCH 7/8] Fix double-counting number of lines in type bound procedures Generic type-bound procedures must have their bindings already counted, so don't count them again --- ford/sourceform.py | 17 ++++++++++------- test/test_sourceform.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ford/sourceform.py b/ford/sourceform.py index 2a79834c..091dae7d 100644 --- a/ford/sourceform.py +++ b/ford/sourceform.py @@ -2040,14 +2040,9 @@ def correlate(self, project): # Get total num_lines, including implementations for proc in self.finalprocs: self.num_lines_all += proc.procedure.num_lines + for proc in self.boundprocs: - for bind in proc.bindings: - if isinstance(bind, FortranProcedure): - self.num_lines_all += bind.num_lines - elif isinstance(bind, FortranBoundProcedure): - for b in bind.bindings: - if isinstance(b, FortranProcedure): - self.num_lines_all += b.num_lines + self.num_lines_all += proc.num_lines def prune(self): """ @@ -2428,6 +2423,14 @@ def full_declaration(self) -> str: attributes = ", ".join(attribute_parts) return f"{self.binding_type}, {attributes}" + @property + def num_lines(self) -> int: + result = 0 + for binding in self.bindings: + if isinstance(binding, FortranProcedure): + result += binding.num_lines + return result + def correlate(self, project): self.all_procs = self.parent.all_procs diff --git a/test/test_sourceform.py b/test/test_sourceform.py index 3a0f4025..e7166a4f 100644 --- a/test/test_sourceform.py +++ b/test/test_sourceform.py @@ -2180,3 +2180,42 @@ def test_type_bound_procedure_formatting(parse_fortran_file): assert example_type.boundprocs[2].full_declaration == "procedure, private, nopass" assert example_type.boundprocs[3].full_declaration == "generic, public" assert example_type.boundprocs[4].full_declaration == "procedure(say_interface), public, deferred" + + +def test_type_num_lines(parse_fortran_file): + data = """\ + module ford_example_type_mod + type, abstract, public :: example_type + contains + procedure :: say_hello => example_type_say + procedure, private, nopass :: say_int + procedure, private, nopass :: say_real + generic :: say_number => say_int, say_real, say_hello + procedure(say_interface), deferred :: say + end type example_type + interface + subroutine say_interface(self) + import say_type_base + class(say_type_base), intent(inout) :: self + end subroutine say_interface + end interface + contains + subroutine example_type_say(self) + class(example_type), intent(inout) :: self + end subroutine example_type_say + subroutine say_int(int) + integer, intent(in) :: int + end subroutine say_int + subroutine say_real(r) + real, intent(in) :: r + end subroutine say_real + end module ford_example_type_mod + """ + + project = FakeProject() + module = parse_fortran_file(data).modules[0] + module.correlate(project) + + example_type = module.types[0] + assert example_type.num_lines == 8 + assert example_type.num_lines_all == 8 + 9 From 0389ea7451907f8414f3fcaf2c8437dd32c83940 Mon Sep 17 00:00:00 2001 From: ZedThree Date: Fri, 13 Oct 2023 15:50:29 +0000 Subject: [PATCH 8/8] [skip ci] Apply black changes --- test/test_sourceform.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_sourceform.py b/test/test_sourceform.py index e7166a4f..2d5d90d6 100644 --- a/test/test_sourceform.py +++ b/test/test_sourceform.py @@ -2179,7 +2179,10 @@ def test_type_bound_procedure_formatting(parse_fortran_file): assert example_type.boundprocs[1].full_declaration == "procedure, private, nopass" assert example_type.boundprocs[2].full_declaration == "procedure, private, nopass" assert example_type.boundprocs[3].full_declaration == "generic, public" - assert example_type.boundprocs[4].full_declaration == "procedure(say_interface), public, deferred" + assert ( + example_type.boundprocs[4].full_declaration + == "procedure(say_interface), public, deferred" + ) def test_type_num_lines(parse_fortran_file):