What is benefit of PASS in type-bound procedures?

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: