Files
kwyatt-extandKevin Wyatt 5c02f7530c [flang] Enumeration Type: (PR 2/5) Name Resolution + Expression + Relational + SELECT CASE (#193028)
This PR completes the addition of enumeration types to the symbol table.
It adds core semantic analysis for ENUMERATION TYPE, name resolution,
expression analysis for constructors and relational operators, and
SELECT CASE support. Also includes a bug fix for non-constant
enumeration comparisons in Relate() (scope() → GetScope()).

It is the 2nd of 5 stacked PRs.

AI Usage Disclosure: AI tools (Claude Opus 4.6) were used to assist with
implementation of this feature and test code generation. I have
reviewed, modified, and tested all AI-generated code.

---------

Co-authored-by: Kevin Wyatt <kwyatt@hpe.com>
2026-06-30 12:24:57 -05:00

357 lines
14 KiB
C++

//===-- lib/Support/Fortran-features.cpp ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "flang/Support/Fortran-features.h"
#include "flang/Common/idioms.h"
#include "flang/Parser/characters.h"
#include "flang/Support/Fortran.h"
#include "llvm/ADT/StringRef.h"
#include <string>
#include <string_view>
namespace Fortran::common {
static std::vector<std::string_view> SplitCamelCase(std::string_view x) {
std::vector<std::string_view> result;
// NB, we start at 1 because the first character is never a word boundary.
size_t xSize{x.size()}, wordStart{0}, wordEnd{1};
for (; wordEnd < xSize; ++wordEnd) {
// Identify when wordEnd is at the start of a new word.
if ((!parser::IsUpperCaseLetter(x[wordEnd - 1]) &&
parser::IsUpperCaseLetter(x[wordEnd])) ||
// ACCUsage => ACC-Usage, CComment => C-Comment, etc.
(parser::IsUpperCaseLetter(x[wordEnd]) && wordEnd + 1 < xSize &&
parser::IsLowerCaseLetter(x[wordEnd + 1]))) {
result.push_back(x.substr(wordStart, wordEnd - wordStart));
wordStart = wordEnd;
}
}
// We went one past the end of the last word.
result.push_back(x.substr(wordStart, wordEnd - wordStart));
return result;
}
// Compound names whose hyphenated CamelCase splitting is wrong.
// Each entry maps the incorrect (deprecated) hyphenated form produced by
// SplitCamelCase to the correct (canonical) form.
static constexpr std::pair<std::string_view, std::string_view>
compoundNameFixups[]{{"open-mp", "openmp"}, {"open-acc", "openacc"}};
// Replace all occurrences of 'from' with 'to' in 's', but only when the
// match is at a word boundary (end of string or followed by '-') to avoid
// e.g. "open-access" -> "openaccess".
static void ReplaceAtWordBoundary(
std::string &s, std::string_view from, std::string_view to) {
for (size_t pos = s.find(from); pos != std::string::npos;
pos = s.find(from, pos + to.size())) {
size_t end = pos + from.size();
if (end == s.size() || s[end] == '-') {
s.replace(pos, from.size(), to);
}
}
}
// Namespace for helper functions for parsing Cli options used instead of static
// so that there can be unit tests for this function.
namespace details {
std::string CamelCaseToLowerCaseHyphenated(std::string_view x) {
std::vector<std::string_view> words{SplitCamelCase(x)};
std::string result{};
result.reserve(x.size() + words.size() + 1);
for (size_t i{0}; i < words.size(); ++i) {
std::string word{parser::ToLowerCaseLetters(words[i])};
result += i == 0 ? "" : "-";
result += word;
}
// Fix known compound names that should not be hyphen-separated.
for (auto [deprecated, canonical] : compoundNameFixups) {
ReplaceAtWordBoundary(result, deprecated, canonical);
}
return result;
}
} // namespace details
LanguageFeatureControl::LanguageFeatureControl() {
// Initialize the bidirectional maps with the default spellings.
cliOptions_.reserve(LanguageFeature_enumSize + UsageWarning_enumSize);
ForEachLanguageFeature([&](auto feature) {
std::string_view name{Fortran::common::EnumToString(feature)};
std::string cliOption{details::CamelCaseToLowerCaseHyphenated(name)};
cliOptions_.insert({cliOption, {feature}});
languageFeatureCliCanonicalSpelling_[EnumToInt(feature)] =
std::move(cliOption);
});
ForEachUsageWarning([&](auto warning) {
std::string_view name{Fortran::common::EnumToString(warning)};
std::string cliOption{details::CamelCaseToLowerCaseHyphenated(name)};
cliOptions_.insert({cliOption, {warning}});
usageWarningCliCanonicalSpelling_[EnumToInt(warning)] =
std::move(cliOption);
});
// Register deprecated "open-mp-*" and "open-acc-*" spellings as aliases.
// The canonical spellings are now "openmp-*" and "openacc-*".
auto makeDeprecatedSpelling = [](std::string_view canonical) {
std::string deprecated{canonical};
bool replaced{false};
for (auto [deprecatedForm, canonicalForm] : compoundNameFixups) {
// Reverse direction: canonical -> deprecated.
for (auto pos{deprecated.find(canonicalForm)}; pos != std::string::npos;
pos = deprecated.find(canonicalForm, pos + deprecatedForm.size())) {
deprecated.replace(pos, canonicalForm.size(), deprecatedForm);
replaced = true;
}
}
return std::pair{deprecated, replaced};
};
ForEachLanguageFeature([&](auto feature) {
std::string_view canonical{
languageFeatureCliCanonicalSpelling_[EnumToInt(feature)]};
auto [deprecated, replaced]{makeDeprecatedSpelling(canonical)};
if (replaced) {
AddDeprecatedCliSpelling(feature, deprecated, std::string{canonical});
}
});
ForEachUsageWarning([&](auto warning) {
std::string_view canonical{
usageWarningCliCanonicalSpelling_[EnumToInt(warning)]};
auto [deprecated, replaced]{makeDeprecatedSpelling(canonical)};
if (replaced) {
AddDeprecatedCliSpelling(warning, deprecated, std::string{canonical});
}
});
// These features must be explicitly enabled by command line options.
disable_.set(LanguageFeature::OldDebugLines);
disable_.set(LanguageFeature::OpenACC);
disable_.set(LanguageFeature::OpenMP);
disable_.set(LanguageFeature::CUDA); // !@cuf
disable_.set(LanguageFeature::CudaManaged);
disable_.set(LanguageFeature::CudaUnified);
disable_.set(LanguageFeature::CudaPinned);
disable_.set(LanguageFeature::ImplicitNoneTypeNever);
disable_.set(LanguageFeature::ImplicitNoneTypeAlways);
disable_.set(LanguageFeature::ImplicitNoneExternal);
disable_.set(LanguageFeature::DefaultSave);
disable_.set(LanguageFeature::SaveMainProgram);
disable_.set(LanguageFeature::RelaxedCLocChecks);
// F2023 ENUMERATION TYPE: experimental. Semantics are implemented, but
// FIR lowering is not, so the feature is off by default and must be
// explicitly enabled (for testing) with -fenumeration-type.
disable_.set(LanguageFeature::EnumerationType);
// These features, if enabled, conflict with valid standard usage,
// so there are disabled here by default.
disable_.set(LanguageFeature::BackslashEscapes);
disable_.set(LanguageFeature::LogicalAbbreviations);
disable_.set(LanguageFeature::XOROperator);
disable_.set(LanguageFeature::OldStyleParameter);
// Possibly an accidental "feature" of nvfortran.
disable_.set(LanguageFeature::AssumedRankPassedToNonAssumedRank);
disable_.set(LanguageFeature::Coarray);
disable_.set(LanguageFeature::OpenAccDefaultNoneScalarsStrict);
// These warnings are enabled by default, but only because they used
// to be unconditional. TODO: prune this list
warnLanguage_.set(LanguageFeature::ExponentMatchingKindParam);
warnLanguage_.set(LanguageFeature::RedundantAttribute);
warnLanguage_.set(LanguageFeature::SubroutineAndFunctionSpecifics);
warnLanguage_.set(LanguageFeature::EmptySequenceType);
warnLanguage_.set(LanguageFeature::NonSequenceCrayPointee);
warnLanguage_.set(LanguageFeature::BranchIntoConstruct);
warnLanguage_.set(LanguageFeature::BadBranchTarget);
warnLanguage_.set(LanguageFeature::HollerithPolymorphic);
warnLanguage_.set(LanguageFeature::ListDirectedSize);
warnLanguage_.set(LanguageFeature::IgnoreIrrelevantAttributes);
warnLanguage_.set(LanguageFeature::TransferBOZ);
warnLanguage_.set(LanguageFeature::AllocatedForAssociated);
warnUsage_.set(UsageWarning::ShortArrayActual);
warnUsage_.set(UsageWarning::FoldingException);
warnUsage_.set(UsageWarning::FoldingAvoidsRuntimeCrash);
warnUsage_.set(UsageWarning::FoldingValueChecks);
warnUsage_.set(UsageWarning::FoldingFailure);
warnUsage_.set(UsageWarning::FoldingLimit);
warnUsage_.set(UsageWarning::Interoperability);
// CharacterInteroperability warnings about length are off by default
warnUsage_.set(UsageWarning::Bounds);
warnUsage_.set(UsageWarning::Preprocessing);
warnUsage_.set(UsageWarning::Scanning);
warnUsage_.set(UsageWarning::OpenAccUsage);
warnUsage_.set(UsageWarning::ProcPointerCompatibility);
warnUsage_.set(UsageWarning::VoidMold);
warnUsage_.set(UsageWarning::KnownBadImplicitInterface);
warnUsage_.set(UsageWarning::EmptyCase);
warnUsage_.set(UsageWarning::CaseOverflow);
warnUsage_.set(UsageWarning::CUDAUsage);
warnUsage_.set(UsageWarning::IgnoreTKRUsage);
warnUsage_.set(UsageWarning::ExternalInterfaceMismatch);
warnUsage_.set(UsageWarning::DefinedOperatorArgs);
warnUsage_.set(UsageWarning::Final);
warnUsage_.set(UsageWarning::ZeroDoStep);
warnUsage_.set(UsageWarning::UnusedForallIndex);
warnUsage_.set(UsageWarning::OpenMPUsage);
warnUsage_.set(UsageWarning::DataLength);
warnUsage_.set(UsageWarning::IgnoredDirective);
warnUsage_.set(UsageWarning::HomonymousSpecific);
warnUsage_.set(UsageWarning::HomonymousResult);
warnUsage_.set(UsageWarning::IgnoredIntrinsicFunctionType);
warnUsage_.set(UsageWarning::PreviousScalarUse);
warnUsage_.set(UsageWarning::RedeclaredInaccessibleComponent);
warnUsage_.set(UsageWarning::ImplicitShared);
warnUsage_.set(UsageWarning::IndexVarRedefinition);
warnUsage_.set(UsageWarning::IncompatibleImplicitInterfaces);
warnUsage_.set(UsageWarning::VectorSubscriptFinalization);
warnUsage_.set(UsageWarning::UndefinedFunctionResult);
warnUsage_.set(UsageWarning::UselessIomsg);
warnUsage_.set(UsageWarning::UnsignedLiteralTruncation);
warnUsage_.set(UsageWarning::NullActualForDefaultIntentAllocatable);
warnUsage_.set(UsageWarning::UseAssociationIntoSameNameSubprogram);
warnUsage_.set(UsageWarning::HostAssociatedIntentOutInSpecExpr);
warnUsage_.set(UsageWarning::NonVolatilePointerToVolatile);
warnUsage_.set(UsageWarning::RealConstantWidening);
// New warnings, on by default
warnLanguage_.set(LanguageFeature::SavedLocalInSpecExpr);
warnLanguage_.set(LanguageFeature::NullActualForAllocatable);
warnUsage_.set(UsageWarning::BadValueInDeadCode);
warnUsage_.set(UsageWarning::MisplacedIgnoreTKR);
warnUsage_.set(UsageWarning::ImpureFinalInPure);
warnUsage_.set(UsageWarning::IgnoredNoReallocateLHS);
warnUsage_.set(UsageWarning::IoImpliedDoIndexConflict);
warnLanguage_.set(LanguageFeature::OpenMPThreadprivateEquivalence);
warnLanguage_.set(LanguageFeature::OpenAccDefaultNoneScalarsStrict);
warnLanguage_.set(LanguageFeature::OpenACCMultipleNamesInRoutine);
}
std::optional<LanguageControlFlag> LanguageFeatureControl::FindWarning(
std::string_view input) {
bool negated{false};
// TODO: replace with starts_with when moving to C++20
if (input.size() > 3 && input.substr(0, 3) == "no-") {
negated = true;
input = input.substr(3);
}
if (auto it{cliOptions_.find(std::string{input})}; it != cliOptions_.end()) {
return std::make_pair(it->second, !negated);
}
return std::nullopt;
}
std::optional<std::string_view> LanguageFeatureControl::CheckDeprecatedSpelling(
std::string_view input) const {
// Strip "no-" prefix for lookup, same as FindWarning does.
// TODO: Consider using std::string_view instead of llvm::StringRef
// when moving to C++20:
if (llvm::StringRef{input}.starts_with("no-")) {
input = input.substr(3);
}
if (auto it{deprecatedCliOptions_.find(std::string{input})};
it != deprecatedCliOptions_.end()) {
return it->second;
}
return std::nullopt;
}
bool LanguageFeatureControl::EnableWarning(std::string_view input) {
if (auto warningAndEnabled{FindWarning(input)}) {
EnableWarning(warningAndEnabled->first, warningAndEnabled->second);
return true;
}
return false;
}
void LanguageFeatureControl::ReplaceCliCanonicalSpelling(
LanguageFeature f, std::string input) {
cliOptions_.erase(languageFeatureCliCanonicalSpelling_[EnumToInt(f)]);
cliOptions_.insert({input, {f}});
languageFeatureCliCanonicalSpelling_[EnumToInt(f)] = std::move(input);
}
void LanguageFeatureControl::ReplaceCliCanonicalSpelling(
UsageWarning w, std::string input) {
cliOptions_.erase(usageWarningCliCanonicalSpelling_[EnumToInt(w)]);
cliOptions_.insert({input, {w}});
usageWarningCliCanonicalSpelling_[EnumToInt(w)] = std::move(input);
}
std::vector<const char *> LanguageFeatureControl::GetNames(
LogicalOperator opr) const {
std::vector<const char *> result;
result.push_back(AsFortran(opr));
if (opr == LogicalOperator::Neqv && IsEnabled(LanguageFeature::XOROperator)) {
result.push_back(".xor.");
}
if (IsEnabled(LanguageFeature::LogicalAbbreviations)) {
switch (opr) {
SWITCH_COVERS_ALL_CASES
case LogicalOperator::And:
result.push_back(".a.");
break;
case LogicalOperator::Or:
result.push_back(".o.");
break;
case LogicalOperator::Not:
result.push_back(".n.");
break;
case LogicalOperator::Neqv:
if (IsEnabled(LanguageFeature::XOROperator)) {
result.push_back(".x.");
}
break;
case LogicalOperator::Eqv:
break;
}
}
return result;
}
std::vector<const char *> LanguageFeatureControl::GetNames(
RelationalOperator opr) const {
switch (opr) {
SWITCH_COVERS_ALL_CASES
case RelationalOperator::LT:
return {".lt.", "<"};
case RelationalOperator::LE:
return {".le.", "<="};
case RelationalOperator::EQ:
return {".eq.", "=="};
case RelationalOperator::GE:
return {".ge.", ">="};
case RelationalOperator::GT:
return {".gt.", ">"};
case RelationalOperator::NE:
if (IsEnabled(LanguageFeature::AlternativeNE)) {
return {".ne.", "/=", "<>"};
} else {
return {".ne.", "/="};
}
}
}
void LanguageFeatureControl::WarnOnAllNonstandard(bool yes) {
warnAllLanguage_ = yes;
warnLanguage_.reset();
if (yes) {
disableAllWarnings_ = false;
warnLanguage_.flip();
// These three features do not need to be warned about,
// but we do want their feature flags.
warnLanguage_.set(LanguageFeature::OpenMP, false);
warnLanguage_.set(LanguageFeature::OpenACC, false);
warnLanguage_.set(LanguageFeature::CUDA, false);
}
}
void LanguageFeatureControl::WarnOnAllUsage(bool yes) {
warnAllUsage_ = yes;
warnUsage_.reset();
if (yes) {
disableAllWarnings_ = false;
warnUsage_.flip();
}
}
} // namespace Fortran::common