Skip to content

Commit 7492e96

Browse files
authored
Merge pull request swiftlang#79494 from tshortli/type-check-availability-spec
Parse/Sema: Move some `AvailabilitySpec` diagnostics from Parse to Sema
2 parents 2ceb8f1 + e905309 commit 7492e96

File tree

11 files changed

+202
-85
lines changed

11 files changed

+202
-85
lines changed

include/swift/AST/Attr.h

Lines changed: 36 additions & 6 deletions
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,
154+
SWIFT_INLINE_BITFIELD(AvailableAttr, DeclAttribute, 4+1+1+1+1+1+1,
155155
/// An `AvailableAttr::Kind` value.
156156
Kind : 4,
157157

@@ -163,7 +163,15 @@ class DeclAttribute : public AttributeBase {
163163
HasRenamedDecl : 1,
164164

165165
/// Whether this attribute was spelled `@_spi_available`.
166-
IsSPI : 1
166+
IsSPI : 1,
167+
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,
172+
173+
/// Whether this attribute was followed by `, *` when parsed from source.
174+
IsFollowedByWildcard : 1
167175
);
168176

169177
SWIFT_INLINE_BITFIELD(ClangImporterSynthesizedTypeAttr, DeclAttribute, 1,
@@ -805,6 +813,10 @@ class AvailableAttr : public DeclAttribute {
805813
/// a rename decl even when this string is empty.
806814
StringRef getRename() const { return Rename; }
807815

816+
bool hasCachedRenamedDecl() const {
817+
return Bits.AvailableAttr.HasRenamedDecl;
818+
}
819+
808820
/// Whether this is an unconditionally unavailable entity.
809821
bool isUnconditionallyUnavailable() const;
810822

@@ -817,6 +829,28 @@ class AvailableAttr : public DeclAttribute {
817829
/// Whether this attribute was spelled `@_spi_available`.
818830
bool isSPI() const { return Bits.AvailableAttr.IsSPI; }
819831

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, *)`.
835+
const AvailableAttr *getNextGroupedAvailableAttr() const {
836+
if (Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr)
837+
return dyn_cast_or_null<AvailableAttr>(Next);
838+
return nullptr;
839+
}
840+
841+
void setIsFollowedByGroupedAvailableAttr() {
842+
Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr = true;
843+
}
844+
845+
/// Whether this attribute was followed by `, *` when parsed from source.
846+
bool isFollowedByWildcard() const {
847+
return Bits.AvailableAttr.IsFollowedByWildcard;
848+
}
849+
850+
void setIsFollowedByWildcard() {
851+
Bits.AvailableAttr.IsFollowedByWildcard = true;
852+
}
853+
820854
/// Returns the kind of availability the attribute specifies.
821855
Kind getKind() const { return static_cast<Kind>(Bits.AvailableAttr.Kind); }
822856

@@ -860,10 +894,6 @@ class AvailableAttr : public DeclAttribute {
860894
return DA->getKind() == DeclAttrKind::Available;
861895
}
862896

863-
bool hasCachedRenamedDecl() const {
864-
return Bits.AvailableAttr.HasRenamedDecl;
865-
}
866-
867897
private:
868898
friend class RenamedDeclRequest;
869899

include/swift/AST/DiagnosticsParse.def

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1998,18 +1998,6 @@ ERROR(availability_query_wildcard_required, none,
19981998
ERROR(unavailability_query_wildcard_not_required, none,
19991999
"platform wildcard '*' is always implicit in #unavailable", ())
20002000

2001-
ERROR(availability_must_occur_alone, none,
2002-
"'%0' version-availability must be specified alone", (StringRef))
2003-
2004-
ERROR(pound_available_swift_not_allowed, none,
2005-
"Swift language version checks not allowed in %0(...)", (StringRef))
2006-
2007-
ERROR(pound_available_package_description_not_allowed, none,
2008-
"PackageDescription version checks not allowed in %0(...)", (StringRef))
2009-
2010-
ERROR(availability_query_repeated_platform, none,
2011-
"version for '%0' already specified", (StringRef))
2012-
20132001
ERROR(availability_cannot_be_mixed,none,
20142002
"#available and #unavailable cannot be in the same statement", ())
20152003

include/swift/AST/DiagnosticsSema.def

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6963,6 +6963,22 @@ ERROR(conformance_availability_only_version_newer, none,
69636963
"conformance of %0 to %1 is only available in %2 %3 or newer",
69646964
(Type, Type, StringRef, llvm::VersionTuple))
69656965

6966+
ERROR(availability_must_occur_alone, none,
6967+
"'%0' version-availability must be specified alone", (StringRef))
6968+
6969+
//------------------------------------------------------------------------------
6970+
// MARK: if #available(...)
6971+
//------------------------------------------------------------------------------
6972+
6973+
ERROR(availability_query_swift_not_allowed, none,
6974+
"Swift language version checks not allowed in %0(...)", (StringRef))
6975+
6976+
ERROR(availability_query_package_description_not_allowed, none,
6977+
"PackageDescription version checks not allowed in %0(...)", (StringRef))
6978+
6979+
ERROR(availability_query_repeated_platform, none,
6980+
"version for '%0' already specified", (StringRef))
6981+
69666982
//------------------------------------------------------------------------------
69676983
// MARK: @discardableResult
69686984
//------------------------------------------------------------------------------

lib/AST/Attr.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,6 +2141,8 @@ AvailableAttr::AvailableAttr(
21412141
ObsoletedRange(ObsoletedRange) {
21422142
Bits.AvailableAttr.Kind = static_cast<uint8_t>(Kind);
21432143
Bits.AvailableAttr.IsSPI = IsSPI;
2144+
Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr = false;
2145+
Bits.AvailableAttr.IsFollowedByWildcard = false;
21442146
}
21452147

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

lib/Parse/ParseDecl.cpp

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -790,24 +790,36 @@ bool Parser::parseAvailability(
790790
// we will synthesize
791791
// @available(_PackageDescription, introduced: 4.2)
792792

793+
AvailabilitySpec *PrevSpec = nullptr;
793794
for (auto *Spec : Specs) {
794-
// FIXME: [availability] Allow arbitrary availability domains.
795+
if (Spec->isWildcard())
796+
continue;
797+
795798
std::optional<AvailabilityDomain> Domain = Spec->getDomain();
796799
if (!Domain)
797800
continue;
798801

799-
addAttribute(new (Context) AvailableAttr(
800-
AtLoc, attrRange, *Domain, Spec->getSourceRange().Start,
801-
AvailableAttr::Kind::Default,
802-
/*Message=*/StringRef(),
803-
/*Rename=*/StringRef(),
804-
/*Introduced=*/Spec->getVersion(),
805-
/*IntroducedRange=*/Spec->getVersionSrcRange(),
806-
/*Deprecated=*/llvm::VersionTuple(),
807-
/*DeprecatedRange=*/SourceRange(),
808-
/*Obsoleted=*/llvm::VersionTuple(),
809-
/*ObsoletedRange=*/SourceRange(),
810-
/*Implicit=*/false, AttrName == SPI_AVAILABLE_ATTRNAME));
802+
auto Attr = new (Context)
803+
AvailableAttr(AtLoc, attrRange, *Domain, Spec->getSourceRange().Start,
804+
AvailableAttr::Kind::Default,
805+
/*Message=*/StringRef(),
806+
/*Rename=*/StringRef(),
807+
/*Introduced=*/Spec->getVersion(),
808+
/*IntroducedRange=*/Spec->getVersionSrcRange(),
809+
/*Deprecated=*/llvm::VersionTuple(),
810+
/*DeprecatedRange=*/SourceRange(),
811+
/*Obsoleted=*/llvm::VersionTuple(),
812+
/*ObsoletedRange=*/SourceRange(),
813+
/*Implicit=*/false, AttrName == SPI_AVAILABLE_ATTRNAME);
814+
addAttribute(Attr);
815+
816+
if (PrevSpec) {
817+
if (PrevSpec->isWildcard())
818+
Attr->setIsFollowedByWildcard();
819+
else
820+
Attr->setIsFollowedByGroupedAvailableAttr();
821+
}
822+
PrevSpec = Spec;
811823
}
812824

813825
return true;

lib/Parse/ParseStmt.cpp

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,9 +1289,8 @@ static void parseGuardedPattern(Parser &P, GuardedPattern &result,
12891289
/// removing specs for unrecognized platforms.
12901290
static void
12911291
validateAvailabilitySpecList(Parser &P,
1292-
SmallVectorImpl<AvailabilitySpec *> &Specs,
1292+
const SmallVectorImpl<AvailabilitySpec *> &Specs,
12931293
Parser::AvailabilitySpecSource Source) {
1294-
llvm::SmallSet<PlatformKind, 4> Platforms;
12951294
std::optional<SourceLoc> WildcardSpecLoc = std::nullopt;
12961295

12971296
if (Specs.size() == 1) {
@@ -1303,37 +1302,9 @@ validateAvailabilitySpecList(Parser &P,
13031302
return;
13041303
}
13051304

1306-
SmallVector<AvailabilitySpec *, 5> RecognizedSpecs;
13071305
for (auto *Spec : Specs) {
1308-
RecognizedSpecs.push_back(Spec);
13091306
if (Spec->isWildcard()) {
13101307
WildcardSpecLoc = Spec->getStartLoc();
1311-
continue;
1312-
}
1313-
1314-
// We keep specs for unrecognized domains around for error recovery
1315-
// during parsing but remove them once parsing is completed.
1316-
auto Domain = Spec->getDomain();
1317-
if (!Domain) {
1318-
RecognizedSpecs.pop_back();
1319-
continue;
1320-
}
1321-
1322-
if (!Domain->isPlatform()) {
1323-
P.diagnose(Spec->getStartLoc(), diag::availability_must_occur_alone,
1324-
Domain->getNameForAttributePrinting());
1325-
continue;
1326-
}
1327-
1328-
bool Inserted = Platforms.insert(Spec->getPlatform()).second;
1329-
if (!Inserted) {
1330-
// Rule out multiple version specs referring to the same platform.
1331-
// For example, we emit an error for
1332-
/// #available(OSX 10.10, OSX 10.11, *)
1333-
PlatformKind Platform = Spec->getPlatform();
1334-
P.diagnose(Spec->getStartLoc(),
1335-
diag::availability_query_repeated_platform,
1336-
platformString(Platform));
13371308
}
13381309
}
13391310

@@ -1362,8 +1333,6 @@ validateAvailabilitySpecList(Parser &P,
13621333
break;
13631334
}
13641335
}
1365-
1366-
Specs = RecognizedSpecs;
13671336
}
13681337

13691338
// #available(...)
@@ -1398,18 +1367,6 @@ ParserResult<PoundAvailableInfo> Parser::parseStmtConditionPoundAvailable() {
13981367
SmallVector<AvailabilitySpec *, 5> Specs;
13991368
ParserStatus Status = parseAvailabilitySpecList(Specs, Source);
14001369

1401-
for (auto *Spec : Specs) {
1402-
if (Spec->getPlatform() != PlatformKind::none || Spec->isWildcard())
1403-
continue;
1404-
1405-
diagnose(Spec->getStartLoc(),
1406-
Spec->getDomain()->isSwiftLanguage()
1407-
? diag::pound_available_swift_not_allowed
1408-
: diag::pound_available_package_description_not_allowed,
1409-
getTokenText(MainToken));
1410-
Status.setIsParseError();
1411-
}
1412-
14131370
SourceLoc RParenLoc;
14141371
if (parseMatchingToken(tok::r_paren, RParenLoc,
14151372
diag::avail_query_expected_rparen, LParenLoc))

lib/Sema/MiscDiagnostics.cpp

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5123,17 +5123,44 @@ checkImplicitPromotionsInCondition(const StmtConditionElement &cond,
51235123
/// was emitted.
51245124
static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
51255125
DeclContext *DC) {
5126+
auto &diags = DC->getASTContext().Diags;
5127+
5128+
llvm::SmallSet<AvailabilityDomain, 8> seenDomains;
5129+
for (auto spec : info->getQueries()) {
5130+
auto domain = spec->getDomain();
5131+
if (!domain) {
5132+
continue;
5133+
}
5134+
5135+
if (!domain->isPlatform()) {
5136+
diags.diagnose(
5137+
spec->getStartLoc(),
5138+
domain->isSwiftLanguage()
5139+
? diag::availability_query_swift_not_allowed
5140+
: diag::availability_query_package_description_not_allowed,
5141+
info->isUnavailability() ? "#unavailable" : "#available");
5142+
return true;
5143+
}
5144+
5145+
// Diagnose duplicate platforms.
5146+
if (!seenDomains.insert(*domain).second) {
5147+
diags.diagnose(spec->getStartLoc(),
5148+
diag::availability_query_repeated_platform,
5149+
domain->getNameForAttributePrinting());
5150+
return true;
5151+
}
5152+
}
5153+
51265154
// Reject inlinable code using availability macros. In order to lift this
51275155
// restriction, macros would need to either be expanded when printed in
51285156
// swiftinterfaces or be parsable as macros by module clients.
51295157
auto fragileKind = DC->getFragileFunctionKind();
51305158
if (fragileKind.kind != FragileFunctionKind::None) {
51315159
for (auto availSpec : info->getQueries()) {
51325160
if (availSpec->getMacroLoc().isValid()) {
5133-
DC->getASTContext().Diags.diagnose(
5134-
availSpec->getMacroLoc(),
5135-
swift::diag::availability_macro_in_inlinable,
5136-
fragileKind.getSelector());
5161+
diags.diagnose(availSpec->getMacroLoc(),
5162+
swift::diag::availability_macro_in_inlinable,
5163+
fragileKind.getSelector());
51375164
return true;
51385165
}
51395166
}

lib/Sema/TypeCheckAttr.cpp

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4957,20 +4957,73 @@ void AttributeChecker::checkOriginalDefinedInAttrs(
49574957
}
49584958
}
49594959

4960-
void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> Attrs) {
4961-
if (Attrs.empty())
4960+
/// Find each of the `AvailableAttr`s that represents the first attribute in a
4961+
/// group of attributes what were parsed from a short-form available attribute,
4962+
/// e.g. `@available(macOS , iOS, *)`.
4963+
static llvm::SmallSet<const AvailableAttr *, 8>
4964+
getAvailableAttrGroups(ArrayRef<AvailableAttr *> attrs) {
4965+
llvm::SmallSet<const AvailableAttr *, 8> heads;
4966+
4967+
// Find each attribute that belongs to a group.
4968+
for (auto attr : attrs) {
4969+
if (attr->getNextGroupedAvailableAttr())
4970+
heads.insert(attr);
4971+
}
4972+
4973+
// Remove the interior attributes of each group, leaving only the head.
4974+
for (auto attr : attrs) {
4975+
if (auto next = attr->getNextGroupedAvailableAttr()) {
4976+
heads.erase(next);
4977+
}
4978+
}
4979+
4980+
return heads;
4981+
}
4982+
4983+
void AttributeChecker::checkAvailableAttrs(ArrayRef<AvailableAttr *> attrs) {
4984+
if (attrs.empty())
49624985
return;
49634986

49644987
// Only diagnose top level decls since nested ones may have inherited availability.
49654988
if (!D->getDeclContext()->getInnermostDeclarationDeclContext()) {
49664989
// If all available are spi available, we should use @_spi instead.
4967-
if (std::all_of(Attrs.begin(), Attrs.end(), [](AvailableAttr *AV) {
4968-
return AV->isSPI();
4969-
})) {
4990+
if (std::all_of(attrs.begin(), attrs.end(),
4991+
[](AvailableAttr *AV) { return AV->isSPI(); })) {
49704992
diagnose(D->getLoc(), diag::spi_preferred_over_spi_available);
49714993
}
49724994
}
49734995

4996+
auto attrGroups = getAvailableAttrGroups(attrs);
4997+
for (const AvailableAttr *groupHead : attrGroups) {
4998+
llvm::SmallSet<AvailabilityDomain, 8> seenDomains;
4999+
5000+
for (auto *groupedAttr = groupHead; groupedAttr != nullptr;
5001+
groupedAttr = groupedAttr->getNextGroupedAvailableAttr()) {
5002+
auto loc = groupedAttr->getLocation();
5003+
auto attr = D->getSemanticAvailableAttr(groupedAttr);
5004+
5005+
// If the attribute cannot be resolved, it may have had an unrecognized
5006+
// domain. Assume this unrecognized domain could be an unrecognized
5007+
// platform and skip it.
5008+
if (!attr)
5009+
continue;
5010+
5011+
// Only platform availability is allowed to be written in short form.
5012+
auto domain = attr->getDomain();
5013+
if (!domain.isPlatform()) {
5014+
diagnose(loc, diag::availability_must_occur_alone,
5015+
domain.getNameForAttributePrinting());
5016+
continue;
5017+
}
5018+
5019+
// Diagnose duplicate platforms.
5020+
if (!seenDomains.insert(domain).second) {
5021+
diagnose(loc, diag::availability_query_repeated_platform,
5022+
domain.getNameForAttributePrinting());
5023+
}
5024+
}
5025+
}
5026+
49745027
if (Ctx.LangOpts.DisableAvailabilityChecking)
49755028
return;
49765029

test/Parse/availability_query.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ if #available(OSX 51 { // expected-error {{expected ')'}} expected-note {{to mat
4747

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

5253
if #available(iDishwasherOS 51, *) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}
@@ -125,4 +126,7 @@ if let _ = Optional(42), #available(iOS 8.0, *) {}
125126
if #available(macOS 51, *) {
126127
}
127128

129+
// FIXME: This is weird, but it's already accepted. It should probably be diagnosed.
130+
if #available(*, macOS 51) {
131+
}
128132

0 commit comments

Comments
 (0)