Skip to content

Commit c6fc53e

Browse files
committed
[Sema] Define availability via compiler flag
Introduce availability macros defined by a frontend flag. This feature makes it possible to set the availability versions at the moment of compilation instead of having it hard coded in the sources. It can be used by projects with a need to change the availability depending on the compilation context while using the same sources. The availability macro is defined with the `-define-availability` flag: swift MyLib.swift -define-availability "_iOS8Aligned:macOS 10.10, iOS 8.0" .. The macro can be used in code instead of a platform name and version: @available(_iOS8Aligned, *) public func foo() {} rdar://problem/65612624
1 parent 327f4f8 commit c6fc53e

File tree

10 files changed

+379
-16
lines changed

10 files changed

+379
-16
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,22 @@ WARNING(attr_availability_nonspecific_platform_unexpected_version,none,
14311431
"unexpected version number in '%0' attribute for non-specific platform "
14321432
"'*'", (StringRef))
14331433

1434+
// availability macro
1435+
ERROR(attr_availability_wildcard_in_macro, none,
1436+
"future platforms identified by '*' cannot be used in "
1437+
"an availability macro definition", ())
1438+
ERROR(attr_availability_missing_macro_name,none,
1439+
"expected an identifier to begin an availability macro definition", ())
1440+
ERROR(attr_availability_expected_colon_macro,none,
1441+
"expected ':' after '%0' in availability macro definition",
1442+
(StringRef))
1443+
ERROR(attr_availability_unknown_version,none,
1444+
"reference to undefined version '%0' for availability macro '%1'",
1445+
(StringRef, StringRef))
1446+
ERROR(attr_availability_duplicate,none,
1447+
"duplicate definition of availability macro '%0' for version '%1'",
1448+
(StringRef, StringRef))
1449+
14341450
// originallyDefinedIn
14351451
ERROR(originally_defined_in_missing_rparen,none,
14361452
"expected ')' in @_originallyDefinedIn argument list", ())

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ namespace swift {
111111
/// when using RequireExplicitAvailability.
112112
std::string RequireExplicitAvailabilityTarget;
113113

114+
// Availability macros definitions to be expanded at parsing.
115+
SmallVector<StringRef, 4> AvailabilityMacros;
116+
114117
/// If false, '#file' evaluates to the full path rather than a
115118
/// human-readable string.
116119
bool EnableConcisePoundFile = false;

include/swift/Option/Options.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,11 @@ def require_explicit_availability_target : Separate<["-"], "require-explicit-ava
414414
HelpText<"Suggest fix-its adding @available(<target>, *) to public declarations without availability">,
415415
MetaVarName<"<target>">;
416416

417+
def define_availability : Separate<["-"], "define-availability">,
418+
Flags<[FrontendOption, NoInteractiveOption]>,
419+
HelpText<"Define an availability macro in the format 'macroName : iOS 13.0, macOS 10.15'">,
420+
MetaVarName<"<macro>">;
421+
417422
def module_name : Separate<["-"], "module-name">,
418423
Flags<[FrontendOption, ModuleInterfaceOption]>,
419424
HelpText<"Name of the module to build">;

include/swift/Parse/Parser.h

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,22 @@ class Parser {
397397
/// Current syntax parsing context where call backs should be directed to.
398398
SyntaxParsingContext *SyntaxContext;
399399

400+
/// Maps of macro name and version to availability specifications.
401+
typedef llvm::DenseMap<llvm::VersionTuple,
402+
SmallVector<AvailabilitySpec *, 4>>
403+
AvailabilityMacroVersionMap;
404+
typedef llvm::DenseMap<StringRef, AvailabilityMacroVersionMap>
405+
AvailabilityMacroMap;
406+
407+
/// Cache of the availability macros parsed from the command line arguments.
408+
/// Organized as two nested \c DenseMap keyed first on the macro name then
409+
/// the macro version. This structure allows to peek at macro names before
410+
/// parsing a version tuple.
411+
AvailabilityMacroMap AvailabilityMacros;
412+
413+
/// Has \c AvailabilityMacros been computed?
414+
bool AvailabilityMacrosComputed = false;
415+
400416
public:
401417
Parser(unsigned BufferID, SourceFile &SF, DiagnosticEngine* LexerDiags,
402418
SILParserStateBase *SIL, PersistentParserState *PersistentState,
@@ -1682,9 +1698,38 @@ class Parser {
16821698
//===--------------------------------------------------------------------===//
16831699
// Availability Specification Parsing
16841700

1685-
/// Parse a comma-separated list of availability specifications.
1701+
/// Parse a comma-separated list of availability specifications. Try to
1702+
/// expand availability macros when /p ParsingMacroDefinition is false.
1703+
ParserStatus
1704+
parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs,
1705+
bool ParsingMacroDefinition = false);
1706+
1707+
/// Does the current matches an argument macro name? Parsing compiler
1708+
/// arguments as required without consuming tokens from the source file
1709+
/// parser.
1710+
bool peekAvailabilityMacroName();
1711+
1712+
/// Try to parse a reference to an availability macro and append its result
1713+
/// to \p Specs. If the current token doesn't match a macro name, return
1714+
/// a success without appending anything to \c Specs.
1715+
ParserStatus
1716+
parseAvailabilityMacro(SmallVectorImpl<AvailabilitySpec *> &Specs);
1717+
1718+
/// Parse the availability macros definitions passed as arguments.
1719+
void parseAllAvailabilityMacroArguments();
1720+
1721+
/// Result of parsing an availability macro definition.
1722+
struct AvailabilityMacroDefinition {
1723+
StringRef Name;
1724+
llvm::VersionTuple Version;
1725+
SmallVector<AvailabilitySpec *, 4> Specs;
1726+
};
1727+
1728+
/// Parse an availability macro definition from a command line argument.
1729+
/// This function should be called on a Parser set up on the command line
1730+
/// argument code.
16861731
ParserStatus
1687-
parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs);
1732+
parseAvailabilityMacroDefinition(AvailabilityMacroDefinition &Result);
16881733

16891734
ParserResult<AvailabilitySpec> parseAvailabilitySpec();
16901735
ParserResult<PlatformVersionConstraintAvailabilitySpec>

lib/Frontend/CompilerInvocation.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,10 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
504504
}
505505
}
506506

507+
for (const Arg *A : Args.filtered(OPT_define_availability)) {
508+
Opts.AvailabilityMacros.push_back(A->getValue());
509+
}
510+
507511
if (const Arg *A = Args.getLastArg(OPT_value_recursion_threshold)) {
508512
unsigned threshold;
509513
if (StringRef(A->getValue()).getAsInteger(10, threshold)) {

lib/Parse/ParseDecl.cpp

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,115 @@ void Parser::parseObjCSelector(SmallVector<Identifier, 4> &Names,
13721372
}
13731373
}
13741374

1375+
bool Parser::peekAvailabilityMacroName() {
1376+
parseAllAvailabilityMacroArguments();
1377+
AvailabilityMacroMap Map = AvailabilityMacros;
1378+
1379+
StringRef MacroName = Tok.getText();
1380+
return Map.find(MacroName) != Map.end();
1381+
}
1382+
1383+
ParserStatus
1384+
Parser::parseAvailabilityMacro(SmallVectorImpl<AvailabilitySpec *> &Specs) {
1385+
// Get the macros from the compiler arguments.
1386+
parseAllAvailabilityMacroArguments();
1387+
AvailabilityMacroMap Map = AvailabilityMacros;
1388+
1389+
StringRef MacroName = Tok.getText();
1390+
auto NameMatch = Map.find(MacroName);
1391+
if (NameMatch == Map.end())
1392+
return makeParserSuccess(); // No match, it could be a standard platform.
1393+
1394+
consumeToken();
1395+
1396+
llvm::VersionTuple Version;
1397+
SourceRange VersionRange;
1398+
if (Tok.isAny(tok::integer_literal, tok::floating_literal)) {
1399+
if (parseVersionTuple(Version, VersionRange,
1400+
diag::avail_query_expected_version_number))
1401+
return makeParserError();
1402+
}
1403+
1404+
auto VersionMatch = NameMatch->getSecond().find(Version);
1405+
if (VersionMatch == NameMatch->getSecond().end()) {
1406+
diagnose(PreviousLoc, diag::attr_availability_unknown_version,
1407+
Version.getAsString(), MacroName);
1408+
return makeParserError(); // Failed to match the version, that's an error.
1409+
}
1410+
1411+
Specs.append(VersionMatch->getSecond().begin(),
1412+
VersionMatch->getSecond().end());
1413+
1414+
return makeParserSuccess();
1415+
}
1416+
1417+
void Parser::parseAllAvailabilityMacroArguments() {
1418+
1419+
if (AvailabilityMacrosComputed) return;
1420+
1421+
AvailabilityMacroMap Map;
1422+
1423+
SourceManager &SM = Context.SourceMgr;
1424+
const LangOptions &LangOpts = Context.LangOpts;
1425+
1426+
for (StringRef macro: LangOpts.AvailabilityMacros) {
1427+
1428+
// Create temporary parser.
1429+
int bufferID = SM.addMemBufferCopy(macro,
1430+
"-define-availability argument");
1431+
swift::ParserUnit PU(SM,
1432+
SourceFileKind::Main, bufferID,
1433+
LangOpts,
1434+
TypeCheckerOptions(), "unknown");
1435+
1436+
ForwardingDiagnosticConsumer PDC(Context.Diags);
1437+
PU.getDiagnosticEngine().addConsumer(PDC);
1438+
1439+
// Parse the argument.
1440+
AvailabilityMacroDefinition ParsedMacro;
1441+
ParserStatus Status =
1442+
PU.getParser().parseAvailabilityMacroDefinition(ParsedMacro);
1443+
if (Status.isError())
1444+
continue;
1445+
1446+
// Copy the Specs to the requesting ASTContext from the temporary context
1447+
// that parsed the argument.
1448+
auto SpecsCopy = SmallVector<AvailabilitySpec*, 4>();
1449+
for (auto *Spec : ParsedMacro.Specs)
1450+
if (auto *PlatformVersionSpec =
1451+
dyn_cast<PlatformVersionConstraintAvailabilitySpec>(Spec)) {
1452+
auto SpecCopy =
1453+
new (Context) PlatformVersionConstraintAvailabilitySpec(
1454+
*PlatformVersionSpec);
1455+
SpecsCopy.push_back(SpecCopy);
1456+
}
1457+
1458+
ParsedMacro.Specs = SpecsCopy;
1459+
1460+
// Find the macro info by name.
1461+
AvailabilityMacroVersionMap MacroDefinition;
1462+
auto NameMatch = Map.find(ParsedMacro.Name);
1463+
if (NameMatch != Map.end()) {
1464+
MacroDefinition = NameMatch->getSecond();
1465+
}
1466+
1467+
// Set the macro info by version.
1468+
auto PreviousEntry =
1469+
MacroDefinition.insert({ParsedMacro.Version, ParsedMacro.Specs});
1470+
if (!PreviousEntry.second) {
1471+
diagnose(PU.getParser().PreviousLoc, diag::attr_availability_duplicate,
1472+
ParsedMacro.Name, ParsedMacro.Version.getAsString());
1473+
}
1474+
1475+
// Save back the macro spec.
1476+
Map.erase(ParsedMacro.Name);
1477+
Map.insert({ParsedMacro.Name, MacroDefinition});
1478+
}
1479+
1480+
AvailabilityMacros = Map;
1481+
AvailabilityMacrosComputed = true;
1482+
}
1483+
13751484
bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
13761485
DeclAttrKind DK) {
13771486
// Ok, it is a valid attribute, eat it, and then process it.
@@ -1975,7 +2084,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
19752084
StringRef Platform = Tok.getText();
19762085

19772086
if (Platform != "*" &&
1978-
peekToken().isAny(tok::integer_literal, tok::floating_literal)) {
2087+
(peekToken().isAny(tok::integer_literal, tok::floating_literal) ||
2088+
peekAvailabilityMacroName())) {
19792089
// We have the short form of available: @available(iOS 8.0.1, *)
19802090
SmallVector<AvailabilitySpec *, 5> Specs;
19812091
ParserStatus Status = parseAvailabilitySpecList(Specs);

lib/Parse/ParseStmt.cpp

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,8 +1183,10 @@ static void parseGuardedPattern(Parser &P, GuardedPattern &result,
11831183

11841184
/// Validate availability spec list, emitting diagnostics if necessary and removing
11851185
/// specs for unrecognized platforms.
1186-
static void validateAvailabilitySpecList(Parser &P,
1187-
SmallVectorImpl<AvailabilitySpec *> &Specs) {
1186+
static void
1187+
validateAvailabilitySpecList(Parser &P,
1188+
SmallVectorImpl<AvailabilitySpec *> &Specs,
1189+
bool ParsingMacroDefinition) {
11881190
llvm::SmallSet<PlatformKind, 4> Platforms;
11891191
bool HasOtherPlatformSpec = false;
11901192

@@ -1232,8 +1234,13 @@ static void validateAvailabilitySpecList(Parser &P,
12321234
}
12331235
}
12341236

1235-
if (!HasOtherPlatformSpec) {
1236-
SourceLoc InsertWildcardLoc = Specs.back()->getSourceRange().End;
1237+
if (ParsingMacroDefinition) {
1238+
if (HasOtherPlatformSpec) {
1239+
SourceLoc InsertWildcardLoc = P.PreviousLoc;
1240+
P.diagnose(InsertWildcardLoc, diag::attr_availability_wildcard_in_macro);
1241+
}
1242+
} else if (!HasOtherPlatformSpec) {
1243+
SourceLoc InsertWildcardLoc = P.PreviousLoc;
12371244
P.diagnose(InsertWildcardLoc, diag::availability_query_wildcard_required)
12381245
.fixItInsertAfter(InsertWildcardLoc, ", *");
12391246
}
@@ -1285,7 +1292,40 @@ ParserResult<PoundAvailableInfo> Parser::parseStmtConditionPoundAvailable() {
12851292
}
12861293

12871294
ParserStatus
1288-
Parser::parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs) {
1295+
Parser::parseAvailabilityMacroDefinition(AvailabilityMacroDefinition &Result) {
1296+
1297+
// Prime the lexer.
1298+
if (Tok.is(tok::NUM_TOKENS))
1299+
consumeTokenWithoutFeedingReceiver();
1300+
1301+
if (!Tok.isIdentifierOrUnderscore()) {
1302+
diagnose(Tok, diag::attr_availability_missing_macro_name);
1303+
return makeParserError();
1304+
}
1305+
1306+
Result.Name = Tok.getText();
1307+
consumeToken();
1308+
1309+
if (Tok.isAny(tok::integer_literal, tok::floating_literal)) {
1310+
SourceRange VersionRange;
1311+
if (parseVersionTuple(Result.Version, VersionRange,
1312+
diag::avail_query_expected_version_number)) {
1313+
return makeParserError();
1314+
}
1315+
}
1316+
1317+
if (!consumeIf(tok::colon)) {
1318+
diagnose(Tok, diag::attr_availability_expected_colon_macro, Result.Name);
1319+
return makeParserError();
1320+
}
1321+
1322+
return parseAvailabilitySpecList(Result.Specs,
1323+
/*ParsingMacroDefinition=*/true);
1324+
}
1325+
1326+
ParserStatus
1327+
Parser::parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs,
1328+
bool ParsingMacroDefinition) {
12891329
SyntaxParsingContext AvailabilitySpecContext(
12901330
SyntaxContext, SyntaxKind::AvailabilitySpecList);
12911331
ParserStatus Status = makeParserSuccess();
@@ -1295,14 +1335,34 @@ Parser::parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs) {
12951335
while (1) {
12961336
SyntaxParsingContext AvailabilityEntryContext(
12971337
SyntaxContext, SyntaxKind::AvailabilityArgument);
1298-
auto SpecResult = parseAvailabilitySpec();
1299-
if (auto *Spec = SpecResult.getPtrOrNull()) {
1300-
Specs.push_back(Spec);
1301-
} else {
1302-
if (SpecResult.hasCodeCompletion()) {
1303-
return makeParserCodeCompletionStatus();
1338+
1339+
// First look for a macro as we need Specs for the expansion.
1340+
bool MatchedAMacro = false;
1341+
if (!ParsingMacroDefinition && Tok.is(tok::identifier)) {
1342+
SmallVector<AvailabilitySpec *, 4> MacroSpecs;
1343+
ParserStatus MacroStatus = parseAvailabilityMacro(MacroSpecs);
1344+
1345+
if (MacroStatus.isError()) {
1346+
// There's a parsing error if the platform name matches a macro
1347+
// but something goes wrong after.
1348+
Status.setIsParseError();
1349+
MatchedAMacro = true;
1350+
} else {
1351+
MatchedAMacro = !MacroSpecs.empty();
1352+
Specs.append(MacroSpecs.begin(), MacroSpecs.end());
1353+
}
1354+
}
1355+
1356+
if (!MatchedAMacro) {
1357+
auto SpecResult = parseAvailabilitySpec();
1358+
if (auto *Spec = SpecResult.getPtrOrNull()) {
1359+
Specs.push_back(Spec);
1360+
} else {
1361+
if (SpecResult.hasCodeCompletion()) {
1362+
return makeParserCodeCompletionStatus();
1363+
}
1364+
Status.setIsParseError();
13041365
}
1305-
Status.setIsParseError();
13061366
}
13071367

13081368
// We don't allow binary operators to combine specs.
@@ -1362,7 +1422,7 @@ Parser::parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs) {
13621422
}
13631423

13641424
if (Status.isSuccess() && !Status.hasCodeCompletion())
1365-
validateAvailabilitySpecList(*this, Specs);
1425+
validateAvailabilitySpecList(*this, Specs, ParsingMacroDefinition);
13661426

13671427
return Status;
13681428
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-swift-frontend -swift-version 5 -enable-library-evolution -typecheck -module-name Test -emit-module-interface-path %t/Test.swiftinterface %s -define-availability "_iOS8Aligned:macOS 10.10, iOS 8.0" -define-availability "_iOS9Aligned:macOS 10.11, iOS 9.0" -define-availability "_iOS9:iOS 9.0" -define-availability "_macOS10_11:macOS 10.11" -define-availability "_myProject 1.0:macOS 10.11" -define-availability "_myProject 2.5:macOS 10.12"
4+
// RUN: %FileCheck %s < %t/Test.swiftinterface
5+
6+
@available(_iOS8Aligned, *)
7+
public func onMacOS10_10() {}
8+
// CHECK: @available(macOS 10.10, iOS 8.0, *)
9+
// CHECK-NEXT: public func onMacOS10_10
10+
11+
@available(_iOS9Aligned, *)
12+
public func onMacOS10_11() {}
13+
// CHECK: @available(macOS 10.11, iOS 9.0, *)
14+
// CHECK-NEXT: public func onMacOS10_11()
15+
16+
@available(_iOS9, _macOS10_11, tvOS 11.0, *)
17+
public func composed() {}
18+
// CHECK: @available(iOS 9.0, macOS 10.11, tvOS 11.0, *)
19+
// CHECK-NEXT: public func composed()
20+
21+
@available(_myProject 1.0, *)
22+
public func onMyProjectV1() {}
23+
// CHECK: @available(macOS 10.11, *)
24+
// CHECK-NEXT: public func onMyProjectV1
25+
26+
@available(_myProject 2.5, *)
27+
public func onMyProjectV2_5() {}
28+
// CHECK: @available(macOS 10.12, *)
29+
// CHECK-NEXT: public func onMyProjectV2_5

0 commit comments

Comments
 (0)