//===--- LangOptions.cpp - Language & configuration options ---------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file defines the LangOptions class, which provides various
//  language and configuration flags.
//
//===----------------------------------------------------------------------===//

#include "swift/Basic/LangOptions.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Feature.h"
#include "swift/Basic/FileTypes.h"
#include "swift/Basic/Platform.h"
#include "swift/Basic/PlaygroundOption.h"
#include "swift/Basic/Range.h"
#include "swift/Config.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/raw_ostream.h"
#include <limits.h>
#include <optional>

using namespace swift;

LangOptions::LangOptions() {
  // Add all promoted language features
#define LANGUAGE_FEATURE(FeatureName, SENumber, Description)                   \
  enableFeature(Feature::FeatureName);
#define UPCOMING_FEATURE(FeatureName, SENumber, LanguageMode)
#define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd)
#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description)
#include "swift/Basic/Features.def"

  // Special case: remove macro support if the compiler wasn't built with a
  // host Swift.
#if !SWIFT_BUILD_SWIFT_SYNTAX
  disableFeature(Feature::Macros);
  disableFeature(Feature::FreestandingExpressionMacros);
  disableFeature(Feature::AttachedMacros);
  disableFeature(Feature::ExtensionMacros);
#endif

  // Enable any playground options that are enabled by default.
#define PLAYGROUND_OPTION(OptionName, Description, DefaultOn, HighPerfOn) \
  if (DefaultOn) \
    PlaygroundOptions.insert(PlaygroundOption::OptionName);
#include "swift/Basic/PlaygroundOptions.def"
}

struct SupportedConditionalValue {
  StringRef value;

  /// If the value has been deprecated, the new value to replace it with.
  StringRef replacement = "";

  SupportedConditionalValue(const char *value) : value(value) {}
  SupportedConditionalValue(const char *value, const char *replacement)
    : value(value), replacement(replacement) {}
};

static const SupportedConditionalValue SupportedConditionalCompilationOSs[] = {
  "OSX",
  "macOS",
  "tvOS",
  "watchOS",
  "iOS",
  "visionOS",
  "xrOS",
  "Linux",
  "FreeBSD",
  "OpenBSD",
  "Windows",
  "Android",
  "PS4",
  "Cygwin",
  "Haiku",
  "WASI",
  "none",
};

static const SupportedConditionalValue SupportedConditionalCompilationArches[] = {
  "arm",
  "arm64",
  "arm64_32",
  "i386",
  "x86_64",
  "powerpc",
  "powerpc64",
  "powerpc64le",
  "s390x",
  "wasm32",
  "riscv32",
  "riscv64",
  "avr"
};

static const SupportedConditionalValue SupportedConditionalCompilationEndianness[] = {
  "little",
  "big"
};

static const SupportedConditionalValue SupportedConditionalCompilationPointerBitWidths[] = {
  "_16",
  "_32",
  "_64"
};

static const SupportedConditionalValue SupportedConditionalCompilationRuntimes[] = {
  "_ObjC",
  "_Native",
  "_multithreaded",
};

static const SupportedConditionalValue SupportedConditionalCompilationTargetEnvironments[] = {
  "simulator",
  { "macabi", "macCatalyst" },
  "macCatalyst", // A synonym for "macabi" when compiling for iOS
};

static const SupportedConditionalValue SupportedConditionalCompilationPtrAuthSchemes[] = {
  "_none",
  "_arm64e",
};

static const SupportedConditionalValue SupportedConditionalCompilationHasAtomicBitWidths[] = {
  "_8",
  "_16",
  "_32",
  "_64",
  "_128"
};

static const SupportedConditionalValue SupportedConditionalCompilationObjectFileFormats[] = {
  "MachO",
  "ELF",
  "COFF",
  "Wasm",
};

static const PlatformConditionKind AllPublicPlatformConditionKinds[] = {
#define PLATFORM_CONDITION(LABEL, IDENTIFIER) PlatformConditionKind::LABEL,
#define PLATFORM_CONDITION_(LABEL, IDENTIFIER)
#include "swift/AST/PlatformConditionKinds.def"
};

static ArrayRef<SupportedConditionalValue> getSupportedConditionalCompilationValues(const PlatformConditionKind &Kind) {
  switch (Kind) {
  case PlatformConditionKind::OS:
    return SupportedConditionalCompilationOSs;
  case PlatformConditionKind::Arch:
    return SupportedConditionalCompilationArches;
  case PlatformConditionKind::Endianness:
    return SupportedConditionalCompilationEndianness;
  case PlatformConditionKind::PointerBitWidth:
    return SupportedConditionalCompilationPointerBitWidths;
  case PlatformConditionKind::Runtime:
    return SupportedConditionalCompilationRuntimes;
  case PlatformConditionKind::CanImport:
    return { };
  case PlatformConditionKind::TargetEnvironment:
    return SupportedConditionalCompilationTargetEnvironments;
  case PlatformConditionKind::PtrAuth:
    return SupportedConditionalCompilationPtrAuthSchemes;
  case PlatformConditionKind::HasAtomicBitWidth:
    return SupportedConditionalCompilationHasAtomicBitWidths;
  case PlatformConditionKind::ObjectFileFormat:
    return SupportedConditionalCompilationObjectFileFormats;
  }
  llvm_unreachable("Unhandled PlatformConditionKind in switch");
}

static PlatformConditionKind suggestedPlatformConditionKind(PlatformConditionKind Kind, const StringRef &V,
                                                            std::vector<StringRef> &suggestedValues) {
  std::string lower = V.lower();
  for (const PlatformConditionKind& candidateKind : AllPublicPlatformConditionKinds) {
    if (candidateKind != Kind) {
      auto supportedValues = getSupportedConditionalCompilationValues(candidateKind);
      for (const SupportedConditionalValue& candidateValue : supportedValues) {
        if (candidateValue.value.lower() == lower) {
          suggestedValues.clear();
          if (candidateValue.value != V) {
            suggestedValues.emplace_back(candidateValue.value);
          }
          return candidateKind;
        }
      }
    }
  }
  return Kind;
}

static bool isMatching(PlatformConditionKind Kind, const StringRef &V,
                       PlatformConditionKind &suggestedKind, std::vector<StringRef> &suggestions) {
  // Compare against known values, ignoring case to avoid penalizing
  // characters with incorrect case.
  unsigned minDistance = std::numeric_limits<unsigned>::max();
  std::string lower = V.lower();
  auto supportedValues = getSupportedConditionalCompilationValues(Kind);
  for (const SupportedConditionalValue& candidate : supportedValues) {
    if (candidate.value == V) {
      suggestedKind = Kind;
      suggestions.clear();
      if (!candidate.replacement.empty())
        suggestions.push_back(candidate.replacement);
      return true;
    }
    unsigned distance = StringRef(lower).edit_distance(candidate.value.lower());
    if (distance < minDistance) {
      suggestions.clear();
      minDistance = distance;
    }
    if (distance == minDistance)
      suggestions.emplace_back(candidate.value);
  }
  suggestedKind = suggestedPlatformConditionKind(Kind, V, suggestions);
  return false;
}

bool LangOptions::
checkPlatformConditionSupported(PlatformConditionKind Kind, StringRef Value,
                                PlatformConditionKind &suggestedKind,
                                std::vector<StringRef> &suggestedValues) {
  switch (Kind) {
  case PlatformConditionKind::OS:
  case PlatformConditionKind::Arch:
  case PlatformConditionKind::Endianness:
  case PlatformConditionKind::PointerBitWidth:
  case PlatformConditionKind::Runtime:
  case PlatformConditionKind::TargetEnvironment:
  case PlatformConditionKind::PtrAuth:
  case PlatformConditionKind::HasAtomicBitWidth:
  case PlatformConditionKind::ObjectFileFormat:
    return isMatching(Kind, Value, suggestedKind, suggestedValues);
  case PlatformConditionKind::CanImport:
    // All importable names are valid.
    // FIXME: Perform some kind of validation of the string?
    return true;
  }
  llvm_unreachable("Unhandled enum value");
}

StringRef
LangOptions::getPlatformConditionValue(PlatformConditionKind Kind) const {
  // Last one wins.
  for (auto &Opt : llvm::reverse(PlatformConditionValues)) {
    if (Opt.first == Kind)
      return Opt.second;
  }
  return StringRef();
}

bool LangOptions::
checkPlatformCondition(PlatformConditionKind Kind, StringRef Value) const {
  // Note: BridgedASTContext_enumerateBuildConfigurationEntries has a redundant
  // copy of these special cases.

  // Check a special case that "macOS" is an alias of "OSX".
  if (Kind == PlatformConditionKind::OS && Value == "macOS")
    return checkPlatformCondition(Kind, "OSX");

  // When compiling for iOS we consider "macCatalyst" to be a
  // synonym of "macabi". This enables the use of
  // #if targetEnvironment(macCatalyst) as a compilation
  // condition for macCatalyst.

  if (Kind == PlatformConditionKind::TargetEnvironment &&
      Value == "macCatalyst" && Target.isiOS()) {
    return checkPlatformCondition(Kind, "macabi");
  }

  for (auto &Opt : llvm::reverse(PlatformConditionValues)) {
    if (Opt.first == Kind)
      if (Opt.second == Value)
        return true;
  }

  if (Kind == PlatformConditionKind::HasAtomicBitWidth) {
    for (auto bitWidth : AtomicBitWidths) {
      if (bitWidth == Value) {
        return true;
      }
    }
  }

  return false;
}

bool LangOptions::isCustomConditionalCompilationFlagSet(StringRef Name) const {
  return std::find(CustomConditionalCompilationFlags.begin(),
                   CustomConditionalCompilationFlags.end(), Name)
      != CustomConditionalCompilationFlags.end();
}

bool LangOptions::FeatureState::isEnabled() const {
  return state == FeatureState::Kind::Enabled;
}

bool LangOptions::FeatureState::isEnabledForMigration() const {
  ASSERT(feature.isMigratable() && "You forgot to make the feature migratable!");

  return state == FeatureState::Kind::EnabledForMigration;
}

LangOptions::FeatureStateStorage::FeatureStateStorage()
    : states(Feature::getNumFeatures(), FeatureState::Kind::Off) {}

void LangOptions::FeatureStateStorage::setState(Feature feature,
                                                FeatureState::Kind state) {
  auto index = size_t(feature);

  states[index] = state;
}

LangOptions::FeatureState
LangOptions::FeatureStateStorage::getState(Feature feature) const {
  auto index = size_t(feature);

  return FeatureState(feature, states[index]);
}

LangOptions::FeatureState LangOptions::getFeatureState(Feature feature) const {
  auto state = featureStates.getState(feature);
  if (state.isEnabled())
    return state;

  if (auto version = feature.getLanguageMode()) {
    if (isLanguageModeAtLeast(*version)) {
      return FeatureState(feature, FeatureState::Kind::Enabled);
    }
  }

  return state;
}

bool LangOptions::hasFeature(Feature feature, bool allowMigration) const {
  auto state = featureStates.getState(feature);
  if (state.isEnabled())
    return true;

  if (auto version = feature.getLanguageMode()) {
    if (isLanguageModeAtLeast(*version))
      return true;
  }

  if (allowMigration && state.isEnabledForMigration())
    return true;

  return false;
}

bool LangOptions::hasFeature(llvm::StringRef featureName) const {
  auto feature = llvm::StringSwitch<std::optional<Feature>>(featureName)
#define LANGUAGE_FEATURE(FeatureName, SENumber, Description)                   \
  .Case(#FeatureName, Feature::FeatureName)
#include "swift/Basic/Features.def"
                     .Default(std::nullopt);
  if (feature)
    return hasFeature(*feature);

  return false;
}

bool LangOptions::isMigratingToFeature(Feature feature) const {
  return featureStates.getState(feature).isEnabledForMigration();
}

void LangOptions::enableFeature(Feature feature, bool forMigration) {
  if (forMigration) {
    ASSERT(feature.isMigratable());
    featureStates.setState(feature, FeatureState::Kind::EnabledForMigration);
    return;
  }

  featureStates.setState(feature, FeatureState::Kind::Enabled);
}

void LangOptions::disableFeature(Feature feature) {
  featureStates.setState(feature, FeatureState::Kind::Off);
}

void LangOptions::setHasAtomicBitWidth(llvm::Triple triple) {
  // We really want to use Clang's getMaxAtomicInlineWidth(), but that requires
  // a Clang::TargetInfo and we're setting up lang opts very early in the
  // pipeline before any ASTContext or any ClangImporter instance where we can
  // access the target's info.

  switch (triple.getArch()) {
  // ARM is only a 32 bit arch and all archs besides the microcontroller profile
  // ones have double word atomics.
  case llvm::Triple::ArchType::arm:
  case llvm::Triple::ArchType::thumb:
    switch (triple.getSubArch()) {
    case llvm::Triple::SubArchType::ARMSubArch_v6m:
    case llvm::Triple::SubArchType::ARMSubArch_v7m:
      setMaxAtomicBitWidth(32);
      break;

    default:
      setMaxAtomicBitWidth(64);
      break;
    }
    break;

  // AArch64 (arm64) supports double word atomics on all archs besides the
  // microcontroller profiles.
  case llvm::Triple::ArchType::aarch64:
    switch (triple.getSubArch()) {
    case llvm::Triple::SubArchType::ARMSubArch_v8m_baseline:
    case llvm::Triple::SubArchType::ARMSubArch_v8m_mainline:
    case llvm::Triple::SubArchType::ARMSubArch_v8_1m_mainline:
      setMaxAtomicBitWidth(64);
      break;

    default:
      setMaxAtomicBitWidth(128);
      break;
    }
    break;

  // arm64_32 has 32 bit pointer words, but it has the same architecture as
  // arm64 and supports 128 bit atomics.
  case llvm::Triple::ArchType::aarch64_32:
    setMaxAtomicBitWidth(128);
    break;

  // PowerPC does not support double word atomics.
  case llvm::Triple::ArchType::ppc:
    setMaxAtomicBitWidth(32);
    break;

  // All of the 64 bit PowerPC flavors do not support double word atomics.
  case llvm::Triple::ArchType::ppc64:
  case llvm::Triple::ArchType::ppc64le:
    setMaxAtomicBitWidth(64);
    break;

  // SystemZ (s390x) does not support double word atomics.
  case llvm::Triple::ArchType::systemz:
    setMaxAtomicBitWidth(64);
    break;

  // Wasm32 supports double word atomics.
  case llvm::Triple::ArchType::wasm32:
    setMaxAtomicBitWidth(64);
    break;

  // x86 supports double word atomics.
  //
  // Technically, this is incorrect. However, on all x86 platforms where Swift
  // is deployed this is true.
  case llvm::Triple::ArchType::x86:
    setMaxAtomicBitWidth(64);
    break;

  // x86_64 supports double word atomics.
  //
  // Technically, this is incorrect. However, on all x86_64 platforms where Swift
  // is deployed this is true. If the ClangImporter ever stops unconditionally
  // adding '-mcx16' to its Clang instance, then be sure to update this below.
  case llvm::Triple::ArchType::x86_64:
    setMaxAtomicBitWidth(128);
    break;

  default:
    // Some exotic architectures may not support atomics at all. If that's the
    // case please update the switch with your flavor of arch. Otherwise assume
    // every arch supports at least word atomics.

    if (triple.isArch32Bit()) {
      setMaxAtomicBitWidth(32);
    }

    if (triple.isArch64Bit()) {
      setMaxAtomicBitWidth(64);
    }
  }
}

static bool isMultiThreadedRuntime(llvm::Triple triple) {
  if (triple.getOS() == llvm::Triple::WASI) {
    return triple.getEnvironmentName() == "threads";
  }
  if (triple.getOSName() == "none") {
    return false;
  }
  return true;
}

std::pair<bool, bool> LangOptions::setTarget(llvm::Triple triple) {
  clearAllPlatformConditionValues();
  clearAtomicBitWidths();

  if (triple.getOS() == llvm::Triple::Darwin &&
      triple.getVendor() == llvm::Triple::Apple) {
    // Rewrite darwinX.Y triples to macosx10.X'.Y ones.
    // It affects code generation on our platform.
    llvm::SmallString<16> osxBuf;
    llvm::raw_svector_ostream osx(osxBuf);
    osx << llvm::Triple::getOSTypeName(llvm::Triple::MacOSX);

    llvm::VersionTuple OSVersion;
    triple.getMacOSXVersion(OSVersion);

    osx << OSVersion.getMajor() << "." << OSVersion.getMinor().value_or(0);
    if (auto Subminor = OSVersion.getSubminor())
      osx << "." << *Subminor;

    triple.setOSName(osx.str());
  }
  Target = std::move(triple);

  bool UnsupportedOS = false;

  // Set the "os" platform condition.
  switch (Target.getOS()) {
  case llvm::Triple::Darwin:
  case llvm::Triple::MacOSX:
    addPlatformConditionValue(PlatformConditionKind::OS, "OSX");
    break;
  case llvm::Triple::TvOS:
    addPlatformConditionValue(PlatformConditionKind::OS, "tvOS");
    break;
  case llvm::Triple::WatchOS:
    addPlatformConditionValue(PlatformConditionKind::OS, "watchOS");
    break;
  case llvm::Triple::IOS:
    addPlatformConditionValue(PlatformConditionKind::OS, "iOS");
    break;
  case llvm::Triple::XROS:
    addPlatformConditionValue(PlatformConditionKind::OS, "xrOS");
    addPlatformConditionValue(PlatformConditionKind::OS, "visionOS");
    break;
  case llvm::Triple::Linux:
    if (Target.getEnvironment() == llvm::Triple::Android)
      addPlatformConditionValue(PlatformConditionKind::OS, "Android");
    else
      addPlatformConditionValue(PlatformConditionKind::OS, "Linux");
    break;
  case llvm::Triple::FreeBSD:
    addPlatformConditionValue(PlatformConditionKind::OS, "FreeBSD");
    break;
  case llvm::Triple::OpenBSD:
    addPlatformConditionValue(PlatformConditionKind::OS, "OpenBSD");
    break;
  case llvm::Triple::Win32:
    if (Target.getEnvironment() == llvm::Triple::Cygnus)
      addPlatformConditionValue(PlatformConditionKind::OS, "Cygwin");
    else
      addPlatformConditionValue(PlatformConditionKind::OS, "Windows");
    break;
  case llvm::Triple::PS4:
    if (Target.getVendor() == llvm::Triple::SCEI)
      addPlatformConditionValue(PlatformConditionKind::OS, "PS4");
    else
      UnsupportedOS = false;
    break;
  case llvm::Triple::Haiku:
    addPlatformConditionValue(PlatformConditionKind::OS, "Haiku");
    break;
  case llvm::Triple::WASI:
    addPlatformConditionValue(PlatformConditionKind::OS, "WASI");
    break;
  case llvm::Triple::UnknownOS:
    if (Target.getOSName() == "none") {
      addPlatformConditionValue(PlatformConditionKind::OS, "none");
      break;
    }
    LLVM_FALLTHROUGH;
  default:
    UnsupportedOS = true;
    break;
  }

  bool UnsupportedArch = false;

  // Set the "arch" platform condition.
  switch (Target.getArch()) {
  case llvm::Triple::ArchType::arm:
  case llvm::Triple::ArchType::thumb:
    addPlatformConditionValue(PlatformConditionKind::Arch, "arm");
    break;
  case llvm::Triple::ArchType::aarch64:
  case llvm::Triple::ArchType::aarch64_32:
    if (Target.getArchName() == "arm64_32") {
      addPlatformConditionValue(PlatformConditionKind::Arch, "arm64_32");
    } else {
      addPlatformConditionValue(PlatformConditionKind::Arch, "arm64");
    }
    break;
  case llvm::Triple::ArchType::ppc:
    addPlatformConditionValue(PlatformConditionKind::Arch, "powerpc");
    break;
  case llvm::Triple::ArchType::ppc64:
    addPlatformConditionValue(PlatformConditionKind::Arch, "powerpc64");
    break;
  case llvm::Triple::ArchType::ppc64le:
    addPlatformConditionValue(PlatformConditionKind::Arch, "powerpc64le");
    break;
  case llvm::Triple::ArchType::x86:
    addPlatformConditionValue(PlatformConditionKind::Arch, "i386");
    break;
  case llvm::Triple::ArchType::x86_64:
    addPlatformConditionValue(PlatformConditionKind::Arch, "x86_64");
    break;
  case llvm::Triple::ArchType::systemz:
    addPlatformConditionValue(PlatformConditionKind::Arch, "s390x");
    break;
  case llvm::Triple::ArchType::wasm32:
    addPlatformConditionValue(PlatformConditionKind::Arch, "wasm32");
    break;
  case llvm::Triple::ArchType::riscv32:
    addPlatformConditionValue(PlatformConditionKind::Arch, "riscv32");
    break;
  case llvm::Triple::ArchType::riscv64:
    addPlatformConditionValue(PlatformConditionKind::Arch, "riscv64");
    break;
  case llvm::Triple::ArchType::avr:
    addPlatformConditionValue(PlatformConditionKind::Arch, "avr");
    break;
  default:
    UnsupportedArch = true;

    if (Target.getOSName() == "none") {
      if (Target.getArch() != llvm::Triple::ArchType::UnknownArch) {
        auto ArchName = llvm::Triple::getArchTypeName(Target.getArch());
        addPlatformConditionValue(PlatformConditionKind::Arch, ArchName);
        UnsupportedArch = false;
      }
    }
  }

  if (UnsupportedOS || UnsupportedArch)
    return { UnsupportedOS, UnsupportedArch };

  // Set the "_endian" platform condition.
  if (Target.isLittleEndian()) {
    addPlatformConditionValue(PlatformConditionKind::Endianness, "little");
  } else {
    addPlatformConditionValue(PlatformConditionKind::Endianness, "big");
  }

  // Set the "_pointerBitWidth" platform condition.
  if (Target.isArch16Bit()) {
    addPlatformConditionValue(PlatformConditionKind::PointerBitWidth, "_16");
  } else if (Target.isArch32Bit()) {
    addPlatformConditionValue(PlatformConditionKind::PointerBitWidth, "_32");
  } else if (Target.isArch64Bit()) {
    addPlatformConditionValue(PlatformConditionKind::PointerBitWidth, "_64");
  }

  // Set the "objectFormat" platform condition.
  if (Target.isOSBinFormatMachO()) {
    addPlatformConditionValue(PlatformConditionKind::ObjectFileFormat, "MachO");
  } else if (Target.isOSBinFormatELF()) {
    addPlatformConditionValue(PlatformConditionKind::ObjectFileFormat, "ELF");
  } else if (Target.isOSBinFormatCOFF()) {
    addPlatformConditionValue(PlatformConditionKind::ObjectFileFormat, "COFF");
  } else if (Target.isOSBinFormatWasm()) {
    addPlatformConditionValue(PlatformConditionKind::ObjectFileFormat, "Wasm");
  }

  // Set the "runtime" platform condition.
  addPlatformConditionValue(PlatformConditionKind::Runtime,
                            EnableObjCInterop ? "_ObjC" : "_Native");

  // Set the pointer authentication scheme.
  if (Target.getArchName() == "arm64e") {
    addPlatformConditionValue(PlatformConditionKind::PtrAuth, "_arm64e");
  } else {
    addPlatformConditionValue(PlatformConditionKind::PtrAuth, "_none");
  }

  // Set the "targetEnvironment" platform condition if targeting a simulator
  // environment. Otherwise _no_ value is present for targetEnvironment; it's
  // an optional disambiguating refinement of the triple.
  if (Target.isSimulatorEnvironment())
    addPlatformConditionValue(PlatformConditionKind::TargetEnvironment,
                              "simulator");

  if (tripleIsMacCatalystEnvironment(Target))
    addPlatformConditionValue(PlatformConditionKind::TargetEnvironment,
                              "macabi");

  if (isMultiThreadedRuntime(Target)) {
    addPlatformConditionValue(PlatformConditionKind::Runtime,
                              "_multithreaded");
  }

  // Set the "_hasHasAtomicBitWidth" platform condition.
  setHasAtomicBitWidth(triple);

  // If you add anything to this list, change the default size of
  // PlatformConditionValues to not require an extra allocation
  // in the common case.

  return { false, false };
}

llvm::StringRef swift::getPlaygroundOptionName(PlaygroundOption option) {
  switch (option) {
#define PLAYGROUND_OPTION(OptionName, Description, DefaultOn, HighPerfOn) \
  case PlaygroundOption::OptionName: return #OptionName;
#include "swift/Basic/PlaygroundOptions.def"
  }
  llvm_unreachable("covered switch");
}

std::optional<PlaygroundOption>
swift::getPlaygroundOption(llvm::StringRef name) {
  return llvm::StringSwitch<std::optional<PlaygroundOption>>(name)
#define PLAYGROUND_OPTION(OptionName, Description, DefaultOn, HighPerfOn) \
  .Case(#OptionName, PlaygroundOption::OptionName)
#include "swift/Basic/PlaygroundOptions.def"
      .Default(std::nullopt);
}

DiagnosticBehavior LangOptions::getAccessNoteFailureLimit() const {
  switch (AccessNoteBehavior) {
  case AccessNoteDiagnosticBehavior::Ignore:
    return DiagnosticBehavior::Ignore;

  case AccessNoteDiagnosticBehavior::RemarkOnFailure:
  case AccessNoteDiagnosticBehavior::RemarkOnFailureOrSuccess:
    return DiagnosticBehavior::Remark;

  case AccessNoteDiagnosticBehavior::ErrorOnFailureRemarkOnSuccess:
    return DiagnosticBehavior::Error;
  }
  llvm_unreachable("covered switch");
}

namespace {
  constexpr std::array<std::string_view, 16> knownSearchPathPrefiexes =
       {"-I",
        "-F",
        "-fmodule-map-file=",
        "-iquote",
        "-idirafter",
        "-iframeworkwithsysroot",
        "-iframework",
        "-iprefix",
        "-iwithprefixbefore",
        "-iwithprefix",
        "-isystemafter",
        "-isystem",
        "-isysroot",
        "-ivfsoverlay",
        "-working-directory=",
        "-working-directory"};

  constexpr std::array<std::string_view, 16>
      knownClangDependencyIgnorablePrefiexes = {"-I",
                                                "-F",
                                                "-fmodule-map-file=",
                                                "-ffile-compilation-dir",
                                                "-iquote",
                                                "-idirafter",
                                                "-iframeworkwithsysroot",
                                                "-iframework",
                                                "-iprefix",
                                                "-iwithprefixbefore",
                                                "-iwithprefix",
                                                "-isystemafter",
                                                "-isystem",
                                                "-isysroot",
                                                "-working-directory=",
                                                "-working-directory"};
}

std::vector<std::string> ClangImporterOptions::getRemappedExtraArgs(
    std::function<std::string(StringRef)> pathRemapCallback) const {
  auto consumeIncludeOption = [](StringRef &arg, StringRef &prefix) {
    for (const auto &option : knownSearchPathPrefiexes)
      if (arg.consume_front(option)) {
        prefix = option;
        return true;
      }
    return false;
  };

  // true if the previous argument was the dash-option of an option pair
  bool remap_next = false;
  std::vector<std::string> args;
  for (auto A : ExtraArgs) {
    StringRef prefix;
    StringRef arg(A);

    if (remap_next) {
      remap_next = false;
      args.push_back(pathRemapCallback(arg));
    } else if (consumeIncludeOption(arg, prefix)) {
      if (arg.empty()) {
        // Option pair
        remap_next = true;
        args.push_back(prefix.str());
      } else {
        // Combine prefix with remapped path value
        args.push_back(prefix.str() + pathRemapCallback(arg));
      }
    } else {
      args.push_back(A);
    }
  }
  return args;
}

std::vector<std::string>
ClangImporterOptions::getReducedExtraArgsForSwiftModuleDependency() const {
  auto matchIncludeOption = [](StringRef &arg) {
    for (const auto &option : knownClangDependencyIgnorablePrefiexes)
      if (arg.consume_front(option))
        return true;
    return false;
  };

  std::vector<std::string> filtered_args;
  bool skip_next = false;
  std::vector<std::string> args;
  for (auto A : ExtraArgs) {
    StringRef arg(A);
    if (skip_next) {
      skip_next = false;
      continue;
    } else if (matchIncludeOption(arg)) {
      if (arg.empty()) {
        // Option pair
        skip_next = true;
      } // else non-pair option e.g. '-I/search/path'
      continue;
    } else {
      filtered_args.push_back(A);
    }
  }

  return filtered_args;
}

std::string ClangImporterOptions::getPCHInputPath() const {
  if (!BridgingHeaderPCH.empty())
    return BridgingHeaderPCH;

  if (llvm::sys::path::extension(BridgingHeader)
          .ends_with(file_types::getExtension(file_types::TY_PCH)))
    return BridgingHeader;

  return {};
}
