Is duplicate construct names being allowed an extension or bug?

The Fortran 2023 standard states that a construct name must be unique within a scoping unit. That is, a construct name for any nameable construct (BLOCK, DO, IF, WHERE, SELECT CASE, …) must be unique within a scoping unit.

Comparing compilers with the playground shows Lfortran allows names to be non-unique. The attached program has two SELECT CASE constructs of the same name within a BLOCK construct of that name. It runs using Lfortran.

Should this be considered an extension or a bug?

open demo_select_case in Fortran Playground

source of xx
program demo_select_case
   implicit none
   TESTIT: block
      integer :: n
      n = 4
      TESTIT: select case (n)
       case (1, 3:5, 8) TESTIT ! selects 1, 3, 4, 5, 8
         write(*,*)'case A',n
       case (100:,:0) TESTIT
         write(*,*)'case B',n,'out of range'
       case default TESTIT
         write(*,*)'default',n
      end select TESTIT

      TESTIT: select case (n)
       case (1, 3:5, 8) TESTIT ! selects 1, 3, 4, 5, 8
         write(*,*)'case A',n
       case (100:,:0) TESTIT
         write(*,*)'case B',n,'out of range'
       case default TESTIT
         write(*,*)'default',n
      end select TESTIT

   end block TESTIT
end program demo_select_case

One additional clause matters here: J3/23-007r1, §4.2(6) requires a processor to have the capability to detect and report name usage that violates the scope rules in Clause 19. That is stronger than just saying the example is nonconforming, but it does not require rejection in every invocation.

§19.3.1 places named constructs in the same class of local identifiers. The two SELECT CASE names collide within the block’s scoping unit, independently of the outer BLOCK name.

So acceptance in the playground alone does not establish an intentional extension. The useful next check is whether LFortran can diagnose the collision with the relevant conformance options, recording its version and flags. I have checked the draft standard, but have not run LFortran for this example.

1 Like

It’s probably a bug in LFortran, that we haven’t implemented a check for this.

Is there agreement that this should be an error?

After considering the additional sections referenced above and not being able to think of how to take advantage of the behavior (could not think of anything significant it would allow me to do that expected behavior did not) and that it can reduce the readability (and one purpose of labeling is to make the code more readable); combined with the fact the other compilers flagged it un-allowed I would go with treating it as a bug. The behavior as-is has appeared “rational” during testing in that an EXIT using the label exits the innermost construct with that name but requiring it to be unique forces a better programming practice and ensures code is more readable in my opinion. Thanks for taking note of the issue!

PS:
I have run across older code where COMMON blocks and variable names and procedures and one construct could share a name. That is non-standard (maybe, there was a good discussion about COMMON blocks and variables sharing a name in this Discourse). So I know of nothing else where multiple constructs in the same scope have been allowed to share a name, but if the rule is carried to those others it might break some old code. Something unusual where duplicate names are allowed that I have seen is if you import two modules that both contain a procedure of the same name but are distinguishable that they are allowed to be referenced just like they had been specified in a generic interface. That is actually useful but I am not certain it is standard. And of course there is overloading but that is clearly defined (well, if you can call reading the standard “clear” :wink: ).

While you are at it …

Well, none of the compilers tested allow that “automatic generic interface”
behavior anymore; but Lfortran gave an ICE instead of an error message. I
showed the error messages from some of the other compilers as they vary
widely and some seem much clearer than others as examples of how you
might like Lfortran to behave.

open demo_duplicates in Fortran Playground

+aoccflang520         :     2	F90-S-0188-Argument number 1 to rabbit: type mismatch (/app/example.f90: 37)
!----------------------------------------------------------------------------------------------------------
+flangtrunk           :     2	error: Semantic errors in /app/example.f90
+flangtrunk           :     3	/app/example.f90:32:9: error: Reference to 'rabbit' is ambiguous
+flangtrunk           :     4	     call rabbit(x)
+flangtrunk           :     5	          ^^^^^^
+flangtrunk           :     6	/app/example.f90:21:5: 'rabbit' was use-associated from module 'a'
+flangtrunk           :     7	  use A
+flangtrunk           :     8	      ^
+flangtrunk           :     9	/app/example.f90:22:5: 'rabbit' was use-associated from module 'b'
+flangtrunk           :    10	  use B
+flangtrunk           :    11	      ^
+flangtrunk           :    12	/app/example.f90:37:9: error: Reference to 'rabbit' is ambiguous
+flangtrunk           :    13	     call rabbit(i)
+flangtrunk           :    14	          ^^^^^^
+flangtrunk           :    15	/app/example.f90:21:5: 'rabbit' was use-associated from module 'a'
+flangtrunk           :    16	  use A
+flangtrunk           :    17	      ^
+flangtrunk           :    18	/app/example.f90:22:5: 'rabbit' was use-associated from module 'b'
+flangtrunk           :    19	  use B
+flangtrunk           :    20	      ^
!+---------------------------------------------------------------------------------------------------------
+ifxlatest            :     2	/app/example.f90(32): error #6405: The same named entity from different modules and/or program units cannot be referenced.   [RABBIT]
+ifxlatest            :     3	   call rabbit(x)
+ifxlatest            :     4	--------^
!+---------------------------------------------------------------------------------------------------------
-lfortranlatest       :     3	Program terminated with signal SIGSEGV (11)
!+---------------------------------------------------------------------------------------------------------
+nvfortran_x86_26_3   :     2	NVFORTRAN-S-0155-rabbit is use-associated from modules b and a, and cannot be accessed (/app/example.f90: 32)
+nvfortran_x86_26_3   :     3	NVFORTRAN-S-0155-rabbit is use-associated from modules b and a, and cannot be accessed (/app/example.f90: 37)
+nvfortran_x86_26_3   :     4	NVFORTRAN-S-0188-Argument number 1 to rabbit: type mismatch (/app/example.f90: 37)
!+---------------------------------------------------------------------------------------------------------
+gfortran161          :     2	/app/example.f90:32:15:
+gfortran161          :     3	
+gfortran161          :     4	   32 |    call rabbit(x)
+gfortran161          :     5	      |               1
+gfortran161          :     6	Error: Name 'rabbit' at (1) is an ambiguous reference to 'rabbit' from module 'a'

At least one compiler used to act like the following if there were duplicates
because I accidently used the “feature” and was unaware of it till I tried
another compiler
I
So you do now have to give duplicate procedures unique names or explicitly
create the generic interface everywhere I tried so I guess it was not standard.

Fortran source

...
module A
implicit none
contains
   subroutine rabbit(value)
      integer :: value
      value=value+1
   end subroutine rabbit
end module A

module B
implicit none
contains
   subroutine rabbit(value)
      real    :: value
      value=value*value
   end subroutine rabbit
end module B

program demo_duplicates
#ifdef CRASH1
! this is non-standard even though the procedures do not
! conflict (currently) 
use A, only : rabbit
use B, only : rabbit
#else
! renaming them uniquely and optionally creating a 
! generic interface with the unique names works
use A, only : a_wabbit       => rabbit
use B, only : waskily_wabbit => rabbit
implicit none
interface rabbit
   procedure a_wabbit
   procedure waskily_wabbit
end interface rabbit
#endif

   real       :: x
   integer    ::i

   x=4.4
   i=5

   write(*,*)x

   call rabbit(x)
   write(*,*)x

   write(*,*)i

   call rabbit(i)
   write(*,*)i

end program demo_duplicates

reminds me why I avoid using list-directed output in production code …

+aoccflang520         :     2	    4.400000    
+aoccflang520         :     3	    19.36000    
+aoccflang520         :     4	            5
+aoccflang520         :     5	            6
+flangtrunk           :     2	 4.4
+flangtrunk           :     3	 19.36
+flangtrunk           :     4	 5
+flangtrunk           :     5	 6
+ifxlatest            :     2	   4.400000    
+ifxlatest            :     3	   19.36000    
+ifxlatest            :     4	           5
+ifxlatest            :     5	           6
+ifxlatest_standard   :     2	 4.400000
+ifxlatest_standard   :     3	 19.36000
+ifxlatest_standard   :     4	 5
+ifxlatest_standard   :     5	 6
+lfortranlatest       :     2	 4.40000010
+lfortranlatest       :     3	 19.3600006
+lfortranlatest       :     4	 5
+lfortranlatest       :     5	 6
+nvfortran_x86_26_3   :     2	    4.400000    
+nvfortran_x86_26_3   :     3	    19.36000    
+nvfortran_x86_26_3   :     4	            5
+nvfortran_x86_26_3   :     5	            6
+gfortran161          :     2	   4.40000010    
+gfortran161          :     3	   19.3600006    
+gfortran161          :     4	           5
+gfortran161          :     5	           6

So looks like there are two bug reports:

Anything else?

I have a few others but I will report them via github once I get my ID working again. You got the ones I reported before I broke my ID.. Very responsive. Looking better and better. I need to install the most recent version to avoid submitting redundant ones you have been going so fast and I have not had the time, but last I
checked running “make -k lfortran” in general-purpose-fortran still hit a few but no compiler has built that without me having to add work-arounds.

Well, add this one too, as it is also related I think:

You should be able to create two independent
modules that are duplicates of each other except for type and not have
them collide if both overload the names. So adding the
INTERFACE lines in each module lets you use both modules.

That is, overloading is a legitimate way to create generic interfaces without
the modules having to know about each others existence unless used in the same scope. And then they will coexist unless they conflict “automatically”.

So declaring all public procedure names in rather redundant-looking
interfaces they can be used with other modules using the same names without renaming as
long as they are distinguishable from one another.

This is what happened automatically with one compiler in the past and accidentally became a “feature” I was using, but now to get that behavior
you have to add the overload syntax.

Using USE with ONLY clauses and renaming things is probably a better
practice, but this use of overloading can make it very easy to generate modules supporting
different types by using INCLUDE and cpp directives judicially.

Anyway, this should be legal and is related to the previous issue:

open wab2 in Fortran Playground

Fortran source

click to see more...
module A
implicit none
interface rabbit; module procedure rabbit; end interface rabbit
contains
   subroutine rabbit(value)
      integer :: value
      value=value+1
   end subroutine rabbit
end module A

module B
implicit none
interface rabbit; module procedure rabbit; end interface rabbit
contains
   subroutine rabbit(value)
      real    :: value
      value=value*value
   end subroutine rabbit
end module B

module gestalt
use A, only : rabbit
use B, only : rabbit
end module gestalt

program demo_duplicates
use A, only : rabbit
use B, only : rabbit

   real       :: x
   integer    ::i

   x=4.4
   i=5

   write(*,*)x

   call rabbit(x)
   write(*,*)x

   write(*,*)i

   call rabbit(i)
   write(*,*)i

end program demo_duplicates

Output

click to see more...
+aoccflang520         :     2	    4.400000    
+aoccflang520         :     3	    19.36000    
+aoccflang520         :     4	            5
+aoccflang520         :     5	            6
+flangtrunk           :     2	 4.4
+flangtrunk           :     3	 19.36
+flangtrunk           :     4	 5
+flangtrunk           :     5	 6
+ifxlatest            :     2	   4.400000    
+ifxlatest            :     3	   19.36000    
+ifxlatest            :     4	           5
+ifxlatest            :     5	           6
+ifxlatest_standard   :     2	 4.400000
+ifxlatest_standard   :     3	 19.36000
+ifxlatest_standard   :     4	 5
+ifxlatest_standard   :     5	 6
+++ cexpl -c lfortranlatest --skip-asm '--cflags=--fixed-form-infer --no-style-suggestions --realloc-lhs-arrays --print-leading-space --std f23 --use-loop-variable-after-loop --cpp -DCOMPILER_=lfortranlatest' wab2.f90
=lfortranlatest       :     3	semantic error: Arguments do not match for any generic procedure, rabbit
-lfortranlatest       :     4	  --> /app/example.f90:33:4
-lfortranlatest       :     5	   |
-lfortranlatest       :     6	33 |    call rabbit(x)
-lfortranlatest       :     7	   |    ^^^^^^^^^^^^^^ 
+nvfortran_x86_26_3   :     2	    4.400000    
+nvfortran_x86_26_3   :     3	    19.36000    
+nvfortran_x86_26_3   :     4	            5
+nvfortran_x86_26_3   :     5	            6
+gfortran161          :     2	   4.40000010    
+gfortran161          :     3	   19.3600006    
+gfortran161          :     4	           5
+gfortran161          :     5	           6

If wishes were fishes …

If Lfortran were built into Discourse so you could just click on the code snippets
and run them locally that would be nice.