Skip to content

Commit ed65963

Browse files
authored
Merge pull request #79628 from tshortli/parse-custom-availability-queries
AST/Parse: Parse custom availability domain specs in `if #available(...)`
2 parents d98d6fd + aaa0e0a commit ed65963

19 files changed

+202
-77
lines changed

include/swift/AST/AvailabilityDomain.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ class AvailabilityDomain final {
208208
/// version ranges.
209209
bool isVersioned() const;
210210

211+
/// Returns true if the domain supports `#available`/`#unavailable` queries.
212+
bool supportsQueries() const;
213+
211214
/// Returns true if this domain is considered active in the current
212215
/// compilation context.
213216
bool isActive(const ASTContext &ctx) const;

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6754,8 +6754,10 @@ NOTE(type_eraser_init_spi,none,
67546754
//------------------------------------------------------------------------------
67556755

67566756
ERROR(availability_must_occur_alone, none,
6757-
"'%0' version-availability must be specified alone", (StringRef))
6758-
6757+
"%0 %select{|version }1availability must be specified alone",
6758+
(StringRef, bool))
6759+
ERROR(availability_unexpected_version, none,
6760+
"unexpected version number for %0", (StringRef))
67596761
WARNING(availability_unrecognized_platform_name,
67606762
PointsToFirstBadToken, "unrecognized platform name %0", (Identifier))
67616763
WARNING(availability_suggest_platform_name,
@@ -6773,11 +6775,8 @@ WARNING(attr_availability_expected_version_spec, none,
67736775
"expected 'introduced', 'deprecated', or 'obsoleted' in '%0' attribute "
67746776
"for platform '%1'", (StringRef, StringRef))
67756777
ERROR(attr_availability_requires_custom_availability, none,
6776-
"%0 requires -enable-experimental-feature CustomAvailability",
6777-
(Identifier))
6778-
WARNING(attr_availability_unexpected_version,none,
6779-
"unexpected version number in '%0' attribute for '%1'",
6780-
(const DeclAttribute, StringRef))
6778+
"%0 requires '-enable-experimental-feature CustomAvailability'",
6779+
(StringRef))
67816780

67826781
ERROR(availability_decl_unavailable, none,
67836782
"%0 is unavailable%select{ in %2|}1%select{|: %3}3",
@@ -7002,14 +7001,12 @@ ERROR(conformance_availability_only_version_newer, none,
70027001
// MARK: if #available(...)
70037002
//------------------------------------------------------------------------------
70047003

7005-
ERROR(availability_query_swift_not_allowed, none,
7006-
"Swift language version checks not allowed in %0(...)", (StringRef))
7007-
7008-
ERROR(availability_query_package_description_not_allowed, none,
7009-
"PackageDescription version checks not allowed in %0(...)", (StringRef))
7004+
ERROR(availability_query_not_allowed, none,
7005+
"%0 %select{|version }1checks not allowed in %2(...)",
7006+
(StringRef, bool, StringRef))
70107007

7011-
ERROR(availability_query_repeated_platform, none,
7012-
"version for '%0' already specified", (StringRef))
7008+
ERROR(availability_query_already_specified, none,
7009+
"%select{|version for }0'%1' already specified", (bool, StringRef))
70137010

70147011
ERROR(availability_query_wildcard_required, none,
70157012
"must handle potential future platforms with '*'", ())

lib/AST/Availability.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -835,8 +835,9 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
835835
versionSourceRange = semanticAttr.getObsoletedSourceRange();
836836

837837
diags
838-
.diagnose(attrLoc, diag::attr_availability_unexpected_version, attr,
839-
domainName)
838+
.diagnose(attrLoc, diag::availability_unexpected_version,
839+
domain->getNameForDiagnostics())
840+
.limitBehaviorIf(domain->isUniversal(), DiagnosticBehavior::Warning)
840841
.highlight(versionSourceRange);
841842
return std::nullopt;
842843
}
@@ -862,11 +863,18 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
862863
case AvailableAttr::Kind::Default:
863864
break;
864865
}
866+
}
865867

866-
if (!hasVersionSpec) {
867-
diags.diagnose(attrLoc, diag::attr_availability_expected_version_spec,
868+
if (!hasVersionSpec && domain->isVersioned()) {
869+
switch (attr->getKind()) {
870+
case AvailableAttr::Kind::Default:
871+
diags.diagnose(domainLoc, diag::attr_availability_expected_version_spec,
868872
attrName, domainName);
869873
return std::nullopt;
874+
case AvailableAttr::Kind::Deprecated:
875+
case AvailableAttr::Kind::Unavailable:
876+
case AvailableAttr::Kind::NoAsync:
877+
break;
870878
}
871879
}
872880

lib/AST/AvailabilityDomain.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,19 @@ bool AvailabilityDomain::isVersioned() const {
7575
}
7676
}
7777

78+
bool AvailabilityDomain::supportsQueries() const {
79+
switch (getKind()) {
80+
case Kind::Universal:
81+
case Kind::Embedded:
82+
case Kind::SwiftLanguage:
83+
case Kind::PackageDescription:
84+
return false;
85+
case Kind::Platform:
86+
case Kind::Custom:
87+
return true;
88+
}
89+
}
90+
7891
bool AvailabilityDomain::isActive(const ASTContext &ctx) const {
7992
switch (getKind()) {
8093
case Kind::Universal:
@@ -94,7 +107,7 @@ bool AvailabilityDomain::isActive(const ASTContext &ctx) const {
94107
llvm::StringRef AvailabilityDomain::getNameForDiagnostics() const {
95108
switch (getKind()) {
96109
case Kind::Universal:
97-
return "";
110+
return "*";
98111
case Kind::SwiftLanguage:
99112
return "Swift";
100113
case Kind::PackageDescription:
@@ -247,7 +260,7 @@ AvailabilityDomainOrIdentifier::lookUpInDeclContext(
247260
!ctx.LangOpts.hasFeature(Feature::CustomAvailability) &&
248261
!declContext->isInSwiftinterface()) {
249262
diags.diagnose(loc, diag::attr_availability_requires_custom_availability,
250-
identifier);
263+
domain->getNameForDiagnostics());
251264
return std::nullopt;
252265
}
253266

lib/AST/AvailabilitySpec.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,32 @@ AvailabilitySpec *AvailabilitySpec::createWildcard(ASTContext &ctx,
2929
/*VersionStartLoc=*/{});
3030
}
3131

32+
static SourceRange getSpecSourceRange(SourceLoc domainLoc,
33+
SourceRange versionRange) {
34+
if (domainLoc.isInvalid())
35+
return SourceRange();
36+
37+
if (versionRange.isValid())
38+
return SourceRange(domainLoc, versionRange.End);
39+
40+
return SourceRange(domainLoc);
41+
}
42+
3243
AvailabilitySpec *AvailabilitySpec::createForDomain(ASTContext &ctx,
3344
AvailabilityDomain domain,
3445
SourceLoc loc,
3546
llvm::VersionTuple version,
3647
SourceRange versionRange) {
37-
DEBUG_ASSERT(!version.empty());
38-
return new (ctx) AvailabilitySpec(domain, SourceRange(loc, versionRange.End),
39-
version, versionRange.Start);
48+
return new (ctx)
49+
AvailabilitySpec(domain, getSpecSourceRange(loc, versionRange), version,
50+
versionRange.Start);
4051
}
4152

4253
AvailabilitySpec *AvailabilitySpec::createForDomainIdentifier(
4354
ASTContext &ctx, Identifier domainIdentifier, SourceLoc loc,
4455
llvm::VersionTuple version, SourceRange versionRange) {
45-
DEBUG_ASSERT(!version.empty());
4656
return new (ctx)
47-
AvailabilitySpec(domainIdentifier, SourceRange(loc, versionRange.End),
57+
AvailabilitySpec(domainIdentifier, getSpecSourceRange(loc, versionRange),
4858
version, versionRange.Start);
4959
}
5060

lib/ASTGen/Sources/ASTGen/Availability.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ extension ASTGenVisitor {
284284
var result: [BridgedAvailabilitySpec] = []
285285

286286
func handle(domainNode: TokenSyntax, versionNode: VersionTupleSyntax?) {
287+
// FIXME: [availability] Add support for custom domains
287288
let nameLoc = self.generateSourceLoc(domainNode)
288289
let version = self.generate(versionTuple: versionNode)
289290
let versionRange = self.generateSourceRange(versionNode)

lib/Parse/ParseExpr.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3715,9 +3715,11 @@ ParserResult<AvailabilitySpec> Parser::parseAvailabilitySpec() {
37153715
llvm::VersionTuple Version;
37163716
SourceRange VersionRange;
37173717

3718-
if (parseVersionTuple(Version, VersionRange,
3719-
diag::avail_query_expected_version_number)) {
3720-
return nullptr;
3718+
if (Tok.isAny(tok::integer_literal, tok::floating_literal)) {
3719+
if (parseVersionTuple(Version, VersionRange,
3720+
diag::avail_query_expected_version_number)) {
3721+
return nullptr;
3722+
}
37213723
}
37223724

37233725
return makeParserResult(AvailabilitySpec::createForDomainIdentifier(

lib/Parse/ParseStmt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1481,7 +1481,7 @@ Parser::parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs,
14811481
diag::avail_query_meant_introduced)
14821482
.fixItInsert(PlatformNameEndLoc, ", introduced:");
14831483
}
1484-
1484+
consumeToken();
14851485
Status.setIsParseError();
14861486
break;
14871487
}

lib/Sema/MiscDiagnostics.cpp

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "swift/AST/ASTWalker.h"
2424
#include "swift/AST/AvailabilitySpec.h"
2525
#include "swift/AST/ConformanceLookup.h"
26+
#include "swift/AST/DiagnosticsParse.h"
2627
#include "swift/AST/DiagnosticsSema.h"
2728
#include "swift/AST/ExistentialLayout.h"
2829
#include "swift/AST/Expr.h"
@@ -5130,6 +5131,8 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
51305131
StringRef queryName =
51315132
info->isUnavailability() ? "#unavailable" : "#available";
51325133

5134+
bool hasValidSpecs = false;
5135+
bool allValidSpecsArePlatform = true;
51335136
std::optional<SourceLoc> wildcardLoc;
51345137
llvm::SmallSet<AvailabilityDomain, 8> seenDomains;
51355138
for (auto spec : info->getSemanticAvailabilitySpecs(DC)) {
@@ -5140,24 +5143,44 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
51405143
}
51415144

51425145
auto domain = spec.getDomain();
5146+
auto loc = parsedSpec->getStartLoc();
5147+
bool hasVersion = !spec.getVersion().empty();
51435148

5144-
if (!domain.isPlatform()) {
5145-
diags.diagnose(
5146-
parsedSpec->getStartLoc(),
5147-
domain.isSwiftLanguage()
5148-
? diag::availability_query_swift_not_allowed
5149-
: diag::availability_query_package_description_not_allowed,
5150-
queryName);
5149+
if (!domain.supportsQueries()) {
5150+
diags.diagnose(loc, diag::availability_query_not_allowed,
5151+
domain.getNameForDiagnostics(), hasVersion, queryName);
51515152
return true;
51525153
}
51535154

5154-
// Diagnose duplicate platforms.
5155+
if (!domain.isPlatform() && info->getQueries().size() > 1) {
5156+
diags.diagnose(loc, diag::availability_must_occur_alone,
5157+
domain.getNameForDiagnostics(), hasVersion);
5158+
return true;
5159+
}
5160+
5161+
if (domain.isVersioned()) {
5162+
if (!hasVersion) {
5163+
diags.diagnose(loc, diag::avail_query_expected_version_number);
5164+
return true;
5165+
}
5166+
} else if (hasVersion) {
5167+
diags
5168+
.diagnose(loc, diag::availability_unexpected_version,
5169+
domain.getNameForDiagnostics())
5170+
.highlight(parsedSpec->getVersionSrcRange());
5171+
return true;
5172+
}
5173+
5174+
// Diagnose duplicate domains.
51555175
if (!seenDomains.insert(domain).second) {
5156-
diags.diagnose(parsedSpec->getStartLoc(),
5157-
diag::availability_query_repeated_platform,
5158-
domain.getNameForAttributePrinting());
5176+
diags.diagnose(loc, diag::availability_query_already_specified,
5177+
domain.isVersioned(), domain.getNameForDiagnostics());
51595178
return true;
51605179
}
5180+
5181+
hasValidSpecs = true;
5182+
if (!domain.isPlatform())
5183+
allValidSpecsArePlatform = false;
51615184
}
51625185

51635186
if (info->isUnavailability()) {
@@ -5167,7 +5190,7 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
51675190
diag::unavailability_query_wildcard_not_required)
51685191
.fixItRemove(*wildcardLoc);
51695192
}
5170-
} else if (!wildcardLoc) {
5193+
} else if (!wildcardLoc && hasValidSpecs && allValidSpecsArePlatform) {
51715194
if (info->getQueries().size() > 0) {
51725195
auto insertLoc = info->getQueries().back()->getSourceRange().End;
51735196
diags.diagnose(insertLoc, diag::availability_query_wildcard_required)

lib/Sema/TypeCheckAttr.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4979,8 +4979,9 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> attrs) {
49794979
llvm::SmallSet<AvailabilityDomain, 8> seenDomains;
49804980

49814981
SourceLoc groupEndLoc;
4982-
bool requiresWildcard = false;
49834982
bool foundWildcard = false;
4983+
bool hasValidSpecs = false;
4984+
bool allValidSpecsArePlatform = true;
49844985
int groupAttrCount = 0;
49854986
for (auto *groupedAttr = groupHead; groupedAttr != nullptr;
49864987
groupedAttr = groupedAttr->getNextGroupedAvailableAttr()) {
@@ -4999,28 +5000,31 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> attrs) {
49995000
continue;
50005001

50015002
auto domain = attr->getDomain();
5002-
if (domain.isPlatform())
5003-
requiresWildcard = true;
50045003

50055004
if (groupAttrCount > 1 || !groupedAttr->isGroupTerminator() ||
50065005
foundWildcard) {
50075006
// Only platform availability is allowed to be written groups with more
50085007
// than one member.
50095008
if (!domain.isPlatform()) {
50105009
diagnose(loc, diag::availability_must_occur_alone,
5011-
domain.getNameForAttributePrinting());
5010+
domain.getNameForDiagnostics(), domain.isVersioned());
50125011
continue;
50135012
}
50145013
}
50155014

50165015
// Diagnose duplicate platforms.
50175016
if (!seenDomains.insert(domain).second) {
5018-
diagnose(loc, diag::availability_query_repeated_platform,
5019-
domain.getNameForAttributePrinting());
5017+
diagnose(loc, diag::availability_query_already_specified,
5018+
domain.isVersioned(), domain.getNameForAttributePrinting());
5019+
continue;
50205020
}
5021+
5022+
hasValidSpecs = true;
5023+
if (!domain.isPlatform())
5024+
allValidSpecsArePlatform = false;
50215025
}
50225026

5023-
if (requiresWildcard && !foundWildcard) {
5027+
if (!foundWildcard && hasValidSpecs && allValidSpecsArePlatform) {
50245028
diagnose(groupEndLoc, diag::availability_query_wildcard_required)
50255029
.fixItInsert(groupEndLoc, ", *");
50265030
}

test/Parse/availability_query.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ if #available( { // expected-error {{expected platform name}} expected-error {{e
3030
if #available() { // expected-error {{expected platform name}}
3131
}
3232

33-
if #available(OSX { // expected-error {{expected version number}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}
33+
if #available(OSX { // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
3434
}
3535

3636
if #available(OSX) { // expected-error {{expected version number}}
@@ -46,8 +46,7 @@ if #available(OSX 51 { // expected-error {{expected ')'}} expected-note {{to mat
4646
}
4747

4848
if #available(iDishwasherOS 51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}
49-
// expected-error@-1 {{must handle potential future platforms with '*'}}
50-
// expected-error@-2 {{condition required for target platform}}
49+
// expected-error@-1 {{condition required for target platform}}
5150
}
5251

5352
if #available(iDishwasherOS 51, *) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}
@@ -75,7 +74,9 @@ if #available(OSX 51, iOS 8.0) { } // expected-error {{must handle potential fu
7574
if #available(iOS 8.0, *) {
7675
}
7776

78-
if #available(iOSApplicationExtension, unavailable) { // expected-error 2{{expected version number}}
77+
if #available(iOSApplicationExtension, unavailable) { // expected-error {{'unavailable' can't be combined with shorthand specification 'iOSApplicationExtension'}}
78+
// expected-error@-1 {{condition required for target platform}}
79+
// expected-note@-2 {{did you mean to specify an introduction version?}}
7980
}
8081

8182
// Want to make sure we can parse this. Perhaps we should not let this validate, though.
@@ -96,7 +97,7 @@ if #available(OSX 51, { // expected-error {{expected platform name}} // expected
9697
if #available(OSX 51,) { // expected-error {{expected platform name}}
9798
}
9899

99-
if #available(OSX 51, iOS { // expected-error {{expected version number}} // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
100+
if #available(OSX 51, iOS { // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
100101
}
101102

102103
if #available(OSX 51, iOS 8.0, iDishwasherOS 51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}

test/Parse/availability_query_unavailability.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ if #unavailable( { // expected-error {{expected platform name}} expected-error {
2828
if #unavailable() { // expected-error {{expected platform name}}
2929
}
3030

31-
if #unavailable(OSX { // expected-error {{expected version number}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}
31+
if #unavailable(OSX { // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
3232
}
3333

3434
if #unavailable(OSX) { // expected-error {{expected version number}}
@@ -62,7 +62,8 @@ if #unavailable(OSX 51, iOS 8.0, *) { } // expected-error {{platform wildcard '
6262
if #unavailable(iOS 8.0) {
6363
}
6464

65-
if #unavailable(iOSApplicationExtension, unavailable) { // expected-error 2{{expected version number}}
65+
if #unavailable(iOSApplicationExtension, unavailable) { // expected-error {{'unavailable' can't be combined with shorthand specification 'iOSApplicationExtension'}}
66+
// expected-note@-1 {{did you mean to specify an introduction version?}}
6667
}
6768

6869
// Should this be a valid spelling since `#unvailable(*)` cannot be written?
@@ -80,7 +81,7 @@ if #unavailable(OSX 51, iOS 8.0) {
8081
if #unavailable(OSX 51, { // expected-error {{expected platform name}} // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
8182
}
8283

83-
if #unavailable(OSX 51, iOS { // expected-error {{expected version number}} // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
84+
if #unavailable(OSX 51, iOS { // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
8485
}
8586

8687
if #unavailable(OSX 51, iOS 8.0, iDishwasherOS 51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}

0 commit comments

Comments
 (0)