[Flang] Interface vs explicit intrinsics priority
Project: llvm/llvm-project | Language: C++ | Concepts: api-design, refactoring, testing-strategy | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/215355
The problem
In Fortran, when both an explicit intrinsic and a user-defined interface (i.e., a specific procedure) are available for a generic name (like sin), the compiler must choose which one to use. The previous implementation in Flang incorrectly prioritized explicit intrinsics over user-defined interface specifics, leading to the wrong procedure being called in some cases.
Why it's non-trivial
A naive fix might simply reorder the checks, but getting the resolution order correct requires careful handling of all possible combinations (elemental, non-elemental, intrinsic, etc.), and ensuring that all branches of the logic are covered and tested. The Fortran standard's rules are subtle, and missing a case could introduce subtle bugs or regressions.
The approach
The core change is in ExpressionAnalyzer::ResolveGeneric. The logic that determines which procedure to resolve for a generic name was updated to give priority to user-defined specifics (both non-elemental and elemental) over explicit intrinsics.
Key code snippets:
-
Remove the check that skipped user-defined specifics if an explicit intrinsic was present:
- if (genericDetails && !explicitIntrinsic) { + if (genericDetails) {Annotation:
Previously, if an explicit intrinsic was present, the code would skip looking for user-defined specifics. The new code always checks for specifics if they exist, regardless of whether an explicit intrinsic is present. -
Reorder the resolution priority:
- if (explicitIntrinsic) { - return {explicitIntrinsic, false}; - } else if (nonElemental) { + if (nonElemental) { return {&AccessSpecific(symbol, *nonElemental), false}; } else if (elemental) { return {&AccessSpecific(symbol, *elemental), false}; + } else if (explicitIntrinsic) { + return {explicitIntrinsic, false}; }Annotation:
The new order is:- Non-elemental user-defined specific
- Elemental user-defined specific
- Explicit intrinsic
This matches the Fortran standard's intended resolution order.
-
Expanded test coverage:
A new test file,
symbol34.f90, was added to verify the new resolution order in various scenarios:!DEF: /MAIN/sin ELEMENTAL, INTRINSIC, PURE (Function) Generic intrinsic :: sin interface sin procedure :: internal_definition_sin_real4 end interface ... a = sin(1.0) ! Should call internal_definition_sin_real4, not the intrinsic b = sin(1.0_8) ! Should fall back to intrinsic for kind=8Annotation:
The test ensures that user-defined specifics are chosen over intrinsics when available, and that the fallback to the intrinsic works when no matching specific exists.
From the review thread
LGTM overall.
One ask before I approve — test coverage. symbol34.f90 currently pins the nonelemental-function case and the kind-8 intrinsic fallthrough, but not the other resolution legs this PR changes. Could you extend it with these cases?
An elemental specific beating the intrinsic (15.5.5.2 step (2) ahead of step (3)):
program p intrinsic :: sin interface sin procedure :: mysin end interface print *, sin(1.0) ! must call mysin (prints 101.0), not the intrinsic contains elemental real function mysin(x) real, intent(in) :: x mysin = x + 100. end function end programA subroutine generic — the same rules apply to subroutines, and no in-tree test pins that leg today:
program p intrinsic :: cpu_time interface cpu_time procedure :: mytime end inte
Takeaways
- Always follow language specification precedence rules when resolving overloaded or generic symbols, especially when both user-defined and intrinsic options exist.
- Comprehensive test coverage should include all resolution branches, not just the most common or obvious cases.
- When changing symbol resolution logic, ensure that fallback behavior (e.g., to intrinsics when no specific matches) is still correct and tested.