Skip to content

Commit c32fb8e

Browse files
committed
SE-0020: Swift Language Version Build Configuration
Introduce a new "swift" build configuration that guards declarations and statements with a language version - if the current language version of the compiler is at least that version, the block will parse as normal. For inactive blocks, the code will not be parsed an no diagnostics will be emitted there. Example: #if swift(>=2.2) print("Active") #else this code will not parse or emit diagnostics #endif https://github.com/apple/swift-evolution/blob/master/proposals/0020-if-swift-version.md rdar://problem/19823607
1 parent ec172cd commit c32fb8e

File tree

10 files changed

+263
-133
lines changed

10 files changed

+263
-133
lines changed

include/swift/AST/DiagnosticsParse.def

+15-10
Original file line numberDiff line numberDiff line change
@@ -1197,33 +1197,38 @@ ERROR(unsupported_build_config_binary_expression,none,
11971197
ERROR(unsupported_build_config_unary_expression,none,
11981198
"expected unary '!' expression", ())
11991199
ERROR(unsupported_target_config_expression,none,
1200-
"unexpected target configuration expression (expected 'os' or 'arch')",())
1200+
"unexpected target configuration expression (expected 'os', 'arch', or 'swift')",
1201+
())
1202+
ERROR(target_config_expected_one_argument,none,
1203+
"expected only one argument to target configuration expression",
1204+
())
12011205
ERROR(unsupported_target_config_runtime_argument,none,
12021206
"unexpected argument for the '_runtime' target configuration, "
12031207
"expected '_Native' or '_ObjC'", ())
12041208
ERROR(unsupported_target_config_argument,none,
1205-
"unexpected target configuration expression argument: expected %0",
1209+
"unexpected target configuration argument: expected %0",
12061210
(StringRef))
1211+
ERROR(unexpected_version_comparison_operator,none,
1212+
"expected '>=' prefix operator on a version requirement",
1213+
())
12071214
ERROR(unsupported_config_conditional_expression_type,none,
12081215
"unexpected configuration expression type", ())
12091216
ERROR(unsupported_config_integer,none,
12101217
"'%0' is not a valid configuration option, use '%1'",
12111218
(StringRef, StringRef))
1212-
ERROR(compiler_version_component_not_number,none,
1213-
"compiler version component is not a number", ())
1219+
ERROR(version_component_not_number,none,
1220+
"version component contains non-numeric characters", ())
12141221
ERROR(compiler_version_too_many_components,none,
12151222
"compiler version must not have more than five components", ())
12161223
WARNING(unused_compiler_version_component,none,
12171224
"the second version component is not used for comparison", ())
1218-
ERROR(empty_compiler_version_component,none,
1219-
"found empty compiler version component", ())
1225+
ERROR(empty_version_component,none,
1226+
"found empty version component", ())
12201227
ERROR(compiler_version_component_out_of_range,none,
12211228
"compiler version component out of range: must be in [0, %0]",
12221229
(unsigned))
1223-
ERROR(empty_compiler_version_string,none,
1224-
"compiler version requirement is empty", ())
1225-
ERROR(cannot_combine_compiler_version,none,
1226-
"cannot combine _compiler_version with binary operators", ())
1230+
ERROR(empty_version_string,none,
1231+
"version requirement is empty", ())
12271232
WARNING(unknown_build_config,none,
12281233
"unknown %0 for build configuration '%1'",
12291234
(StringRef, StringRef))

include/swift/Basic/Version.h

+7-3
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,19 @@ class Version {
9797
/// Parse a generic version string of the format [0-9]+(.[0-9]+)*
9898
///
9999
/// Version components can be any unsigned 64-bit number.
100-
static Version parseVersionString(llvm::StringRef VersionString,
101-
SourceLoc Loc,
102-
DiagnosticEngine *Diags);
100+
static Optional<Version> parseVersionString(llvm::StringRef VersionString,
101+
SourceLoc Loc,
102+
DiagnosticEngine *Diags);
103103

104104
/// Returns a version from the currently defined SWIFT_COMPILER_VERSION.
105105
///
106106
/// If SWIFT_COMPILER_VERSION is undefined, this will return the empty
107107
/// compiler version.
108108
static Version getCurrentCompilerVersion();
109+
110+
/// Returns a version from the currently defined SWIFT_VERSION_MAJOR and
111+
/// SWIFT_VERSION_MAJOR.
112+
static Version getCurrentLanguageVersion();
109113
};
110114

111115
bool operator>=(const Version &lhs, const Version &rhs);

include/swift/Parse/ParserResult.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ enum class ConfigExprKind {
222222
Error,
223223
OS,
224224
Arch,
225+
LanguageVersion,
225226
CompilerVersion,
226227
Binary,
227228
Paren,
@@ -261,7 +262,9 @@ class ConfigParserState {
261262
bool shouldParse() const {
262263
if (Kind == ConfigExprKind::Error)
263264
return true;
264-
return ConditionActive || (Kind != ConfigExprKind::CompilerVersion);
265+
return ConditionActive ||
266+
(Kind != ConfigExprKind::CompilerVersion &&
267+
Kind != ConfigExprKind::LanguageVersion);
265268
}
266269

267270
static ConfigParserState error() {

lib/Basic/Version.cpp

+45-19
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ static void printFullRevisionString(raw_ostream &out) {
8484
void splitVersionComponents(
8585
SmallVectorImpl<std::pair<StringRef, SourceRange>> &SplitComponents,
8686
StringRef &VersionString, SourceLoc Loc,
87-
DiagnosticEngine *Diags) {
88-
SourceLoc Start = Loc.isValid() ? Loc.getAdvancedLoc(1) : Loc;
87+
DiagnosticEngine *Diags, bool skipQuote = false) {
88+
SourceLoc Start = (Loc.isValid() && skipQuote) ? Loc.getAdvancedLoc(1) : Loc;
8989
SourceLoc End = Start;
9090

9191
// Split the version string into tokens separated by the '.' character.
@@ -125,7 +125,8 @@ Version Version::parseCompilerVersionString(
125125
}
126126
};
127127

128-
splitVersionComponents(SplitComponents, VersionString, Loc, Diags);
128+
splitVersionComponents(SplitComponents, VersionString, Loc, Diags,
129+
/*skipQuote=*/true);
129130

130131
uint64_t ComponentNumber;
131132

@@ -137,7 +138,7 @@ Version Version::parseCompilerVersionString(
137138
// Version components can't be empty.
138139
if (SplitComponent.empty()) {
139140
if (Diags)
140-
Diags->diagnose(Range.Start, diag::empty_compiler_version_component);
141+
Diags->diagnose(Range.Start, diag::empty_version_component);
141142
else
142143
llvm_unreachable("Found empty compiler version component");
143144
continue;
@@ -165,7 +166,7 @@ Version Version::parseCompilerVersionString(
165166
continue;
166167
} else if (Diags) {
167168
Diags->diagnose(Range.Start,
168-
diag::compiler_version_component_not_number);
169+
diag::version_component_not_number);
169170
} else {
170171
llvm_unreachable("Invalid character in _compiler_version build configuration");
171172
}
@@ -182,18 +183,25 @@ Version Version::parseCompilerVersionString(
182183
return CV;
183184
}
184185

185-
Version Version::parseVersionString(StringRef VersionString,
186-
SourceLoc Loc,
187-
DiagnosticEngine *Diags) {
188-
Version CV;
186+
Optional<Version> Version::parseVersionString(StringRef VersionString,
187+
SourceLoc Loc,
188+
DiagnosticEngine *Diags) {
189+
Version TheVersion;
189190
SmallString<16> digits;
190191
llvm::raw_svector_ostream OS(digits);
191192
SmallVector<std::pair<StringRef, SourceRange>, 5> SplitComponents;
192193
// Skip over quote character in string literal.
193194

195+
if (VersionString.empty()) {
196+
if (Diags)
197+
Diags->diagnose(Loc, diag::empty_version_string);
198+
return None;
199+
}
200+
194201
splitVersionComponents(SplitComponents, VersionString, Loc, Diags);
195202

196203
uint64_t ComponentNumber;
204+
bool isValidVersion = true;
197205

198206
for (size_t i = 0; i < SplitComponents.size(); ++i) {
199207
StringRef SplitComponent;
@@ -203,37 +211,55 @@ Version Version::parseVersionString(StringRef VersionString,
203211
// Version components can't be empty.
204212
if (SplitComponent.empty()) {
205213
if (Diags)
206-
Diags->diagnose(Range.Start, diag::empty_compiler_version_component);
207-
else
208-
llvm_unreachable("Found empty compiler version component");
214+
Diags->diagnose(Range.Start, diag::empty_version_component);
215+
216+
isValidVersion = false;
209217
continue;
210218
}
211219

212220
// All other version components must be numbers.
213221
if (!SplitComponent.getAsInteger(10, ComponentNumber)) {
214-
CV.Components.push_back(ComponentNumber);
222+
TheVersion.Components.push_back(ComponentNumber);
215223
continue;
216-
} else if (Diags) {
217-
Diags->diagnose(Range.Start,
218-
diag::compiler_version_component_not_number);
219224
} else {
220-
llvm_unreachable("Invalid character in _compiler_version build configuration");
225+
if (Diags)
226+
Diags->diagnose(Range.Start,
227+
diag::version_component_not_number);
228+
isValidVersion = false;
221229
}
222230
}
223231

224-
return CV;
232+
return isValidVersion ? Optional<Version>(TheVersion) : None;
225233
}
226234

227235

228236
Version Version::getCurrentCompilerVersion() {
229237
#ifdef SWIFT_COMPILER_VERSION
230-
return Version::parseVersionString(
238+
auto currentVersion = Version::parseVersionString(
231239
SWIFT_COMPILER_VERSION, SourceLoc(), nullptr);
240+
assert(currentVersion.hasValue() &&
241+
"Embedded Swift language version couldn't be parsed: '"
242+
SWIFT_COMPILER_VERSION
243+
"'");
244+
return currentVersion;
232245
#else
233246
return Version();
234247
#endif
235248
}
236249

250+
Version Version::getCurrentLanguageVersion() {
251+
#ifndef SWIFT_VERSION_STRING
252+
#error Swift language version is not set!
253+
#endif
254+
auto currentVersion = Version::parseVersionString(
255+
SWIFT_VERSION_STRING, SourceLoc(), nullptr);
256+
assert(currentVersion.hasValue() &&
257+
"Embedded Swift language version couldn't be parsed: '"
258+
SWIFT_VERSION_STRING
259+
"'");
260+
return currentVersion.getValue();
261+
}
262+
237263
std::string Version::str() const {
238264
std::string VersionString;
239265
llvm::raw_string_ostream OS(VersionString);

lib/Parse/ParseStmt.cpp

+49-10
Original file line numberDiff line numberDiff line change
@@ -1534,12 +1534,6 @@ ConfigParserState Parser::evaluateConfigConditionExpr(Expr *configExpr) {
15341534
if (name.equals("||") || name.equals("&&")) {
15351535
auto rhs = evaluateConfigConditionExpr(elements[iOperand]);
15361536

1537-
if (result.getKind() == ConfigExprKind::CompilerVersion
1538-
|| rhs.getKind() == ConfigExprKind::CompilerVersion) {
1539-
diagnose(UDREOp->getLoc(), diag::cannot_combine_compiler_version);
1540-
return ConfigParserState::error();
1541-
}
1542-
15431537
if (name.equals("||")) {
15441538
result = result || rhs;
15451539
if (result.isConditionActive())
@@ -1594,18 +1588,25 @@ ConfigParserState Parser::evaluateConfigConditionExpr(Expr *configExpr) {
15941588
if (auto *CE = dyn_cast<CallExpr>(configExpr)) {
15951589
// look up target config, and compare value
15961590
auto fnNameExpr = dyn_cast<UnresolvedDeclRefExpr>(CE->getFn());
1597-
1591+
15981592
// Get the arg, which should be in a paren expression.
1599-
auto *PE = dyn_cast<ParenExpr>(CE->getArg());
1600-
if (!fnNameExpr || !PE) {
1593+
if (!fnNameExpr) {
16011594
diagnose(CE->getLoc(), diag::unsupported_target_config_expression);
16021595
return ConfigParserState::error();
16031596
}
16041597

16051598
auto fnName = fnNameExpr->getName().getBaseName().str();
16061599

1600+
auto *PE = dyn_cast<ParenExpr>(CE->getArg());
1601+
if (!PE) {
1602+
auto diag = diagnose(CE->getLoc(),
1603+
diag::target_config_expected_one_argument);
1604+
return ConfigParserState::error();
1605+
}
1606+
16071607
if (!fnName.equals("arch") && !fnName.equals("os") &&
16081608
!fnName.equals("_runtime") &&
1609+
!fnName.equals("swift") &&
16091610
!fnName.equals("_compiler_version")) {
16101611
diagnose(CE->getLoc(), diag::unsupported_target_config_expression);
16111612
return ConfigParserState::error();
@@ -1614,7 +1615,7 @@ ConfigParserState Parser::evaluateConfigConditionExpr(Expr *configExpr) {
16141615
if (fnName.equals("_compiler_version")) {
16151616
if (auto SLE = dyn_cast<StringLiteralExpr>(PE->getSubExpr())) {
16161617
if (SLE->getValue().empty()) {
1617-
diagnose(CE->getLoc(), diag::empty_compiler_version_string);
1618+
diagnose(CE->getLoc(), diag::empty_version_string);
16181619
return ConfigParserState::error();
16191620
}
16201621
auto versionRequirement =
@@ -1630,6 +1631,44 @@ ConfigParserState Parser::evaluateConfigConditionExpr(Expr *configExpr) {
16301631
"string literal");
16311632
return ConfigParserState::error();
16321633
}
1634+
} else if(fnName.equals("swift")) {
1635+
auto PUE = dyn_cast<PrefixUnaryExpr>(PE->getSubExpr());
1636+
if (!PUE) {
1637+
diagnose(PE->getSubExpr()->getLoc(),
1638+
diag::unsupported_target_config_argument,
1639+
"a unary comparison, such as '>=2.2'");
1640+
return ConfigParserState::error();
1641+
}
1642+
1643+
auto prefix = dyn_cast<UnresolvedDeclRefExpr>(PUE->getFn());
1644+
auto versionArg = PUE->getArg();
1645+
auto versionStartLoc = versionArg->getStartLoc();
1646+
auto endLoc = Lexer::getLocForEndOfToken(SourceMgr,
1647+
versionArg->getSourceRange().End);
1648+
CharSourceRange versionCharRange(SourceMgr, versionStartLoc,
1649+
endLoc);
1650+
auto versionString = SourceMgr.extractText(versionCharRange);
1651+
1652+
auto versionRequirement =
1653+
version::Version::parseVersionString(versionString,
1654+
versionStartLoc,
1655+
&Diags);
1656+
1657+
if (!versionRequirement.hasValue())
1658+
return ConfigParserState::error();
1659+
1660+
auto thisVersion = version::Version::getCurrentLanguageVersion();
1661+
1662+
if (!prefix->getName().getBaseName().str().equals(">=")) {
1663+
diagnose(PUE->getFn()->getLoc(),
1664+
diag::unexpected_version_comparison_operator)
1665+
.fixItReplace(PUE->getFn()->getLoc(), ">=");
1666+
return ConfigParserState::error();
1667+
}
1668+
1669+
auto VersionNewEnough = thisVersion >= versionRequirement.getValue();
1670+
return ConfigParserState(VersionNewEnough,
1671+
ConfigExprKind::LanguageVersion);
16331672
} else {
16341673
if (auto UDRE = dyn_cast<UnresolvedDeclRefExpr>(PE->getSubExpr())) {
16351674
// The sub expression should be an UnresolvedDeclRefExpr (we won't

test/BuildConfigurations/basicParseErrors.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var x = 0
88
var y = 0
99
#endif
1010

11-
#if swift(FOO) // expected-error {{unexpected target configuration expression (expected 'os' or 'arch')}}
11+
#if foo(BAR) // expected-error {{unexpected target configuration expression (expected 'os', 'arch', or 'swift')}}
1212
var z = 0
1313
#endif
1414

@@ -28,7 +28,7 @@ func h() {}
2828
#endif /* bbb */
2929

3030
#if foo.bar()
31-
.baz() // expected-error {{unexpected target configuration expression (expected 'os' or 'arch')}}
31+
.baz() // expected-error {{unexpected target configuration expression (expected 'os', 'arch', or 'swift')}}
3232

3333
#endif
3434

0 commit comments

Comments
 (0)