Correct program?

I encountered a curious situation with the attached program. It makes a forward reference to an argument and this is accepted by both gfortran and flang-new. However, ifx protests:

chk_forward_reference_dimension.f90(23): error #6415: This name cannot be assigned this data type because it conflicts with prior uses of the name.   [B]
    integer, intent(in) :: b
---------------------------^

If I change the name b to nb (so, under the ancient rules of implict typing it would be an integer), then ifx accepts the program.

My question: is ifx right to reject it when the argument is called b?

I think it is clear that whatever the correct behaviour, it should not depend on the argument name.
chk_forward_reference_dimension.f90 (714 Bytes)

A variable in a specification expression shall have its type and type parameters, if any, specified by a previous declaration in the same scoping unit, by the implicit typing rules in effect for the scoping unit, or by host or use association. If a variable in a specification expression is typed by the implicit typing rules, its appearance in any subsequent type declaration statement shall confirm the implied type and type parameters. If a specification inquiry depends on the type of an object of derived type, that type shall be previously defined.

B in the line below is a specification expression. There are no implicit typing rules in effect, courtesy of IMPLICIT NONE. The error message from ifx is not great. A better message is given by nagfor, which says “Implicit type for B”.

25      integer, intent(in) :: a(b)
1 Like

gfortran and flang are implementing a common extension. They will report the problem with the right options.

Using this test case:

subroutine print_size(a, b)
    implicit none
    integer, intent(in) :: a(b)
    integer, intent(in) :: b
end subroutine

With gfortran -std=f2018:

test.f90:3:29:

    3 |     integer, intent(in) :: a(b)
      |                             1
Error: GNU Extension: Symbol 'b' is used before it is typed at (1)
test.f90:1:24:

    1 | subroutine print_size( a, b )
      |                        1
Error: Symbol 'a' at (1) has no IMPLICIT type

With flang -pedantic:

./test.f90:3:30: warning: 'b' was used under IMPLICIT NONE(TYPE) before being explicitly typed [-Wforward-ref-implicit-none]
      integer, intent(in) :: a(b)
                               ^

This actually goes a little deeper than the given example might suggest. Consider this:

subroutine declare( a, b )
   implicit none
   integer, intent(in) :: a(b), b
end subroutine declare

$ gfortran -c -std=f2018 declare.f90
declare.f90:3:28:

    3 |    integer, intent(in) :: a(b), b
      |                            1
Error: GNU Extension: Symbol 'b' is used before it is typed at (1)
declare.f90:1:21:

    1 | subroutine declare( a, b )
      |                     1
Error: Symbol 'a' at (1) has no IMPLICIT type
declare.f90:1:24:

    1 | subroutine declare( a, b )
      |                        1
Error: Symbol 'b' at (1) has no IMPLICIT type

If you remove the IMPLICIT NONE statement, then you get a slightly different error message for this same declaration error. If you change the declaration statement to

integer, intent(in) :: b, a(b)

then everything is fine.

I think something similar can occur with derived types.

   type x
      type(y) :: a
   end type x
   type y
      type(x) :: b
   end type y

That circular type of declaration is not allowed. Change the first type declaration so that it does not reference type(y), and it will compile. Change the second so that it does not reference type(x), and it still results in an error because of the forward reference to type(y). However, direct self references are allowed, such as linked list type declarations:

   type llist
      integer :: value
      type(llist), allocatable :: next
   end type llist

Another example where self references are allowed is:

integer, parameter :: mat(2,2) = reshape( [1,2,3,4], shape(mat) )

Right, so the acceptance of the program by gfortran and flang is actually an extension. ifx seems to be doing an odd thing, though, when a name like “nb” leads to an accepted program (despite the “implicit none”) and a name like “b” leads to an error message.

I had not thought of using the option to specify the standard. -Wall and --check=all did not cause an error.

I don’t know why that would work. However, implicit typing is not ancient, it has been, and is now, the standard. The new thing (since f90) is that it can be turned off by the programmer with implicit none. Before f90, things like implicit none and implicit undefined were themselves extensions.

The program is not correct. The odd error message results from an extension ifx offers that lets you use a real as a bound in an array declaration. If you turn on standards checking, you get:

chk_forward_reference_dimension.f90(25): warning #8586: Implicit type is given to allow out-of-order declaration. Non-standard extension. [B]
integer, intent(in) :: a(b)
-----------------------------^
chk_forward_reference_dimension.f90(25): warning #6187: Fortran 2018 requires an INTEGER data type in this context.
integer, intent(in) :: a(b)
-----------------------------^
chk_forward_reference_dimension.f90(26): error #6415: This name cannot be assigned this data type because it conflicts with prior uses of the name. [B]
integer, intent(in) :: b
---------------------------^

b gets its type implicitly from its use in the declaration of a - following the default intrinsic rules, that makes it default real. Once an identifier is given a type, you’re not allowed to change it in a later statement. If b is renamed nb, then that is default integer and it’s OK to reaffirm that.

1 Like

The rules in Fortran are similar to the rules of C99 variable length arrays (VLA), where the size must be passed before the array using it:

#include <stdio.h>

void print_vla_info(int n, int array[n]) {
    // DON'T do this: sizeof(array) -> This is the size of the POINTER (usually 8 bytes)

    // DO do this:
    size_t size_in_bytes = n * sizeof(int);
    int length = n;

    printf("Inside function (n = %d):\n", n);
    printf("  Total bytes: %zu\n", size_in_bytes);
    printf("  Length: %d\n", length);
}

int main() {
    const int size1 = 10;
    int arr1[size1];

    printf("Test 1 (Expected 40 bytes if int is 4 bytes):\n");
    print_vla_info(size1, arr1);

    return 0;
}

In practice the actual array arguments decays into a pointer, nevertheless the information is useful for self-documentation and also the compiler might use it for improved warnings, code generation, etc.

As a GNU extension, it is possible to pass the array first, and the length afterwards, but it requires a forward declaration in the parameter list (note the semicolon!):

void print_vla_info(int n; int array[n], int n) { /* ... */ }

Just to be clear, in fortran the declarations occur separately from the argument list, so the argument list order has no constraints based on the variable declaration order. The argument list order in fortran is constrained, at least to some degree, based on programming standards (e.g. to list all input arguments before all output arguments, or visa versa) or the use of optional arguments (usually listed last), or when keyword argument association is used (once a keyword is used, then all subsequent arguments must also be keyword associated).

I personally think the C syntax (argument list and variable declaration together) is nice when there are only one or two arguments, but I prefer the fortran approach for anything beyond that for its flexibility.

1 Like

Sorry for introducing confusion; yes, the order of the argument list is less restrictive in Fortran than for C99 VLAs. I meant similar to the expectation for the array dimension specification expressions as per @themos’s reply.

Libraries like LAPACK make use of this “freedom” extensively, typically passing the true dimensions at the beginning while the leading dimension of the array comes after the array itself, e.g.

      SUBROUTINE DGESV(N, NRHS, A, LDA, IPIV, B, LDB, INFO)
*     .. Scalar Arguments ..
      INTEGER            INFO, LDA, LDB, N, NRHS
*     ..
*     .. Array Arguments ..
      INTEGER            IPIV( * )
      DOUBLE PRECISION   A( LDA, * ), B( LDB, * )

So Fortran is more like the GNU extension, the length can come afterward in the argument list, but a “forward declaration” is needed (unless implicit rules are in effect).

If LAPACK were written in C99, the argument order may have been different.

Probably so. The LAPACK argument order is more or less an extension of the BLAS argument order, which dates back to the 1970s. One might also presume that if LAPACK had been written originally with f90+, or modern fortran, then the arguments would have been different (assumed shape, allocatable, stride actual arguments, etc.).

@themos quotes the Standard, and he is quite right, but it was not always like this. Some 1980s compilers allowed forward references in declarations (e.g. VMS 6.3) and it is trivial for a compiler to sort the dependencies in declarations (fpt does this). Fortran-like continuous system simulation languages- ACSL, CSSL4, ADSIM … even sort the executable statements. Does this restriction need to exist?

I’m wondering the same.

JavaScript had vars hoisting from the start —although once could argue it’s a dynamic language, so compilers usually behave differently…

…Until you see that Go allows forward declarations for everything within a package —and since Go compilations are quite fast (provided there’s no C FFI involved), the slow compilation times argument doesn’t hold.

@Arjen this is a common gotcha in Fortran, see When is "n" in explicit-shape array A(n) implicitly typed, which discusses this at length with examples what is and what is not allowed and how exactly it works.

1 Like

Thanks for digging that up! I was surprised to find my own reply - time flies.

2 Likes

Thank you for that reference :slight_smile: . It shows another tricky program as well. I knew the program should not work, but was surprised by the variety of responses from the various compilers.

1 Like

One way to answer this is to look at the various compilers that support the forward-looking extension. Does this extension come with any problems? Are there any future features that this extension precludes or conflicts with? Are there any semantic differences in how the extension is defined in the various compilers that would need to be resolved? Is the problem that it solves significant enough to worry about?

My main concern is probably that last one. It does makes things a little easier for a programmer, but it seems like a rather trivial problem to solve, so is it really worth the effort to go through the standardization process. And if any of those previous issues exist, then that seems more significant than the minor inconvenience that it avoids. I can think of several other features I would rather have first; I expect most other fortran programmers can too.

Forward-looking modules must also be avoided. “Modern Fortran Explained” (2023) p74 says “No overall ordering of modules is required by the standard, but no module may precede a module that it uses.”

1 Like

Again, why not?

Just noticing: Forward referencing is the only possibility for contained routines and within a module there is no requirements on the order in which the routines are to be defined.