Can intents be added in an explicit interface?

Many old codes do not specify intent for procedure arguments. When I write interfaces to these, my habit is to provide intent when I can infer it. For example, LAPACK clearly documents the intent of each argument. I was surprised to learn recently that my habit may be wrong.

Consider the following subroutine and program defined in separate files

subroutine sub(i, o, io)
  integer :: i, o, io
  o = i+1
  io = 2*io
end subroutine
program ex
  implicit none
  interface
    subroutine sub(i, o, io)
      integer, intent(in) :: i
      integer, intent(out) :: o
      integer, intent(in out) :: io
    end subroutine
  end interface
  integer :: o, io
  io = 2
  call sub(9, o, io)
  print *, o, io ! expect: 10 4
end program

I found that gfortran 13.2.0 (-std=f2018) as well as ifort 2021.11.0 and ifx 2024.0.0 (-standard-semantics) all happily compile both of these and produce the expected output when run.

However, if I place the subroutine and program in the same file, gfortran rejects it:

Error: Interface mismatch in global procedure 'sub' at (1): INTENT mismatch in argument 'i'

Perhaps it is worth noting that if I do not ask for standards conformance or choose -std=legacy, gfortran simply emits a warning rather than an error.

This is my first time encountering such an mismatch issue. I suppose in the past, I had never seen this because the libraries I was linking to (e.g., LAPACK) were pre-compiled binaries, so the compiler had to trust that my interface was correct. However, it seems that gfortran can sometimes use its compile-time knowledge of sub here to catch the mismatch. On the other hand, ifort and ifx do not complain in either case.

This has some unfortunate implications for using old code that does not specify intents. For example, it is incorrect to call such code from within pure procedures, since pure requires that all intents are specified. Likewise, elemental procedures calling intentless procedures have to be declared impure elemental.

I have two questions:

  1. Can anyone confirm that indeed, it is illegal to add intent where none was originally specified?

  2. If it is illegal, what workarounds are there to correctly use legacy code in pure or elemental contexts?

2 Likes

Regarding workarounds, can you add a wrapper with proper intents, and inside the wrapper call the old code with unspecified intent?

The main technical issue here is that if intent is not specified, and you call your function like sub(1, 2, 3), it leads to trouble, since the second and third arguments are “out”. A safe solution could be for the compiler to require “inout” for unspecified intent, but that will prevent to call sub(9, o, io), since the first unspecified intent argument would become “inout”. So the compiler leaves it unspecified, does not enforce any checks, and it is the responsibility of the user to never pass a value for an argument that is “out”.

I am guessing what is happening above is that GFortran notices that the interface has intent(in) for something that is “intent(unspecified)”, and refuses to compile it.

As a user, I would like the compiler to not allow me to use “intent(unspecified)”, which avoids all the issues above, and for legacy codes, I think allowing to specify this using interfaces might be a good idea.

As far as the standard is concerned, interfaces with different intents are not the same interface. So gfortran is right to complain. It might work out with many compilers and many situations, but there are enough subtleties with unspecified vs specified intents that it’s probably not entirely safe (if even possible) all the time. Take this fun example for instance.

subroutine get_set(op, var, val)
  integer :: val, var, op

  select case (op)
  case (1)
    val = var
  case (2)
    var = val
  end select
end subroutine

integer :: i
i = 2
call get_set(1, 3, i)
call get_set(2, i, 4)
end

There is absolutely no way to add intents to that procedure and still be able to call it like that. But it is standards conforming Fortran.

1 Like

I guess you have meant

  integer :: val, var, op

Otherwise, the code is not compilable.

1 Like

@everythingfunctional great example.

I think that as a user, I want the compiler to simply enforce intent, and then at the call site the value must be passed as a variable, like this:

subroutine get_set(op, var, val)
  integer, intent(in) :: op
  integer, intent(inout) :: val, var

  select case (op)
  case (1)
    val = var
  case (2)
    var = val
  end select
end subroutine

integer :: i, j
i = 2
print *, i
j = 3
call get_set(1, j, i)
print *, i
j = 4
call get_set(2, i, j)
print *, i
end

I think “intent(unspecified)” is equivalent to intent(inout) except that it allows to pass in values at the call site, and then you are hoping that they won’t get modified. If you switch “1” and “2” in your original code, it will compile, but segfault at runtime. As a user I want my code to never segfault in Debug mode.

As noted, your interface and the external procedure with implicit interface don’t match. Intel will complain if you add -warn interface.

An unspecified intent dummy argument may be associated with an actual argument that is a literal constant, or a parameter constant, or an intent(in) dummy argument, or a protected module variable. Those actual arguments cannot be associated with an intent(inout) dummy argument, and I think the compiler is required to detect the argument association error. As you say, it is the programmer’s responsibility to ensure that the dummy argument is not modified though the unspecified intent dummy argument, and in practice, there are many ways that the programmer can hide the status of the actual argument so that the compiler cannot detect such an error.

3 Likes

Yeah, I’d like a compiler that at least warns me if I don’t specify intent for my arguments.

Yep, the Discourse compiler is just too lenient sometimes. :stuck_out_tongue:

2 Likes