[clang] [flang] [llvm] [flang] Diagnose and optionally repair missing MODULE procedure prefixes (PR #220783)

Eugene Epshteyn via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 2 07:28:11 PDT 2026


================
@@ -5933,6 +5941,18 @@ bool SubprogramVisitor::BeginSubprogram(const parser::Name &name,
         EraseSymbol(name);
       }
     }
+  } else if (isValid && !inInterfaceBlock() && currScope().IsSubmodule() &&
+      context().IsEnabled(common::LanguageFeature::ImplicitModulePrefix) &&
+      (moduleInterface = FindSeparateModuleProcedureInterface(
+           name, /*emitError=*/false))) {
+    // As with the missing-prefix diagnostic below, imported parent scopes
+    // must not suppress a warning for a definition in the current source.
+    context().messages().Warn(/*isInModuleFile=*/InModuleFile(),
+        context().languageFeatures(),
+        common::LanguageFeature::ImplicitModulePrefix, name.source,
+        "Assuming a missing MODULE prefix on '%s' to repair the separate module procedure interface '%s:%s'"_port_en_US,
+        name.source, moduleInterface->owner().GetName().value(),
+        moduleInterface->name());
   }
----------------
eugeneepshteyn wrote:

The `InModuleFile()` check here gates only the warning. The binding itself (`moduleInterface` → `set_moduleInterface` → `MODULE`) still runs while re-resolving a `.mod`. So a translation unit compiled with `-fimplicit-module-prefix` reinterprets a submodule `.mod` produced without that option. In that `.mod` the unprefixed procedure is a local procedure (F2023 19.5.1.4 p2), and the reinterpretation silently rebinds it to the interface:

```fortran
! m.f90
module m
  interface
    module subroutine helper
    end subroutine
    module subroutine run
    end subroutine
  end interface
end module
! sm1.f90 -- compiled WITHOUT the flag
submodule(m) sm1
contains
  subroutine helper ! no MODULE prefix: a local procedure of sm1
    print *, 'sm1 local helper'
  end subroutine
end submodule
! sm2.f90
submodule(m) sm2
contains
  module subroutine helper
    print *, 'm:helper'
  end subroutine
end submodule
! sm3.f90
submodule(m:sm1) sm3
contains
  module subroutine run
    call helper ! sm1's local procedure, by host association
  end subroutine
end submodule
! main.f90
program main
  use m
  call run
end program
```

```console
$ flang -c m.f90 && flang -c sm1.f90 && flang -c sm2.f90 && flang -c main.f90
$ flang -c sm3.f90 && flang m.o sm1.o sm2.o sm3.o main.o && ./a.out
 sm1 local helper
$ flang -c -fimplicit-module-prefix -Wimplicit-module-prefix -pedantic sm3.f90 && flang m.o sm1.o sm2.o sm3.o main.o && ./a.out
 m:helper
```

The second compile of `sm3.f90` prints nothing, yet the program now calls a different procedure. Without `sm2.o` in the link, it fails instead with an undefined reference to `_QMmPhelper`.

A `.mod` produced *with* the flag already records `module subroutine helper()`, so module-file content never needs the repair, and skipping it there is enough. Moving the owner check into the branch also stops a repair warning from appearing next to the existing "already declared" error when the interface is declared in the same submodule:

```suggestion
  } else if (isValid && !inInterfaceBlock() && !InModuleFile() &&
      currScope().IsSubmodule() &&
      context().IsEnabled(common::LanguageFeature::ImplicitModulePrefix)) {
    // Repair only definitions in the current source: a module file already
    // records whether its producer treated the subprogram as MODULE.
    if (Symbol *iface{FindSeparateModuleProcedureInterface(
            name, /*emitError=*/false)};
        iface && &iface->owner() != &currScope()) {
      moduleInterface = iface;
      context().messages().Warn(/*isInModuleFile=*/false,
          context().languageFeatures(),
          common::LanguageFeature::ImplicitModulePrefix, name.source,
          "Assuming a missing MODULE prefix on '%s' to repair the separate module procedure interface '%s:%s'"_port_en_US,
          name.source, moduleInterface->owner().GetName().value(),
          moduleInterface->name());
    }
  }
```

With this change the example keeps calling `_QMmSsm1Phelper`, and `check-flang` results are unchanged for me. A split-file test that needs no linking would pin this: compile `sm1.f90` without the flag, then check `-fimplicit-module-prefix -emit-hlfir sm3.f90` for `fir.call @_QMmSsm1Phelper`. The existing IMPORT RUN line in `implicit-module-prefix-modfile.f90` covers only the case where the producer was compiled with the flag.


https://github.com/llvm/llvm-project/pull/220783


More information about the cfe-commits mailing list