The CamelCase-to-hyphenated conversion was incorrectly splitting "OpenMP" and "OpenACC" into "open-mp" and "open-acc", producing wrong -W flag names like -Wopen-mp-usage instead of -Wopenmp-usage. Fix the conversion to treat these as compound names, keep the old spellings as deprecated aliases, and emit a warning when deprecated spellings are used. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
347 lines
14 KiB
C++
347 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::ImplicitNoneTypeNever);
|
|
disable_.set(LanguageFeature::ImplicitNoneTypeAlways);
|
|
disable_.set(LanguageFeature::ImplicitNoneExternal);
|
|
disable_.set(LanguageFeature::DefaultSave);
|
|
disable_.set(LanguageFeature::SaveMainProgram);
|
|
// 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);
|
|
// 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);
|
|
warnLanguage_.set(LanguageFeature::OpenMPThreadprivateEquivalence);
|
|
}
|
|
|
|
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
|