llvm / llvm/llvm-project

[flang] Semantic warnings are silently suppressed in separately-compiled submodules

Open
#223,457 1 comment 0 reactions 0 assignees View on GitHub
flang:frontend
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Semantic warnings emitted through `SemanticsContext::Warn` are silently dropped for source that lives in a **separately-compiled submodule**. The same code warns normally when the submodule and its ancestor module are compiled together, so the suppression depends on the compilation model rather than on the code.

### Reproducer

```fortran
! a.f90
module m
implicit none
interface
module subroutine foo
end subroutine
end interface
end module
```

```fortran
! b.f90
submodule (m) sm
contains
module subroutine foo
type :: empty_seq
sequence
end type
type(empty_seq) :: v
print *, 1
end subroutine
end submodule
```

```
mkdir -p d
flang -fc1 -fsyntax-only -module-dir d a.f90
flang -fc1 -fsyntax-only -module-dir d b.f90
```

**Actual:** no diagnostics at all for `b.f90`.

**Expected:** `-Wempty-sequence-type` for `empty_seq`, and `-Wunused-variable` for `v`. Both are enabled by default.

**Control** — concatenating the two units into one file and compiling that reports both, as it should:

```
cat a.f90 b.f90 > same.f90
flang -fc1 -fsyntax-only -module-dir d2 same.f90
```
```
same.f90:11:5: warning: A sequence type should have at least one component [-Wempty-sequence-type]
same.f90:14:24: warning: Value of local variable 'v' is never used [-Wunused-variable]
```

### Cause

Every `SemanticsContext::Warn` overload forwards `IsInModuleFile(at)`, and `SemanticsContext::IsInModuleFile` walks **scope ancestry** from `FindScope(source)` up to any scope flagged `IsModuleFile()`:

```cpp
bool SemanticsContext::IsInModuleFile(parser::CharBlock source) const {
for (const Scope *scope{&FindScope(source)}; !scope->IsGlobal();
scope = &scope->parent()) {
if (scope->IsModuleFile()) {
return true;
}
}
...
```

When a submodule is compiled separately, its ancestor module's scope is materialized by reading the `.mod`, so that scope carries `IsModuleFile()`. The submodule's own scope is a descendant of it, and the ancestry walk therefore reports "in a module file" for a location that is in the user's own source file. Every warning raised through `SemanticsContext::Warn` under that scope is dropped.

This is location-based suppression being used to answer a phase question. The predicate that actually means "I am currently resolving module-file content" is `BaseVisitor::InModuleFile()` in `flang/lib/Semantics/resolve-names.cpp`, which is exactly `GetFoldingContext().moduleFileName().has_value()` — and `moduleFileName` is set only around a module-file read in `flang/lib/Semantics/mod-file.cpp`.

### This is already known in one place

`flang/lib/Semantics/resolve-names.cpp` contains one diagnostic that works around this by hand, with a comment naming the problem:

```cpp
// Use the low-level Warn() call to avoid module-file suppression
// based on scope ancestry; InModuleFile() provides the appropriate
// check here.
context().messages().Warn(/*isInModuleFile=*/InModuleFile(),
context().languageFeatures(), common::UsageWarning::Portability,
name.source,
"Subprogram '%s' in this submodule is missing the MODULE prefix ...
```

The contrast is visible in a single compile. Dropping the `MODULE` prefix from `foo` in `b.f90` above and compiling with `-pedantic` reports the portability warning — which uses that hand-rolled bypass — while `-Wempty-sequence-type` and `-Wunused-variable`, raised in the very same scope during the very same compile, are still suppressed.

So today the workaround has to be repeated at every call site that wants to be correct inside a submodule, and every site that does not know about it is silently wrong.

### Suggested direction

Either:

1. make the suppression ask whether the *location* is module-file source, rather than whether any enclosing scope came from a module file; or
2. have `SemanticsContext::Warn` consult the resolution-phase state (the `moduleFileName()` predicate above) instead of scope ancestry.

Option 2 matches what the existing workaround does and would let that site drop its hand-rolled call.

### Notes

- Not limited to the two diagnostics above; they are simply easy to trigger. Any `SemanticsContext::Warn` under a separately-compiled submodule is affected.
- Parser-layer diagnostics are unaffected, because they do not pass through this gate — e.g. `nonstandard usage: TYPE*KIND syntax` is still reported in the same submodule.
- Semantic **errors** are unaffected; only warnings go through the suppression path.
- Surfaced while reviewing #220779, whose new diagnostic is invisible in separately-compiled submodules for this reason. That PR is not the cause and does not need to fix this.

### Version

Reproduced with `flang` built from `main` at `d79ce87f19d70db353b96b3f0e5f40b85cf99b14` (the two-warning reproducer above) and again at `33578bd6698a455c2faef2c5a5a9652f621ec9d5` (which also shows the single-compile contrast, since the hand-rolled bypass site landed between those two revisions). x86_64 Linux, assertions enabled.

(This issue was filed with assistance of AI.)

Contributor guide

Open the contributing guide

Research direction

Start with SemanticsContext::Warn and IsInModuleFile, then compare their suppression logic with BaseVisitor::InModuleFile() in flang/lib/Semantics/resolve-names.cpp and moduleFileName handling in flang/lib/Semantics/mod-file.cpp. Use the a.f90 and b.f90 reproducer with separate -fsyntax-only compilations, and compare it with the concatenated control case. Done means semantic warnings appear for separately compiled submodules without suppressing diagnostics from actual module-file reads.

Written by the indexing model from the issue text.

Assessment

Tech stack
fortran
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.