Calling Fortran final subroutine

It appears that compilers have different treatment of the final subroutine in OO.
This is an example:

module object_module

type :: Object
contains
final :: destructor
endtype

type, extends(Object) :: ObjectDerived
contains
endtype

type, extends(Object) :: ObjectDerivedWithDummyFinal
contains
final :: destructor_ObjectDerivedWithDummyFinal
endtype

contains

subroutine destructor(this)
type(Object) :: this
write(0,*) 'destructor called'
end subroutine

subroutine destructor_ObjectDerivedWithDummyFinal(this)
type(ObjectDerivedWithDummyFinal) :: this
! dummy, just so destructor will be called
end subroutine

end module

The request is:
When instance of ObjectDerived leaves scope, should the destructor subroutine from Object get called? Some compilers do, others require a dummy ā€˜finalā€™ routine which is empty such as done in ObjectDerivedWithDummyFinal.

From my point of view, the parent finalize should always be calledā€¦ Looking at the standard, I donā€™t see a clear explanation:

  • Fortran 2023: 7.5.6 and C3.6: each type needs a separated final subroutine, which is not inherited through type extension.
  • 7.5.6.1 Note 2.: the Final Subroutine (if exists) of its parent type (Object) is also called after the Final Subroutine of the extended type (ObjectDerivedWithDummyFinal here) is called.

Indeed the standard is not clearā€¦

What exactly do you expect to happen, but isnā€™t? Can you provide a whole program that demonstrates the behavior?

I will note that compilers have traditionally been really bad at this. Less than a year ago we wrote up a test suite for all the cases where final subroutines are supposed to be run and I think maybe one compiler got them all right. I believe gfortran has subsequently been fixed.

What I do expect is not part of the question but thanks for asking :). This is really what the compiler is supposed to do by adhering to the standard.

Sure, I have a program to test it. Apologize if I have not included in the first place.

main.f90 (283 Bytes)
object_module.f90 (1.2 KB)

In the case the parent finalize is not called, you get:

> ./main.x 
 entering
 ctor1
 ctor2
 destructor2 called 2
 destructor1 called 2
 exit

while it would be more logical (IMO) to have the following output:

entering
 ctor1
 ctor2
 destructor1 called 1
 destructor2 called 2
 destructor1 called 2
 exit

Gfortran is not really used (Iā€™m not sure even 13 is working with finalize, very bad experience with it). Iā€™ve used Cray, Intel, nvidia, NAG compilers and they have different results.

Hereā€™s the output I get from NAG (7.1):

 entering
 ctor1
 destructor1 called 0
 destructor1 called 1
 ctor2
 destructor2 called 0
 destructor1 called 0
 destructor2 called 2
 destructor1 called 2
 exit

Which is correct (I believe).

On line obj1 = ObjectDerived(), the constructor is called, the lhs (obj1) is finalized, the assignment is performed and then the rhs is finalized. Then on line obj2 = ObjectDerivedWithDummyFinal(), the constructor is called, the lhs (obj2) is finalized, which involves calling destructor2 then destructor1, and then the rhs is finalized.

With gfortran (13.2) I get:

 entering
 destructor1 called       32645
 ctor1
 destructor1 called           0
 destructor2 called   335544320
 destructor1 called   335544320
 ctor2
 destructor2 called           0
 destructor1 called           0
 exit

Which in this case would be valid to call the finalizer for the lhs before evaluating the rhs, but is not valid in the general case (i.e. if the lhs appears on the rhs). Iā€™m not sure why the assigned value from the constructor doesnā€™t get propagated through to the finalization of the rhs though.

Is there a reason you would expect something different?

OK, letā€™s consider the NAG output (as I said, Iā€™m not interested in gfortran, which I think it is buggy or at least ā€œsuspiciousā€).

My initial question was: should we call the destructor1 for the ObjectDerived()? NAG does. Still, the question remains: is this conformant to the standard? Indeed, the Cray compiler doesnā€™t call destructor1 for the ObjectDerived() and you have to go for the second case (ObjectDerivedWithDummyFinal()) to make sure you call destructor1.

Then Iā€™ve tried other compilers: NVHPC and Intel do like NAG.

Do we go for the majority and assume this is what the standard requires?

@alazzaro ,

Clearly the position of all practitioners should be no. It is what the standard states what matters, the ā€œmajorityā€ is of no relevance.

Have you referred to the finalization section in Fortran 2023 standard document (or the proxy for it, 23-007r1 or perhaps 24-007r1 - not sure itā€™s been posted yet)? Section 7.5.6 if I recall correctly.

If yes, what in that section is unclear to you and what, if any, do you notice with NAG Fortran that is inconsistent with the standard?

@FortranFan
Thanks for your reply. Please check my first message. I do mention the Fortran standard. Your point about the standard docet is exactly what I would like to know. Iā€™m not interested in what the compilers are doing (and indeed Iā€™m not mentioning them). It is not me who introduced NAG compiler (or any other compiler) in the discussionā€¦

When an intrinsic assignment statement is executed (10.2.1.3), if the variable is not an unallocated allocatable variable, it is finalized after evaluation of expr and before the definition of the variable.

That covers the LHS in your assignment statements

If an executable construct references a nonpointer function, the result is finalized after execution of the innermost executable construct containing the reference.

And that covers the RHS.

A derived type is finalizable if and only if it has a final subroutine or a nonpointer, nonallocatable component of finalizable type.

An extended type has a scalar, nonpointer, nonallocatable, parent component with the type and type parameters of the parent type.

When an entity is finalized, the following steps are carried out in sequence.

  1. If the dynamic type of the entity has a final subroutine whose dummy argument has the same kind type parameters and rank as the entity being finalized, it is called with the entity as an actual argument. Otherwise, if there is an elemental final subroutine whose dummy argument has the same kind type parameters as the entity being finalized, or a final subroutine whose dummy argument is assumed-rank with the same kind type parameters as the entity being finalized, it is called with the entity as an actual argument. Otherwise, no subroutine is called at this point.
  2. All nonallocatable finalizable components that appear in the type definition are finalized in a processor- dependent order. If the entity being finalized is an array, each finalizable component of each element of that entity is finalized separately.
  3. If the entity is of extended type and the parent type is finalizable, the parent component is finalized.

Neither of your types have finalizable components that appear in the type definition, so 2 does not apply. In the first assignment, your entities do not have final subroutines themselves, so 1 does not occur, but 3 does. In the second assignment they do have their own final subroutines, so 1 and 3 occur, in that order, individually for each entity that is finalized. Like I said, NAG is getting this right.

:person_shrugging:

1 Like

@everythingfunctional
First of all, thanks for your analysis. This is inlined with my guess (see my first message: ā€œFrom my point of view, the parent finalize should always be calledā€).

To clarify my last message about NAG, you extracted a sentence from a larger context. I was referring to my first message where I didnā€™t mention any compiler (and then you started to talk about NAG, so all credits to you for introducing NAG, which is not really of my interest).

OK, Iā€™m OK with the explanation. Thanks.

24-007 is now posted.