Fortitude 0.9.0 - release

We’ve just released Fortitude v0.9.0 :tada:

The main highlights:

  • support for pFUnit .pf files
  • support for pyproject.toml for configuration
  • support for incremental linting on existing projects with --git-staged to run just on staged changes, and --git-since to run on changes since a particular commit/branch
  • 14 new rules, bringing us to 101 rules total!
  • 34 rules out of preview mode

As always, we’d love to hear your feedback and ideas for future features and rules, and please feel free to raise an issue if you run into any problems!

12 Likes

Nice work :slight_smile:

I tried it on a few Fortran patterns that I would consider bug-prone. The two cases I could not get it to detect were:

  1. intent(out) dummy arguments assigned only inside a conditional block:
subroutine set_value(flag, x)
   implicit none
   logical, intent(in)  :: flag
   integer, intent(out) :: x
   
   if (flag) then
      x = 1
   end if

end subroutine set_value
  1. Passing an N-D array to a routine that reinterprets it with a different explicit shape:
program demo
  implicit none
  integer :: x(3, 3, 3, 3)   ! 4D array

  call fill_as_2d(x)

contains
  subroutine fill_as_2d(a)
     implicit none
     integer, intent(out) :: a(3,3) !!! < redefined as 2D array
     
     a = 1

  end subroutine fill_as_2d

end program demo

The second case is especially easy to miss because gfortran catches the mismatch if the dummy is assumed-shape, for example a(:,:), but not when it is explicit-shape.

2 Likes

Thanks @Simo! Those are both great ideas for rules. The first one I think should be pretty easy to achieve, but the second one might be a little harder right now because it needs semantic information which might come from other modules, and currently we only collect it for local variables. However, the fact that it’s a bug and gfortran misses it means it would be really good if we could catch it.

I’ll make issues for both of these, they should definitely be on our radar.

1 Like

Thank you!

If I understand correctly, the Fortran standard allows the second program (it’s not a bug when the shapes are specified explicitly), although I am pretty sure it’s bug-prone in practice, at least in my experience.

1 Like

Both programs are standard conforming. That is, it is allowed to declare variables in an undefined state, or to perform some action that leaves a variable in an undefined state. The intent(out) declaration with no subsequent definition of that entity does that. And, as you say, it is also allowed to have array dimension and array rank mismatches with explicit shape declarations. That sometimes causes copy-in/copy-out argument association, but it is standard conforming.

I did not mean to say that the first program is not standard-conforming. As far as I know, that particular pitfall is already well known and has been discussed on this forum and elsewhere. My point was only that the second program is also standard-conforming, even though the first reaction many people might have when seeing it is: “This is definitely wrong.”

should be

integer :: x(3, 3, 1, 1) ! 4D array

I think.

$ cat main.f90
program demo
  implicit none
  integer :: x(3, 3, 3, 3)   ! 4D array

  call fill_as_2d(x)

  print *, "Works."
contains
  subroutine fill_as_2d(a)
    implicit none
    integer, intent(out) :: a(3,3) !!! < redefined as 2D array

    a = 1

  end subroutine fill_as_2d
end program demo
$ gfortran -Wall -Wextra -Wsurprising -o demo main.f90

$ ./demo
 Works.

This is the way that line of code would have been written in f77, before array syntax. Nowadays, one would probably write that line of code as

call fill_as_2d( x(:,:,1,1) )

I think this does exactly the same thing, the compiler will see those two lines as being equivalent, but now a human reader will see that only a subset of the array elements are being referenced, and he will also see that a rank-2 actual array argument is associated with a rank-2 dummy array, so everything is now clear.

On the other hand, if the line of code were written as

call fill_as_2d( x(1,:,1,:)) 

then this is still legal fortran, but now the compiler must do something special (e.g. copy-in/copy-out type argument association) to make things work. (actually, only copy-out in this case because of intent(out)). This requires slice notation and array syntax, so this was not possible with f77. The programmer must understand the storage sequence convention to know the difference.