I thought so too; but then I thought about C and C++ - when you have a pointer argument, there is always a possibility it could be null.
I just created a smaller example, and it works consistently across compilers (Compiler Explorer) in the non-polymorphic case:
module test_mod
implicit none
type :: foo
contains
procedure, pass :: say_hello
procedure, nopass :: say_goodbye
end type
contains
subroutine say_hello(f)
class(foo), optional :: f
if (present(f)) then
print *, "hello from f"
else
print *, "hello from null()"
end if
end subroutine
subroutine say_goodbye
print *, "goodbye"
end subroutine
end module
program test
use test_mod
type(foo), allocatable :: f
call f%say_hello
allocate(f)
call f%say_hello
call f%say_goodbye
end program
In the polymorphic case, the majority of compilers produce crashing executables:
program test
use test_mod
! N.b. v-- class here
class(foo), allocatable :: f
call f%say_hello ! <-- breaks on ifx, ifort, flang, nvfortran, and nagfor
allocate(f)
call f%say_hello
call f%say_goodbye
end program
I guess the crash has to do with the fact that before allocation, the dynamic type of f isn’t yet established, meaning the virtual method table of the f instance isn’t populated either (the procedured could be overriden by a child type). If I add the non_overridable attribute to say_hello, then flang doesn’t crash which makes sense to me.
The rules for type-bound procedure overriding stipulate:
