Hi Yuao,

Yuao Ma wrote:

===============================
@@ -6709,0 +6733,6 @@ conv_dummy_value (gfc_se * parmse, gfc_expr * e, 
gfc_symbol * fsym,
+      else if (e->expr_type == EXPR_CONDITIONAL)
+       {
+         gcc_assert (parmse && TREE_CODE (parmse->expr) == COND_EXPR);
+         tree cond = TREE_OPERAND (parmse->expr, 0);
+         vec_safe_push (optionalargs, fold_convert (boolean_type_node, cond));
+       }
===============================

IIUC, this approach won't work for a simple case like (.false. ? .nil.
: 1). We ultimately need to know if the value is .nil. or not, rather
than just checking the first condition. Since this seems similar to
how EXPR_VARIABLE is handled, I've just reused that logic.

Hmm, this one is indeed slightly tricky. If there is no .NIL.
and no allocatable/pointer is passed to a non-allocatable/non-pointer,
then the proper call would be:

sub2(cond ? *a : *b, 1)

but what we get is the following:

D.4678 = cond;
D.4679 = cond;
D.4681 = D.4679 ? (integer(kind=4) *) a != 0B : (integer(kind=4) *) b != 0B ? 
D.4678 ? *a : *b : condtemp.0;
sub2 (D.4681, D.4679 ? (integer(kind=4) *) a != 0B : (integer(kind=4) *) b != 
0B);

Admittedly, due to:
  "integer(kind=4) & restrict a, integer(kind=4) & restrict b"

the middle end knows that 'a' and 'b' cannot be NULL and, hence,
the compiler can optimize it (and will with -O1 and higher).

Ignoring alloctables/pointers for the moment, I think we could
do

  if (e->expr_type == EXPR_CONDITIONAL
      && !gfc_conditional_expr_with_nil (e))
    is_present = true;
  else ...

or keep your current code:

        /* F2018:15.5.2.12 Argument presence and
          restrictions on arguments not present.  */
-      if (e->expr_type == EXPR_VARIABLE
-         && e->rank == 0
-         && (gfc_expr_attr (e).allocatable
-             || gfc_expr_attr (e).pointer))
+      if ((e->expr_type == EXPR_VARIABLE && e->rank == 0
+          && (gfc_expr_attr (e).allocatable || gfc_expr_attr (e).pointer))
+         || e->expr_type == EXPR_CONDITIONAL)
         {

But with an updated comment - explaining why EXPR_CONDITIONAL
is here (because of .NIL. - and the non-nil parts will be optimized
away by the middle end).

* * *

HOWEVER, there is something in addition:

* Disassociated pointers and deallocated allocatables also
  count as absent (when passing them to a dummy argument
  that is optional but neither pointer or allocatable).

* Passing absent arguments on also count as, well, absent.

* allocatable/pointer + optional can be combined

* * *

In any case, there is something not quite right in this
area, as shown by the attached test cases.

(A) conditional-expr-3.f90 - this one shows that

  call sub()
...
  subroutine sub(x)
     integer, OPTIONAL, VALUE :: x
     logical :: cond = .true.
     call test ( (cond ? x : .nil.) )
...
  subroutine test(y)
     integer, OPTIONAL, VALUE :: y
     print *, present(y)  ! Prints T

is mishandled by printing 'true' despite
'call sub()' lacking an argument.

* * *

(B) I think this is about absent + allocatable/pointer,
but I have not fully checked. In any case, if
I comment in any of the 'FIXME' lines,
I get a segfault with:

   conditional-expr-1.f90

conditional-expr-2.f90 uses character strings and
is presumably fine, once expr-1 is fixed.

* * *

(C) Missing feature, also shown in
conditional-expr-1.f90

Namely, a conditional argument can be an ALLOCATABLE
or POINTER - if all its expressions are ALLOCATABLE
or all are POINTER.

Obviously, this only matters when passing a conditional
argument as actual argument to a dummy argument that
requires an allocatable or a pointer.

[And it can be combined with .NIL. if that dummy argument
is also 'optional'.]


I think it can be handled later on - but as it affects
how optional is handled, it might make sense to handle
it already with this patch.

In any case, I wanted to mention it.

* * *

Subject: [PATCH] fortran: support .NIL. in conditional arguments

This patch introduces support for the NIL value within a conditional arguments,
addressing several scenarios for how NIL should be handled.

I think  NIL  should be  .NIL.
Otherwise, this does not really add anything to the subject line,
but it is also not really wrong. (Keep it or remove it as you like.)

* * *

BTW: If you think it should be best handled incrementally, one option
would be to add a 'sorry' error – and handle those later.

I think we need a sorry for code that is known to produce
invalid code. - For bogus errors (like 'call sub((c ? allocvar:allocvar2))'
not being rejected as not allocatable), it is not strictly required.
Reason: While the error is bogus, it does not compile and, hence,
there is no issue with silently generating wrong code.

If you go for that route, I think we want to have a sorry
for the FIXME issues in expr-1 and those in expr-3. And for
the code in 'conv_dummy_value', I think a comment would be good
why that's called for conditional expr, possibly with a FIXME
about the missing bits.

Tobias
! { dg-do run }
! { dg-additional-options "-g" }

module m
implicit none (type, external)

logical :: is_present
logical :: is_allocated
integer :: has_value

contains

subroutine test(a,c, cond)
  integer, allocatable :: a,c
  logical, value :: cond
  call sub(( cond ? a : c))
  call sub_val(( cond ? a : c))
  !call sub_alloc(( cond ? a : c))  ! -> BOGUS ERROR: Actual argument for 'x' must be ALLOCATABLE
end

subroutine test2(a,c, cond)
  integer, allocatable, optional :: a,c
  logical, value :: cond
  call sub(( cond ? a : c))
  call sub_val(( cond ? a : c))
  ! call sub_alloc(( cond ? a : c))  ! -> BOGUS ERROR: Actual argument for 'x' must be ALLOCATABLE
end

subroutine test_nil(a, cond)
  integer, allocatable :: a
  logical, value :: cond
  call sub(( cond ? a : .nil.))
  call sub(( .not.cond ? .nil. : a))
  call sub_val(( cond ? a : .nil.))
  call sub_val(( .not.cond ? .nil. : a))
!  call sub_alloc(( cond ? a : .nil.))  ! -> BOGUS ERROR: Actual argument for 'x' must be ALLOCATABLE
!  call sub_alloc(( .not.cond ? .nil. : a)) ! Likewise
end

subroutine test2_nil(a, cond)
  integer, allocatable, optional :: a
  logical, value :: cond
  call sub(( cond ? a : .nil.))
  call sub(( .not.cond ? .nil. : a))
  call sub_val(( cond ? a : .nil.))
  call sub_val(( .not.cond ? .nil. : a))
  !call sub_alloc(( cond ? a : .nil.))  ! -> BOGUS ERROR: Actual argument for 'x' must be ALLOCATABLE
  !call sub_alloc(( .not.cond ? .nil. : a)) ! Likewise
end

subroutine sub(x)
  integer, optional :: x
  if (present(x) .neqv. (is_present .and. is_allocated)) error stop
  if (present(x)) then
    if (x /= has_value) error stop
  end if
!  print *, present(x)
!  if (present(x)) print *, x
end

subroutine sub_val(x)
  integer, optional, value :: x
  if (present(x) .neqv. (is_present .and. is_allocated)) error stop
  if (present(x)) then
    if (x /= has_value) error stop
  end if
!  print *, present(x)
!  if (present(x)) print *, x
end

subroutine sub_alloc(x)
  integer, allocatable, optional :: x
  if (present(x) .neqv. is_present) error stop
  if (present(x)) then
    if (allocated(x) .neqv. is_allocated) error stop
    if (allocated(x)) then
      if (x /= has_value) error stop
    end if
  end if
!  print *, present(x)
!  if (present(x)) print *, x
end

end

use m
implicit none (type, external)
integer, allocatable :: b,d

is_allocated = .false.
is_present = .true.
call test(b, d, .true.)
call test(b, d, .false.)
call test_nil(b, .true.)
call test_nil(b, .false.)

is_present = .true.
call test2(b, cond=.true.)
call test2_nil(b, cond=.true.)
!call test2(c=d, cond=.false.)  ! FIXME: Segfaults

is_present = .false.
!call test2(b, cond=.false.)  ! FIXME: Segfaults
!call test2(c=d, cond=.true.)  ! FIXME: Segfaults
call test2_nil(b, cond=.false.)
!call test2_nil(cond=.true.)  ! FIXME: Segfaults
!call test2_nil(cond=.false.)  ! FIXME: Segfaults

b = 4
is_present = .true.
is_allocated = .true.
has_value = 4
call test(b, d, .true.)
call test2(b, d, cond=.true.)
call test2(b, cond=.true.)
is_allocated = .false.
call test(b, d, .false.)
call test2(b, d, cond=.false.)
!call test2(b, cond=.false.)  ! FIXME: Segfaults

is_present = .false.
!call test2(c=b, cond=.true.)  ! FIXME: Segfaults
is_present = .true.
is_allocated = .true.
!call test2(c=b, cond=.false.)  ! FIXME: Segfaults
deallocate(b)

d = 5
is_present = .true.
is_allocated = .false.
call test(b, d, .true.)
call test2(b, d, cond=.true.)
is_allocated = .true.; has_value = 5
call test(b, d, .false.)
call test2(b, d, cond=.false.)
b = 6
is_allocated = .true.; has_value = 6
call test(b, d, .true.)
call test2(b, d, cond=.true.)
is_allocated = .true.; has_value = 5
call test(b, d, .false.)
call test2(b, d, cond=.false.)

is_allocated = .true.; has_value = 6
call test_nil(b, .true.)
call test2_nil(b, cond=.true.)
is_present = .false.
call test_nil(b, .false.)
call test2_nil(b, cond=.false.)

deallocate(b,d)
end
! { dg-do run }
! { dg-additional-options "-g" }

module m
implicit none (type, external)

logical :: is_present
character(1), pointer :: has_value1 => null()
character(:), pointer :: has_value => null()

contains

subroutine test(a,c,a1,c1, cond)
  character(:), allocatable :: a,c
  character(1), allocatable :: a1,c1
  logical, value :: cond
  call sub(( cond ? a : c))
  call sub_val(( cond ? a1 : c1))
end

subroutine test2(a,c,a1,c1, cond)
  character(:), allocatable, optional :: a,c
  character(1), allocatable, optional :: a1,c1
  logical, value :: cond
  call sub(( cond ? a : c))
  call sub_val(( cond ? a1 : c1))
end

subroutine test_nil(a,a1, cond)
  character(:), allocatable :: a
  character(1), allocatable :: a1
  logical, value :: cond
  call sub(( cond ? a : .nil.))
  call sub_val(( cond ? a1 : .nil.))
  call sub(( .not.cond ? .nil. : a))
  call sub_val(( .not.cond ? .nil. : a1))
end

subroutine test2_nil(a,a1, cond)
  character(:), allocatable, optional :: a
  character(1), allocatable, optional :: a1
  logical, value :: cond
  call sub(( cond ? a : .nil.))
  call sub_val(( cond ? a1 : .nil.))
  call sub(( .not.cond ? .nil. : a))
  call sub_val(( .not.cond ? .nil. : a1))
end


subroutine sub(x)
  character(*), optional :: x
  if (present(x) .neqv. is_present) error stop
  if (present(x)) then
    if (len(x) /= len(has_value)) error stop
    if (x /= has_value) error stop
  end if
!  print *, present(x)
!  if (present(x)) print *, x
end

subroutine sub_Val(x)
  character(1), optional, value :: x
  if (present(x) .neqv. is_present) error stop
  if (present(x)) then
    if (x /= has_value1) error stop
  end if
!  print *, present(x)
!  if (present(x)) print *, x
end
end

program main
use m
implicit none (type, external)

character(:), allocatable, target :: b,d
character(1), allocatable, target :: b1,d1

is_present = .false.
call test(b, d, b1, d1, .true.)
call test(b, d, b1, d1, .false.)
call test_nil(b, b1, cond=.true.)
call test_nil(b, b1, cond=.false.)

call test2(b, a1=b1, cond=.true.)
!call test2(b1, a1=b1, cond=.false.)
!call test2(c=d, c1=d1, cond=.true.)
!call test2(c=d, c1=d1, cond=.false.)
call test2_nil(b, a1=b1, cond=.true.)
call test2_nil(b, a1=b1, cond=.false.)
!call test2_nil(cond=.true.)
!call test2_nil(cond=.false.)

b = "abc"; has_value => b
b1 = "a"; has_value1 => b1
is_present = .true.; 
call test(b, d, b1, d1, .true.)
call test2(b, d, b1, d1, cond=.true.)
call test2(b, a1=b1, cond=.true.)
is_present = .false.
call test(b, d, b1, d1, .false.)
call test2(b, d, b1, d1, cond=.false.)
!call test2(b, a1=b1, cond=.false.)
!call test2(c=b, c1=b1, cond=.true.)
!call test2(c=b, c1=b1, cond=.false.)
deallocate(b,b1)

d = "12345"; has_value => d
d1 = "1"; has_value1 => d1
is_present = .false.
call test(b, d, b1, d1, .true.)
call test2(b, d, b1, d1, cond=.true.)
is_present = .true.
call test(b, d, b1, d1, .false.)
call test2(b, d, b1, d1, cond=.false.)

b = "abq"; b1 = "q"
is_present = .true.; has_value => b; has_value1 => b1
call test(b, d, b1, d1, .true.)
call test2(b, d, b1, d1, cond=.true.)
is_present = .true.; has_value => d; has_value1 => d1
call test(b, d, b1, d1, .false.)
call test2(b, d, b1, d1, cond=.false.)

is_present = .true.; has_value =>b; has_value1 => b1
call test_nil(b, a1=b1, cond=.true.)
call test2_nil(b, a1=b1, cond=.true.)
is_present = .false.
call test_nil(b, b1, .false.)
call test2_nil(b, b1, cond=.false.)

has_value => null()
has_value1 => null()
deallocate(b,d,b1,d1)
end
module m
  implicit none (type,external)
  logical :: is_present
  integer :: has_value
contains
subroutine test(a,c, cond)
  integer, value, optional :: a,c
  logical, value :: cond
  call sub(( cond ? a : c))
  call sub_val(( cond ? a : c))
end

subroutine test_nil(a, cond)
  integer, value, optional :: a
  logical, value :: cond
  call sub(( cond ? a : .nil.))
  call sub(( .not.cond ? .nil. : a))
  call sub_val(( cond ? a : .nil.))
  call sub_val(( .not.cond ? .nil. : a))
end

subroutine sub(x)
  integer, optional :: x
!  print *, present(x)
!  if (present(x)) print *, x
  if (present(x) .neqv. is_present) error stop
  if (present(x)) then
    if (x /= has_value) error stop
  end if
end

subroutine sub_val(x)
  integer, optional, value :: x
!  print *, present(x)
!  if (present(x)) print *, x
  if (present(x) .neqv. is_present) error stop
  if (present(x)) then
    if (x /= has_value) error stop
  end if
end
end module

use m
implicit none (type,external)

is_present = .false.        ! The following items fail with an 'error stop' for the present check
!call test(cond=.true.) - Wrongly claims that 'a' is present
!call test(cond=.false.) - Wrongly claims that 'c' is present
!call test_nil(cond=.true.) - Wrongly claims that 'a' is present
call test_nil(cond=.false.) ! OK

is_present = .true.; has_value = 2
call test_nil(2, cond=.true.) ! OK
call test (2, cond=.true.) ! OK
call test (c=2, cond=.false.) ! OK

is_present = .false.
call test_nil(cond=.false.) ! OK
!call test (c=4, cond=.true.) ! - Wrongly claims that 'a' is present
!call test (4, cond=.false.) !  - Wrongly claims that 'c' is present

end

Reply via email to