Repository: PlasmaFAIR/fortitude (language: python)

Issue:
`missing-intent` shouldn't check `procedure` arguments
If a routine argument is a `procedure`, then it shouldn't have an `intent`


Discussion / hints:
Could you post a motivating example of this one? It's not a Fortran feature I'm familiar with. I tried with the following example, but Fortitude didn't complain about anything, so I think I have it confused with something else:

```f90
module my_mod

  use, intrinsic :: iso_fortran_env, only: dp => real64
  implicit none

contains

  subroutine my_sub(x, y, z)
    real(dp), intent(in) :: x
    real(dp), intent(in) :: y
    real(dp), intent(out) :: z

    z = x + y

  end subroutine my_sub

  real(dp) function my_func(x, y) result(z)
    real(dp), intent(in) :: x
    real(dp), intent(in) :: y

    z = x + y

  end function my_func

  real(dp) function sub_as_arg(f, x, y) result(z)
    real(dp), intent(in) :: x
    real(dp), intent(in) :: y

    interface
      subroutine f(x, y, z)
        use, intrinsic :: iso_fortran_env, only: dp => real64
        implicit none
        real(dp), intent(in) :: x
        real(dp), intent(in) :: y
        real(dp), intent(out) :: z
      end subroutine f
    end interface

    call f(x, y, z)

  end function sub_as_arg

  real(dp) function func_as_arg(f, x, y) result(z)
    real(dp), intent(in) :: x
    real(dp), intent(in) :: y

    interface
      real(dp) function f(x, y)
        use, intrinsic :: iso_fortran_env, only: dp => real64
        implicit none
        real(dp), intent(in) :: x
        real(dp), intent(in) :: y
      end function f
    end interface

    z = f(x, y)

  end function func_as_arg

end module my_mod

program my_prog

  use, intrinsic :: iso_fortran_env, only: dp => real64
  use my_mod, only: sub_as_arg, func_as_arg, my_sub, my_func
  implicit none

  real(dp) :: x, y, z

  x = 1
  y = 2
  z = sub_as_arg(my_sub, x, y)

  write (*, *) z
  write (*, *) func_as_arg(my_func, x, y)

end program my_prog
```
(edit: whitespace)
```f90
module foo
  implicit none
  interface
    subroutine bar
      implicit none
    end subroutine bar
  end interface
contains
  subroutine zing(func)
    procedure(bar) :: func
  end subroutine zing
end module foo
```

```console
fortitude check /tmp/foo.f90 
/tmp/foo.f90:10:23: T031 subroutine argument 'func' missing 'intent' attribute
   |
 8 | contains
 9 |   subroutine zing(func)
10 |     procedure(bar) :: func
   |                       ^^^^ T031
11 |   end subroutine zing
12 | end module foo
   |


fortitude: 1 files scanned.
Number of errors: 1

For more information about specific rules, run:

    fortitude explain X001,Y002,...
```
Cheers! Can't say I've come across that before, but it looks like a neat way to get around a lot of the repeat-code that interface blocks often introduce.

Fix patch:
diff --git a/fortitude/src/rules/typing/intent.rs b/fortitude/src/rules/typing/intent.rs
index 11117dd..dde09a3 100644
--- a/fortitude/src/rules/typing/intent.rs
+++ b/fortitude/src/rules/typing/intent.rs
@@ -53,9 +53,10 @@ impl AstRule for MissingIntent {
 
         // Logic here is:
         // 1. find variable declarations
-        // 2. filter to the declarations that don't have an `intent`
-        // 3. filter to the ones that contain any of the dummy arguments
-        // 4. collect into a vec of violations
+        // 2. ignore `procedure` arguments
+        // 3. filter to the declarations that don't have an `intent`
+        // 4. filter to the ones that contain any of the dummy arguments
+        // 5. collect into a vec of violations
         //
         // We filter by missing intent first, so we only have to
         // filter by the dummy args once -- otherwise we either catch
@@ -64,6 +65,13 @@ impl AstRule for MissingIntent {
         let violations = parent
             .named_children(&mut parent.walk())
             .filter(|child| child.kind() == "variable_declaration")
+            .filter(|decl| {
+                if let Some(type_) = decl.child_by_field_name("type") {
+                    type_.kind() != "procedure"
+                } else {
+                    false
+                }
+            })
             .filter(|decl| {
                 !decl
                     .children_by_field_name("attribute", &mut decl.walk())


Test patch:
diff --git a/fortitude/resources/test/fixtures/typing/T031.f90 b/fortitude/resources/test/fixtures/typing/T031.f90
index 7b748d4..a06b71a 100644
--- a/fortitude/resources/test/fixtures/typing/T031.f90
+++ b/fortitude/resources/test/fixtures/typing/T031.f90
@@ -1,12 +1,22 @@
-integer function foo(a, b, c)
-  use mod
-  integer :: a, c(2), f
-  integer, dimension(:), intent(in) :: b
-end function foo
+module mod_test
+  implicit none
+  interface
+    subroutine sub
+    end subroutine sub
+  end interface
+contains
 
-subroutine bar(d, e, f)
-  integer, pointer :: d
-  integer, allocatable :: e(:, :)
-  type(integer(kind=int64)), intent(inout) :: f
-  integer :: g
-end subroutine bar
+  integer function foo(a, b, c, p)
+    use mod
+    integer :: a, c(2), f
+    integer, dimension(:), intent(in) :: b
+    procedure(sub) :: p         ! must not have `intent`
+  end function foo
+
+  subroutine bar(d, e, f)
+    integer, pointer :: d
+    integer, allocatable :: e(:, :)
+    type(integer(kind=int64)), intent(inout) :: f
+    integer :: g
+  end subroutine bar
+end module mod_test
diff --git a/fortitude/src/rules/typing/snapshots/fortitude__rules__typing__tests__missing-intent_T031.f90.snap b/fortitude/src/rules/typing/snapshots/fortitude__rules__typing__tests__missing-intent_T031.f90.snap
index 0af9226..2fd9da2 100644
--- a/fortitude/src/rules/typing/snapshots/fortitude__rules__typing__tests__missing-intent_T031.f90.snap
+++ b/fortitude/src/rules/typing/snapshots/fortitude__rules__typing__tests__missing-intent_T031.f90.snap
@@ -3,41 +3,41 @@ source: fortitude/src/rules/typing/mod.rs
 expression: diagnostics
 snapshot_kind: text
 ---
-./resources/test/fixtures/typing/T031.f90:3:14: T031 function argument 'a' missing 'intent' attribute
-  |
-1 | integer function foo(a, b, c)
-2 |   use mod
-3 |   integer :: a, c(2), f
-  |              ^ T031
-4 |   integer, dimension(:), intent(in) :: b
-5 | end function foo
-  |
+./resources/test/fixtures/typing/T031.f90:11:16: T031 function argument 'a' missing 'intent' attribute
+   |
+ 9 |   integer function foo(a, b, c, p)
+10 |     use mod
+11 |     integer :: a, c(2), f
+   |                ^ T031
+12 |     integer, dimension(:), intent(in) :: b
+13 |     procedure(sub) :: p         ! must not have `intent`
+   |
 
-./resources/test/fixtures/typing/T031.f90:3:17: T031 function argument 'c' missing 'intent' attribute
-  |
-1 | integer function foo(a, b, c)
-2 |   use mod
-3 |   integer :: a, c(2), f
-  |                 ^^^^ T031
-4 |   integer, dimension(:), intent(in) :: b
-5 | end function foo
-  |
+./resources/test/fixtures/typing/T031.f90:11:19: T031 function argument 'c' missing 'intent' attribute
+   |
+ 9 |   integer function foo(a, b, c, p)
+10 |     use mod
+11 |     integer :: a, c(2), f
+   |                   ^^^^ T031
+12 |     integer, dimension(:), intent(in) :: b
+13 |     procedure(sub) :: p         ! must not have `intent`
+   |
 
-./resources/test/fixtures/typing/T031.f90:8:23: T031 subroutine argument 'd' missing 'intent' attribute
+./resources/test/fixtures/typing/T031.f90:17:25: T031 subroutine argument 'd' missing 'intent' attribute
    |
- 7 | subroutine bar(d, e, f)
- 8 |   integer, pointer :: d
-   |                       ^ T031
- 9 |   integer, allocatable :: e(:, :)
-10 |   type(integer(kind=int64)), intent(inout) :: f
+16 |   subroutine bar(d, e, f)
+17 |     integer, pointer :: d
+   |                         ^ T031
+18 |     integer, allocatable :: e(:, :)
+19 |     type(integer(kind=int64)), intent(inout) :: f
    |
 
-./resources/test/fixtures/typing/T031.f90:9:27: T031 subroutine argument 'e' missing 'intent' attribute
-   |
- 7 | subroutine bar(d, e, f)
- 8 |   integer, pointer :: d
- 9 |   integer, allocatable :: e(:, :)
-   |                           ^^^^^^^ T031
-10 |   type(integer(kind=int64)), intent(inout) :: f
-11 |   integer :: g
+./resources/test/fixtures/typing/T031.f90:18:29: T031 subroutine argument 'e' missing 'intent' attribute
+   |
+16 |   subroutine bar(d, e, f)
+17 |     integer, pointer :: d
+18 |     integer, allocatable :: e(:, :)
+   |                             ^^^^^^^ T031
+19 |     type(integer(kind=int64)), intent(inout) :: f
+20 |     integer :: g
    |