Files
swift-mirror/lib/Basic/Version.cpp
Becca Royal-Gordon 3843c7cd5e Update SWIFT_COMPILER_VERSION language features
The `SWIFT_COMPILER_VERSION` define is used to stamp a vendor’s version number into a Swift compiler binary. It can be queried from Swift code using `#if _compiler_version` and from Clang by using a preprocessor definition called `__SWIFT_COMPILER_VERSION`. These are unsupported compiler-internal features used primarily by Apple Swift.

In Swift 1.0 through 5.5, Apple Swift used a scheme for `SWIFT_COMPILER_VERSION` where the major version matched the embedded clang (e.g. 1300 for Apple Clang 13.0.0) and the minor version was ignored. Starting in Swift 5.6, Apple Swift started using major and minor version numbers that matched the Swift.org version number. This makes them easier to understand, but it means that version 1300.0.x was followed by version 5.6.x. Not only did version numbers go backwards, but also the old logic to ignore minor versions was now a liability, because it meant you would not be able to target a change to 5.7.x compilers but not 5.6.x compilers.

This commit addresses the problem by:

* Modifying the existing `#if _compiler_version(string-literal)` feature so it transforms the major version into a major and minor that will compare correctly to new version numbers. For instance, “1300.*” is transformed into “1.300”, which will compare correctly to a “5.6” or “5.7” version even if it doesn’t really capture the fact that “1300” was a Swift 5.5 compiler. As a bonus, this allows you to use the feature to backwards-compatibly test new compilers using the existing feature: “5007.*” will be seen by compilers before 5.7 as an unknown future version, but will be seen by 5.7 compilers as targeting them.

* Modifying the `__SWIFT_COMPILER_VERSION` clang define similarly so that, to preprocessor conditions written for the old scheme, a 5.7 compiler will appear to have major version 5007.

* Adding a new variant of `#if _compiler_version` with the same syntax as `#if swift` and `#if compiler`—that is, taking a comparison operator and a bare set of dotted version numbers, rather than a string literal. Going forward, this will be how version checks are written once compatibility with compilers before this change is no longer a concern.

These changes are only lightly tested because tests have to work without any compiler version defined (the default in most configurations), but I’ve tested what I can.

Fixes rdar://89841295.
2022-04-27 18:27:52 -07:00

504 lines
16 KiB
C++

//===--- Version.cpp - Swift Version Number -------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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 several version-related utility functions for Swift.
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/CharInfo.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "swift/AST/DiagnosticsParse.h"
#include "swift/Basic/LLVM.h"
#include "swift/Basic/Version.h"
#include <vector>
#define TOSTR2(X) #X
#define TOSTR(X) TOSTR2(X)
#ifdef SWIFT_VERSION_PATCHLEVEL
/// Helper macro for SWIFT_VERSION_STRING.
#define SWIFT_MAKE_VERSION_STRING(X, Y, Z) TOSTR(X) "." TOSTR(Y) "." TOSTR(Z)
/// A string that describes the Swift version number, e.g., "1.0".
#define SWIFT_VERSION_STRING \
SWIFT_MAKE_VERSION_STRING(SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR, \
SWIFT_VERSION_PATCHLEVEL)
#else
/// Helper macro for SWIFT_VERSION_STRING.
#define SWIFT_MAKE_VERSION_STRING(X, Y) TOSTR(X) "." TOSTR(Y)
/// A string that describes the Swift version number, e.g., "1.0".
#define SWIFT_VERSION_STRING \
SWIFT_MAKE_VERSION_STRING(SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR)
#endif
#include "LLVMRevision.inc"
#include "SwiftRevision.inc"
namespace swift {
namespace version {
/// Print a string of the form "LLVM xxxxx, Swift zzzzz", where each placeholder
/// is the revision for the associated repository.
static void printFullRevisionString(raw_ostream &out) {
// Arbitrarily truncate to 15 characters. This should be enough to unique Git
// hashes while keeping the REPL version string from overflowing 80 columns.
#if defined(LLVM_REVISION)
out << "LLVM " << StringRef(LLVM_REVISION).slice(0, 15);
# if defined(SWIFT_REVISION)
out << ", ";
# endif
#endif
#if defined(SWIFT_REVISION)
out << "Swift " << StringRef(SWIFT_REVISION).slice(0, 15);
#endif
}
static void splitVersionComponents(
SmallVectorImpl<std::pair<StringRef, SourceRange>> &SplitComponents,
StringRef &VersionString, SourceLoc Loc,
bool skipQuote = false) {
SourceLoc Start = (Loc.isValid() && skipQuote) ? Loc.getAdvancedLoc(1) : Loc;
SourceLoc End = Start;
// Split the version string into tokens separated by the '.' character.
while (!VersionString.empty()) {
StringRef SplitComponent, Rest;
std::tie(SplitComponent, Rest) = VersionString.split('.');
if (Loc.isValid()) {
End = End.getAdvancedLoc(SplitComponent.size());
}
auto Range = Loc.isValid() ? SourceRange(Start, End) : SourceRange();
if (Loc.isValid())
End = End.getAdvancedLoc(1);
Start = End;
SplitComponents.push_back({SplitComponent, Range});
VersionString = Rest;
}
}
Optional<Version> Version::parseCompilerVersionString(
StringRef VersionString, SourceLoc Loc, DiagnosticEngine *Diags) {
Version CV;
SmallString<16> digits;
llvm::raw_svector_ostream OS(digits);
SmallVector<std::pair<StringRef, SourceRange>, 5> SplitComponents;
splitVersionComponents(SplitComponents, VersionString, Loc,
/*skipQuote=*/true);
uint64_t ComponentNumber;
bool isValidVersion = true;
auto checkVersionComponent = [&](unsigned Component, SourceRange Range) {
unsigned limit = CV.Components.empty() ? 9223371 : 999;
if (Component > limit) {
if (Diags)
Diags->diagnose(Range.Start,
diag::compiler_version_component_out_of_range, limit);
isValidVersion = false;
}
};
for (size_t i = 0; i < SplitComponents.size(); ++i) {
StringRef SplitComponent;
SourceRange Range;
std::tie(SplitComponent, Range) = SplitComponents[i];
// Version components can't be empty.
if (SplitComponent.empty()) {
if (Diags)
Diags->diagnose(Range.Start, diag::empty_version_component);
isValidVersion = false;
continue;
}
// The second version component isn't used for comparison.
if (i == 1) {
if (!SplitComponent.equals("*")) {
if (Diags) {
// Majors 600-1300 were used for Swift 1.0-5.5 (based on clang
// versions), but then we reset the numbering based on Swift versions,
// so 5.6 had major 5. We assume that majors below 600 use the new
// scheme and equal/above it use the old scheme.
bool firstComponentLooksNew = CV.Components[0] < 600;
auto diag = Diags->diagnose(Range.Start,
diag::unused_compiler_version_component,
firstComponentLooksNew);
if (firstComponentLooksNew &&
!SplitComponent.getAsInteger(10, ComponentNumber)) {
// Fix-it version like "5.7.1.2.3" to "5007.*.1.2.3".
auto newDigits = llvm::formatv("{0}{1,0+3}.*", CV.Components[0],
ComponentNumber).str();
diag.fixItReplaceChars(SplitComponents[0].second.Start,
Range.End, newDigits);
}
else {
diag.fixItReplaceChars(Range.Start, Range.End, "*");
}
}
}
CV.Components.push_back(0);
continue;
}
// All other version components must be numbers.
if (!SplitComponent.getAsInteger(10, ComponentNumber)) {
checkVersionComponent(ComponentNumber, Range);
CV.Components.push_back(ComponentNumber);
continue;
} else {
if (Diags)
Diags->diagnose(Range.Start, diag::version_component_not_number);
isValidVersion = false;
}
}
if (CV.Components.size() > 5) {
if (Diags)
Diags->diagnose(Loc, diag::compiler_version_too_many_components);
isValidVersion = false;
}
// In the beginning, '_compiler_version(string-literal)' was designed for a
// different version scheme where the major was fairly large and the minor
// was ignored; now we use one where the minor is significant and major and
// minor match the Swift language version. See the comment above on
// `firstComponentLooksNew` for details.
//
// However, we want the string literal variant of '_compiler_version' to
// maintain source compatibility with old checks; that means checks for new
// versions have to be written so that old compilers will think they represent
// newer versions, while new compilers have to interpret old version number
// strings in a way that will compare correctly to the new versions compiled
// into them.
//
// To achieve this, modern compilers divide the major by 1000 and overwrite
// the wildcard component with the remainder, effectively shifting the last
// three digits of the major into the minor, before comparing it to the
// compiler version:
//
// _compiler_version("5007.*.1.2.3") -> 5.7.1.2.3
// _compiler_version("1300.*.1.2.3") -> 1.300.1.2.3 (smaller than 5.6)
// _compiler_version( "600.*.1.2.3") -> 0.600.1.2.3 (smaller than 5.6)
//
// So if you want to specify a 5.7.z.a.b version, we ask users to either write
// it as 5007.*.z.a.b, or to use the new '_compiler_version(>= version)'
// syntax instead, which does not perform this conversion.
if (!CV.Components.empty()) {
if (CV.Components.size() == 1)
CV.Components.push_back(0);
CV.Components[1] = CV.Components[0] % 1000;
CV.Components[0] = CV.Components[0] / 1000;
}
return isValidVersion ? Optional<Version>(CV) : None;
}
Optional<Version> Version::parseVersionString(StringRef VersionString,
SourceLoc Loc,
DiagnosticEngine *Diags) {
Version TheVersion;
SmallString<16> digits;
llvm::raw_svector_ostream OS(digits);
SmallVector<std::pair<StringRef, SourceRange>, 5> SplitComponents;
// Skip over quote character in string literal.
if (VersionString.empty()) {
if (Diags)
Diags->diagnose(Loc, diag::empty_version_string);
return None;
}
splitVersionComponents(SplitComponents, VersionString, Loc, Diags);
uint64_t ComponentNumber;
bool isValidVersion = true;
for (size_t i = 0; i < SplitComponents.size(); ++i) {
StringRef SplitComponent;
SourceRange Range;
std::tie(SplitComponent, Range) = SplitComponents[i];
// Version components can't be empty.
if (SplitComponent.empty()) {
if (Diags)
Diags->diagnose(Range.Start, diag::empty_version_component);
isValidVersion = false;
continue;
}
// All other version components must be numbers.
if (!SplitComponent.getAsInteger(10, ComponentNumber)) {
TheVersion.Components.push_back(ComponentNumber);
continue;
} else {
if (Diags)
Diags->diagnose(Range.Start,
diag::version_component_not_number);
isValidVersion = false;
}
}
return isValidVersion ? Optional<Version>(TheVersion) : None;
}
Version::Version(StringRef VersionString,
SourceLoc Loc,
DiagnosticEngine *Diags)
: Version(*parseVersionString(VersionString, Loc, Diags))
{}
Version Version::getCurrentCompilerVersion() {
#ifdef SWIFT_COMPILER_VERSION
auto currentVersion = Version::parseVersionString(
SWIFT_COMPILER_VERSION, SourceLoc(), nullptr);
assert(currentVersion.hasValue() &&
"Embedded Swift language version couldn't be parsed: '"
SWIFT_COMPILER_VERSION
"'");
return currentVersion.getValue();
#else
return Version();
#endif
}
Version Version::getCurrentLanguageVersion() {
#if SWIFT_VERSION_PATCHLEVEL
return {SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR, SWIFT_VERSION_PATCHLEVEL};
#else
return {SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR};
#endif
}
raw_ostream &operator<<(raw_ostream &os, const Version &version) {
if (version.empty())
return os;
os << version[0];
for (size_t i = 1, e = version.size(); i != e; ++i)
os << '.' << version[i];
return os;
}
std::string
Version::preprocessorDefinition(StringRef macroName,
ArrayRef<uint64_t> componentWeights) const {
uint64_t versionConstant = 0;
for (size_t i = 0, e = std::min(componentWeights.size(), Components.size());
i < e; ++i) {
versionConstant += componentWeights[i] * Components[i];
}
std::string define("-D");
llvm::raw_string_ostream(define) << macroName << '=' << versionConstant;
// This isn't using stream.str() so that we get move semantics.
return define;
}
Version::operator llvm::VersionTuple() const
{
switch (Components.size()) {
case 0:
return llvm::VersionTuple();
case 1:
return llvm::VersionTuple((unsigned)Components[0]);
case 2:
return llvm::VersionTuple((unsigned)Components[0],
(unsigned)Components[1]);
case 3:
return llvm::VersionTuple((unsigned)Components[0],
(unsigned)Components[1],
(unsigned)Components[2]);
case 4:
case 5:
return llvm::VersionTuple((unsigned)Components[0],
(unsigned)Components[1],
(unsigned)Components[2],
(unsigned)Components[3]);
default:
llvm_unreachable("swift::version::Version with 6 or more components");
}
}
Optional<Version> Version::getEffectiveLanguageVersion() const {
switch (size()) {
case 0:
return None;
case 1:
break;
case 2:
// The only valid explicit language version with a minor
// component is 4.2.
if (Components[0] == 4 && Components[1] == 2)
break;
return None;
default:
// We do not want to permit users requesting more precise effective language
// versions since accepting such an argument promises more than we're able
// to deliver.
return None;
}
// FIXME: When we switch to Swift 5 by default, the "4" case should return
// a version newer than any released 4.x compiler, and the
// "5" case should start returning getCurrentLanguageVersion. We should
// also check for the presence of SWIFT_VERSION_PATCHLEVEL, and if that's
// set apply it to the "3" case, so that Swift 4.0.1 will automatically
// have a compatibility mode of 3.2.1.
switch (Components[0]) {
case 4:
// Version '4' on its own implies '4.1.50'.
if (size() == 1)
return Version{4, 1, 50};
// This should be true because of the check up above.
assert(size() == 2 && Components[0] == 4 && Components[1] == 2);
return Version{4, 2};
case 5:
static_assert(SWIFT_VERSION_MAJOR == 5,
"getCurrentLanguageVersion is no longer correct here");
return Version::getCurrentLanguageVersion();
case 6:
// Allow version '6' in asserts compilers *only* so that we can start
// testing changes slated for Swift 6. Note that it's still not listed in
// `Version::getValidEffectiveVersions()`.
// FIXME: When Swift 6 becomes real, remove 'REQUIRES: asserts' from tests
// using '-swift-version 6'.
#ifdef NDEBUG
LLVM_FALLTHROUGH;
#else
return Version{6};
#endif
default:
return None;
}
}
Version Version::asMajorVersion() const {
if (empty())
return {};
Version res;
res.Components.push_back(Components[0]);
return res;
}
std::string Version::asAPINotesVersionString() const {
// Other than for "4.2.x", map the Swift major version into
// the API notes version for Swift. This has the effect of allowing
// API notes to effect changes only on Swift major versions,
// not minor versions.
if (size() >= 2 && Components[0] == 4 && Components[1] == 2)
return "4.2";
return llvm::itostr(Components[0]);
}
bool operator>=(const class Version &lhs,
const class Version &rhs) {
// The empty compiler version represents the latest possible version,
// usually built from the source repository.
if (lhs.empty())
return true;
auto n = std::max(lhs.size(), rhs.size());
for (size_t i = 0; i < n; ++i) {
auto lv = i < lhs.size() ? lhs[i] : 0;
auto rv = i < rhs.size() ? rhs[i] : 0;
if (lv < rv)
return false;
else if (lv > rv)
return true;
}
// Equality
return true;
}
bool operator<(const class Version &lhs, const class Version &rhs) {
return !(lhs >= rhs);
}
bool operator==(const class Version &lhs,
const class Version &rhs) {
auto n = std::max(lhs.size(), rhs.size());
for (size_t i = 0; i < n; ++i) {
auto lv = i < lhs.size() ? lhs[i] : 0;
auto rv = i < rhs.size() ? rhs[i] : 0;
if (lv != rv)
return false;
}
return true;
}
std::pair<unsigned, unsigned> getSwiftNumericVersion() {
return { SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR };
}
std::string getSwiftFullVersion(Version effectiveVersion) {
std::string buf;
llvm::raw_string_ostream OS(buf);
#ifdef SWIFT_VENDOR
OS << SWIFT_VENDOR " ";
#endif
OS << "Swift version " SWIFT_VERSION_STRING;
#ifndef SWIFT_COMPILER_VERSION
OS << "-dev";
#endif
if (effectiveVersion != Version::getCurrentLanguageVersion()) {
OS << " effective-" << effectiveVersion;
}
#if defined(SWIFT_COMPILER_VERSION)
OS << " (swiftlang-" SWIFT_COMPILER_VERSION;
#if defined(CLANG_COMPILER_VERSION)
OS << " clang-" CLANG_COMPILER_VERSION;
#endif
OS << ")";
#elif defined(LLVM_REVISION) || defined(SWIFT_REVISION)
OS << " (";
printFullRevisionString(OS);
OS << ")";
#endif
// Suppress unused function warning
(void)&printFullRevisionString;
return OS.str();
}
StringRef getSwiftRevision() {
#ifdef SWIFT_REVISION
return SWIFT_REVISION;
#else
return "";
#endif
}
} // end namespace version
} // end namespace swift