Skip to content

Commit db29a1c

Browse files
committed
Parse/Sema: Move #available query wildcard diagnostics to Sema.
In order to unblock resolution of availability domains during type-checking instead of parsing, diagnostics about missing or superfluous wildcards in availability specification lists need to move to Sema.
1 parent 53b3460 commit db29a1c

12 files changed

+136
-93
lines changed

include/swift/AST/Attr.h

+24-19
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class DeclAttribute : public AttributeBase {
151151
Value : 32
152152
);
153153

154-
SWIFT_INLINE_BITFIELD(AvailableAttr, DeclAttribute, 4+1+1+1+1+1+1,
154+
SWIFT_INLINE_BITFIELD(AvailableAttr, DeclAttribute, 4+1+1+1+1+1+1+1,
155155
/// An `AvailableAttr::Kind` value.
156156
Kind : 4,
157157

@@ -165,13 +165,14 @@ class DeclAttribute : public AttributeBase {
165165
/// Whether this attribute was spelled `@_spi_available`.
166166
IsSPI : 1,
167167

168-
/// Whether this attribute is an interior attribute of a group of
169-
/// `@available` attributes that were written in source using short form
170-
/// syntax (`@available(macOS 15, ...)`).
171-
IsFollowedByGroupedAvailableAttr : 1,
168+
/// Whether this attribute belongs to a chain of adjacent `@available` attributes that were generated from a single attribute written in source using short form syntax e.g. (`@available(macOS 15, iOS 18, *)`).
169+
IsGroupMember : 1,
172170

173-
/// Whether this attribute was followed by `, *` when parsed from source.
174-
IsFollowedByWildcard : 1
171+
/// Whether this attribute is the final one in its group.
172+
IsGroupTerminator : 1,
173+
174+
/// Whether this attribute's specification was followed by `, *` in source.
175+
IsAdjacentToWildcard : 1
175176
);
176177

177178
SWIFT_INLINE_BITFIELD(ClangImporterSynthesizedTypeAttr, DeclAttribute, 1,
@@ -829,26 +830,30 @@ class AvailableAttr : public DeclAttribute {
829830
/// Whether this attribute was spelled `@_spi_available`.
830831
bool isSPI() const { return Bits.AvailableAttr.IsSPI; }
831832

832-
/// Returns the following `@available` if this was generated from an
833-
/// attribute that was written in source using short form syntax, e.g.
834-
/// `@available(macOS 15, iOS 18, *)`.
833+
/// Returns the next attribute in the chain of adjacent `@available`
834+
/// attributes that were generated from a single attribute written in source
835+
/// using short form syntax e.g. (`@available(macOS 15, iOS 18, *)`).
835836
const AvailableAttr *getNextGroupedAvailableAttr() const {
836-
if (Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr)
837+
if (Bits.AvailableAttr.IsGroupMember && !isGroupTerminator())
837838
return dyn_cast_or_null<AvailableAttr>(Next);
838839
return nullptr;
839840
}
840841

841-
void setIsFollowedByGroupedAvailableAttr() {
842-
Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr = true;
843-
}
842+
bool isGroupMember() const { return Bits.AvailableAttr.IsGroupMember; }
843+
void setIsGroupMember() { Bits.AvailableAttr.IsGroupMember = true; }
844844

845-
/// Whether this attribute was followed by `, *` when parsed from source.
846-
bool isFollowedByWildcard() const {
847-
return Bits.AvailableAttr.IsFollowedByWildcard;
845+
/// Whether this attribute is the final one in its group.
846+
bool isGroupTerminator() const {
847+
return Bits.AvailableAttr.IsGroupTerminator;
848848
}
849+
void setIsGroupTerminator() { Bits.AvailableAttr.IsGroupTerminator = true; }
849850

850-
void setIsFollowedByWildcard() {
851-
Bits.AvailableAttr.IsFollowedByWildcard = true;
851+
/// Whether this attribute's specification was followed by `, *` in source.
852+
bool isAdjacentToWildcard() const {
853+
return Bits.AvailableAttr.IsAdjacentToWildcard;
854+
}
855+
void setIsAdjacentToWildcard() {
856+
Bits.AvailableAttr.IsAdjacentToWildcard = true;
852857
}
853858

854859
/// Returns the kind of availability the attribute specifies.

include/swift/AST/DiagnosticsParse.def

-6
Original file line numberDiff line numberDiff line change
@@ -1992,12 +1992,6 @@ NOTE(avail_query_meant_introduced,PointsToFirstBadToken,
19921992
ERROR(avail_query_version_comparison_not_needed,
19931993
none,"version comparison not needed", ())
19941994

1995-
ERROR(availability_query_wildcard_required, none,
1996-
"must handle potential future platforms with '*'", ())
1997-
1998-
ERROR(unavailability_query_wildcard_not_required, none,
1999-
"platform wildcard '*' is always implicit in #unavailable", ())
2000-
20011995
ERROR(availability_cannot_be_mixed,none,
20021996
"#available and #unavailable cannot be in the same statement", ())
20031997

include/swift/AST/DiagnosticsSema.def

+6
Original file line numberDiff line numberDiff line change
@@ -6990,6 +6990,12 @@ ERROR(availability_query_package_description_not_allowed, none,
69906990
ERROR(availability_query_repeated_platform, none,
69916991
"version for '%0' already specified", (StringRef))
69926992

6993+
ERROR(availability_query_wildcard_required, none,
6994+
"must handle potential future platforms with '*'", ())
6995+
6996+
ERROR(unavailability_query_wildcard_not_required, none,
6997+
"platform wildcard '*' is always implicit in #unavailable", ())
6998+
69936999
//------------------------------------------------------------------------------
69947000
// MARK: @discardableResult
69957001
//------------------------------------------------------------------------------

include/swift/AST/Stmt.h

+18-10
Original file line numberDiff line numberDiff line change
@@ -492,28 +492,36 @@ class alignas(8) PoundAvailableInfo final :
492492
/// This is filled in by Sema.
493493
VersionRange VariantAvailableRange;
494494

495-
/// Indicates that the expression is checking if a version range
496-
/// is **not** available.
497-
bool _isUnavailability;
495+
struct {
496+
unsigned isInvalid : 1;
497+
498+
/// Indicates that the expression is checking if a version range
499+
/// is **not** available.
500+
unsigned isUnavailability : 1;
501+
} Flags;
498502

499503
PoundAvailableInfo(SourceLoc PoundLoc, SourceLoc LParenLoc,
500504
ArrayRef<AvailabilitySpec *> queries, SourceLoc RParenLoc,
501505
bool isUnavailability)
502-
: PoundLoc(PoundLoc), LParenLoc(LParenLoc), RParenLoc(RParenLoc),
503-
NumQueries(queries.size()), AvailableRange(VersionRange::empty()),
504-
VariantAvailableRange(VersionRange::empty()),
505-
_isUnavailability(isUnavailability) {
506+
: PoundLoc(PoundLoc), LParenLoc(LParenLoc), RParenLoc(RParenLoc),
507+
NumQueries(queries.size()), AvailableRange(VersionRange::empty()),
508+
VariantAvailableRange(VersionRange::empty()), Flags() {
509+
Flags.isInvalid = false;
510+
Flags.isUnavailability = isUnavailability;
506511
std::uninitialized_copy(queries.begin(), queries.end(),
507512
getTrailingObjects<AvailabilitySpec *>());
508513
}
509-
514+
510515
public:
511516
static PoundAvailableInfo *create(ASTContext &ctx, SourceLoc PoundLoc,
512517
SourceLoc LParenLoc,
513518
ArrayRef<AvailabilitySpec *> queries,
514519
SourceLoc RParenLoc,
515520
bool isUnavailability);
516-
521+
522+
bool isInvalid() const { return Flags.isInvalid; }
523+
void setInvalid() { Flags.isInvalid = true; }
524+
517525
ArrayRef<AvailabilitySpec *> getQueries() const {
518526
return llvm::ArrayRef(getTrailingObjects<AvailabilitySpec *>(), NumQueries);
519527
}
@@ -541,7 +549,7 @@ class alignas(8) PoundAvailableInfo final :
541549
VariantAvailableRange = Range;
542550
}
543551

544-
bool isUnavailability() const { return _isUnavailability; }
552+
bool isUnavailability() const { return Flags.isUnavailability; }
545553
};
546554

547555
/// An expression that guards execution based on whether the symbols for the

lib/AST/Attr.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -2144,8 +2144,9 @@ AvailableAttr::AvailableAttr(
21442144
ObsoletedRange(ObsoletedRange) {
21452145
Bits.AvailableAttr.Kind = static_cast<uint8_t>(Kind);
21462146
Bits.AvailableAttr.IsSPI = IsSPI;
2147-
Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr = false;
2148-
Bits.AvailableAttr.IsFollowedByWildcard = false;
2147+
Bits.AvailableAttr.IsGroupMember = false;
2148+
Bits.AvailableAttr.IsGroupTerminator = false;
2149+
Bits.AvailableAttr.IsAdjacentToWildcard = false;
21492150
}
21502151

21512152
AvailableAttr *AvailableAttr::createUniversallyUnavailable(ASTContext &C,

lib/Parse/ParseDecl.cpp

+10-9
Original file line numberDiff line numberDiff line change
@@ -790,10 +790,13 @@ bool Parser::parseAvailability(
790790
// we will synthesize
791791
// @available(_PackageDescription, introduced: 4.2)
792792

793-
AvailabilitySpec *PrevSpec = nullptr;
793+
AvailableAttr *PrevAttr = nullptr;
794794
for (auto *Spec : Specs) {
795-
if (Spec->isWildcard())
795+
if (Spec->isWildcard()) {
796+
if (PrevAttr)
797+
PrevAttr->setIsAdjacentToWildcard();
796798
continue;
799+
}
797800

798801
std::optional<AvailabilityDomain> Domain = Spec->getDomain();
799802
if (!Domain)
@@ -813,13 +816,11 @@ bool Parser::parseAvailability(
813816
/*Implicit=*/false, AttrName == SPI_AVAILABLE_ATTRNAME);
814817
addAttribute(Attr);
815818

816-
if (PrevSpec) {
817-
if (PrevSpec->isWildcard())
818-
Attr->setIsFollowedByWildcard();
819-
else
820-
Attr->setIsFollowedByGroupedAvailableAttr();
821-
}
822-
PrevSpec = Spec;
819+
Attr->setIsGroupMember();
820+
if (!PrevAttr)
821+
Attr->setIsGroupTerminator();
822+
823+
PrevAttr = Attr;
823824
}
824825

825826
return true;

lib/Parse/ParseStmt.cpp

+8-35
Original file line numberDiff line numberDiff line change
@@ -1292,48 +1292,18 @@ static void
12921292
validateAvailabilitySpecList(Parser &P,
12931293
const SmallVectorImpl<AvailabilitySpec *> &Specs,
12941294
Parser::AvailabilitySpecSource Source) {
1295-
std::optional<SourceLoc> WildcardSpecLoc = std::nullopt;
1296-
1297-
if (Specs.size() == 1) {
1298-
// @available(swift N) and @available(_PackageDescription N) are allowed
1299-
// only in isolation; they cannot be combined with other availability specs
1300-
// in a single list.
1301-
auto domain = Specs[0]->getDomain();
1302-
if (domain && !domain->isPlatform())
1303-
return;
1304-
}
1295+
if (Source != Parser::AvailabilitySpecSource::Macro)
1296+
return;
13051297

1298+
std::optional<SourceLoc> WildcardSpecLoc;
13061299
for (auto *Spec : Specs) {
13071300
if (Spec->isWildcard()) {
13081301
WildcardSpecLoc = Spec->getStartLoc();
13091302
}
13101303
}
13111304

1312-
switch (Source) {
1313-
case Parser::AvailabilitySpecSource::Available: {
1314-
if (WildcardSpecLoc == std::nullopt) {
1315-
SourceLoc InsertWildcardLoc = P.PreviousLoc;
1316-
P.diagnose(InsertWildcardLoc, diag::availability_query_wildcard_required)
1317-
.fixItInsertAfter(InsertWildcardLoc, ", *");
1318-
}
1319-
break;
1320-
}
1321-
case Parser::AvailabilitySpecSource::Unavailable: {
1322-
if (WildcardSpecLoc != std::nullopt) {
1323-
SourceLoc Loc = WildcardSpecLoc.value();
1324-
P.diagnose(Loc, diag::unavailability_query_wildcard_not_required)
1325-
.fixItRemove(Loc);
1326-
}
1327-
break;
1328-
}
1329-
case Parser::AvailabilitySpecSource::Macro: {
1330-
if (WildcardSpecLoc != std::nullopt) {
1331-
SourceLoc Loc = WildcardSpecLoc.value();
1332-
P.diagnose(Loc, diag::attr_availability_wildcard_in_macro);
1333-
}
1334-
break;
1335-
}
1336-
}
1305+
if (WildcardSpecLoc)
1306+
P.diagnose(*WildcardSpecLoc, diag::attr_availability_wildcard_in_macro);
13371307
}
13381308

13391309
// #available(...)
@@ -1387,6 +1357,9 @@ ParserResult<PoundAvailableInfo> Parser::parseStmtConditionPoundAvailable() {
13871357

13881358
auto *result = PoundAvailableInfo::create(Context, PoundLoc, LParenLoc, Specs,
13891359
RParenLoc, isUnavailability);
1360+
if (Status.isError())
1361+
result->setInvalid();
1362+
13901363
return makeParserResult(Status, result);
13911364
}
13921365

lib/Sema/MiscDiagnostics.cpp

+25-3
Original file line numberDiff line numberDiff line change
@@ -5123,16 +5123,22 @@ checkImplicitPromotionsInCondition(const StmtConditionElement &cond,
51235123
/// was emitted.
51245124
static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
51255125
DeclContext *DC) {
5126+
if (info->isInvalid())
5127+
return false;
5128+
51265129
auto &diags = DC->getASTContext().Diags;
51275130
StringRef queryName =
51285131
info->isUnavailability() ? "#unavailable" : "#available";
51295132

5133+
std::optional<SourceLoc> wildcardLoc;
51305134
llvm::SmallSet<AvailabilityDomain, 8> seenDomains;
51315135
for (auto spec : info->getSemanticAvailabilitySpecs(DC)) {
5132-
if (spec.isWildcard())
5136+
auto parsedSpec = spec.getParsedSpec();
5137+
if (spec.isWildcard()) {
5138+
wildcardLoc = parsedSpec->getStartLoc();
51335139
continue;
5140+
}
51345141

5135-
auto parsedSpec = spec.getParsedSpec();
51365142
auto domain = spec.getDomain();
51375143

51385144
if (!domain.isPlatform()) {
@@ -5154,6 +5160,21 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
51545160
}
51555161
}
51565162

5163+
if (info->isUnavailability()) {
5164+
if (wildcardLoc) {
5165+
diags
5166+
.diagnose(*wildcardLoc,
5167+
diag::unavailability_query_wildcard_not_required)
5168+
.fixItRemove(*wildcardLoc);
5169+
}
5170+
} else if (!wildcardLoc) {
5171+
if (info->getQueries().size() > 0) {
5172+
auto insertLoc = info->getQueries().back()->getSourceRange().End;
5173+
diags.diagnose(insertLoc, diag::availability_query_wildcard_required)
5174+
.fixItInsertAfter(insertLoc, ", *");
5175+
}
5176+
}
5177+
51575178
// Reject inlinable code using availability macros. In order to lift this
51585179
// restriction, macros would need to either be expanded when printed in
51595180
// swiftinterfaces or be parsable as macros by module clients.
@@ -5260,7 +5281,8 @@ static void checkLabeledStmtConditions(ASTContext &ctx,
52605281
break;
52615282
case StmtConditionElement::CK_Availability: {
52625283
auto info = elt.getAvailability();
5263-
(void)diagnoseAvailabilityCondition(info, DC);
5284+
if (diagnoseAvailabilityCondition(info, DC))
5285+
info->setInvalid();
52645286
break;
52655287
}
52665288
case StmtConditionElement::CK_HasSymbol: {

lib/Sema/TypeCheckAttr.cpp

+27-6
Original file line numberDiff line numberDiff line change
@@ -4942,7 +4942,7 @@ getAvailableAttrGroups(ArrayRef<AvailableAttr *> attrs) {
49424942

49434943
// Find each attribute that belongs to a group.
49444944
for (auto attr : attrs) {
4945-
if (attr->getNextGroupedAvailableAttr())
4945+
if (attr->isGroupMember())
49464946
heads.insert(attr);
49474947
}
49484948

@@ -4973,9 +4973,18 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> attrs) {
49734973
for (const AvailableAttr *groupHead : attrGroups) {
49744974
llvm::SmallSet<AvailabilityDomain, 8> seenDomains;
49754975

4976+
SourceLoc groupEndLoc;
4977+
bool requiresWildcard = false;
4978+
bool foundWildcard = false;
4979+
int groupAttrCount = 0;
49764980
for (auto *groupedAttr = groupHead; groupedAttr != nullptr;
49774981
groupedAttr = groupedAttr->getNextGroupedAvailableAttr()) {
4982+
groupAttrCount++;
49784983
auto loc = groupedAttr->getLocation();
4984+
groupEndLoc = groupedAttr->getEndLoc();
4985+
if (groupedAttr->isAdjacentToWildcard())
4986+
foundWildcard = true;
4987+
49794988
auto attr = D->getSemanticAvailableAttr(groupedAttr);
49804989

49814990
// If the attribute cannot be resolved, it may have had an unrecognized
@@ -4984,12 +4993,19 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> attrs) {
49844993
if (!attr)
49854994
continue;
49864995

4987-
// Only platform availability is allowed to be written in short form.
49884996
auto domain = attr->getDomain();
4989-
if (!domain.isPlatform()) {
4990-
diagnose(loc, diag::availability_must_occur_alone,
4991-
domain.getNameForAttributePrinting());
4992-
continue;
4997+
if (domain.isPlatform())
4998+
requiresWildcard = true;
4999+
5000+
if (groupAttrCount > 1 || groupedAttr->isAdjacentToWildcard() ||
5001+
!groupedAttr->isGroupTerminator()) {
5002+
// Only platform availability is allowed to be written groups with more
5003+
// than one member.
5004+
if (!domain.isPlatform()) {
5005+
diagnose(loc, diag::availability_must_occur_alone,
5006+
domain.getNameForAttributePrinting());
5007+
continue;
5008+
}
49935009
}
49945010

49955011
// Diagnose duplicate platforms.
@@ -4998,6 +5014,11 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> attrs) {
49985014
domain.getNameForAttributePrinting());
49995015
}
50005016
}
5017+
5018+
if (requiresWildcard && !foundWildcard) {
5019+
diagnose(groupEndLoc, diag::availability_query_wildcard_required)
5020+
.fixItInsert(groupEndLoc, ", *");
5021+
}
50015022
}
50025023

50035024
if (Ctx.LangOpts.DisableAvailabilityChecking)

test/Parse/availability_query.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ if #available(OSX 0) { // expected-warning {{expected version number; this is an
4242
if #available(OSX 0.0) { // expected-warning {{expected version number; this is an error in the Swift 6 language mode}}
4343
}
4444

45-
if #available(OSX 51 { // expected-error {{expected ')'}} expected-note {{to match this opening '('}} expected-error {{must handle potential future platforms with '*'}} {{21-21=, *}}
45+
if #available(OSX 51 { // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
4646
}
4747

4848
if #available(iDishwasherOS 51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}

0 commit comments

Comments
 (0)