| //===-- 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); | |
| // Spelled only in module files, by the compiler itself; enabled just for | |
| // parsing them, so that user code cannot write it. | |
| disable_.set(LanguageFeature::CUDAImplicitDataAttrSpelling); | |
| disable_.set(LanguageFeature::CUDAInit); | |
| 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); | |
| disable_.set(LanguageFeature::ImplicitModulePrefix); | |
| // 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::OpenMPDeprecated); | |
| warnUsage_.set(UsageWarning::OpenMPFuture); | |
| 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); | |
| warnUsage_.set(UsageWarning::BOZLiteralTruncation); | |
| warnLanguage_.set(LanguageFeature::PreferIntrinsicModuleUseAssociation); | |
| warnLanguage_.set(LanguageFeature::OpenMPThreadprivateEquivalence); | |
| warnLanguage_.set(LanguageFeature::OpenAccDefaultNoneScalarsStrict); | |
| warnLanguage_.set(LanguageFeature::OpenACCMultipleNamesInRoutine); | |
| warnLanguage_.set(LanguageFeature::MultipleCommonBlockInit); | |
| warnLanguage_.set(LanguageFeature::OutOfBoundsSubscripts); | |
| } | |
| 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 |