diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 9d3bf148f8089..c4dab4126ce59 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -151,7 +151,7 @@ class DeclAttribute : public AttributeBase { Value : 32 ); - SWIFT_INLINE_BITFIELD(AvailableAttr, DeclAttribute, 4+1+1+1+1, + SWIFT_INLINE_BITFIELD(AvailableAttr, DeclAttribute, 4+1+1+1+1+1+1, /// An `AvailableAttr::Kind` value. Kind : 4, @@ -163,7 +163,15 @@ class DeclAttribute : public AttributeBase { HasRenamedDecl : 1, /// Whether this attribute was spelled `@_spi_available`. - IsSPI : 1 + IsSPI : 1, + + /// Whether this attribute is an interior attribute of a group of + /// `@available` attributes that were written in source using short form + /// syntax (`@available(macOS 15, ...)`). + IsFollowedByGroupedAvailableAttr : 1, + + /// Whether this attribute was followed by `, *` when parsed from source. + IsFollowedByWildcard : 1 ); SWIFT_INLINE_BITFIELD(ClangImporterSynthesizedTypeAttr, DeclAttribute, 1, @@ -805,6 +813,10 @@ class AvailableAttr : public DeclAttribute { /// a rename decl even when this string is empty. StringRef getRename() const { return Rename; } + bool hasCachedRenamedDecl() const { + return Bits.AvailableAttr.HasRenamedDecl; + } + /// Whether this is an unconditionally unavailable entity. bool isUnconditionallyUnavailable() const; @@ -817,6 +829,28 @@ class AvailableAttr : public DeclAttribute { /// Whether this attribute was spelled `@_spi_available`. bool isSPI() const { return Bits.AvailableAttr.IsSPI; } + /// Returns the following `@available` if this was generated from an + /// attribute that was written in source using short form syntax, e.g. + /// `@available(macOS 15, iOS 18, *)`. + const AvailableAttr *getNextGroupedAvailableAttr() const { + if (Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr) + return dyn_cast_or_null(Next); + return nullptr; + } + + void setIsFollowedByGroupedAvailableAttr() { + Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr = true; + } + + /// Whether this attribute was followed by `, *` when parsed from source. + bool isFollowedByWildcard() const { + return Bits.AvailableAttr.IsFollowedByWildcard; + } + + void setIsFollowedByWildcard() { + Bits.AvailableAttr.IsFollowedByWildcard = true; + } + /// Returns the kind of availability the attribute specifies. Kind getKind() const { return static_cast(Bits.AvailableAttr.Kind); } @@ -860,10 +894,6 @@ class AvailableAttr : public DeclAttribute { return DA->getKind() == DeclAttrKind::Available; } - bool hasCachedRenamedDecl() const { - return Bits.AvailableAttr.HasRenamedDecl; - } - private: friend class RenamedDeclRequest; diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index ecbe52d68a26e..4bf294609b2a3 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1998,18 +1998,6 @@ ERROR(availability_query_wildcard_required, none, ERROR(unavailability_query_wildcard_not_required, none, "platform wildcard '*' is always implicit in #unavailable", ()) -ERROR(availability_must_occur_alone, none, - "'%0' version-availability must be specified alone", (StringRef)) - -ERROR(pound_available_swift_not_allowed, none, - "Swift language version checks not allowed in %0(...)", (StringRef)) - -ERROR(pound_available_package_description_not_allowed, none, - "PackageDescription version checks not allowed in %0(...)", (StringRef)) - -ERROR(availability_query_repeated_platform, none, - "version for '%0' already specified", (StringRef)) - ERROR(availability_cannot_be_mixed,none, "#available and #unavailable cannot be in the same statement", ()) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 6aeacc59a11de..a9c4da7f4148c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6963,6 +6963,22 @@ ERROR(conformance_availability_only_version_newer, none, "conformance of %0 to %1 is only available in %2 %3 or newer", (Type, Type, StringRef, llvm::VersionTuple)) +ERROR(availability_must_occur_alone, none, + "'%0' version-availability must be specified alone", (StringRef)) + +//------------------------------------------------------------------------------ +// MARK: if #available(...) +//------------------------------------------------------------------------------ + +ERROR(availability_query_swift_not_allowed, none, + "Swift language version checks not allowed in %0(...)", (StringRef)) + +ERROR(availability_query_package_description_not_allowed, none, + "PackageDescription version checks not allowed in %0(...)", (StringRef)) + +ERROR(availability_query_repeated_platform, none, + "version for '%0' already specified", (StringRef)) + //------------------------------------------------------------------------------ // MARK: @discardableResult //------------------------------------------------------------------------------ diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index faf30e60fd12d..95474f9896976 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -2141,6 +2141,8 @@ AvailableAttr::AvailableAttr( ObsoletedRange(ObsoletedRange) { Bits.AvailableAttr.Kind = static_cast(Kind); Bits.AvailableAttr.IsSPI = IsSPI; + Bits.AvailableAttr.IsFollowedByGroupedAvailableAttr = false; + Bits.AvailableAttr.IsFollowedByWildcard = false; } AvailableAttr *AvailableAttr::createUniversallyUnavailable(ASTContext &C, diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 4c0f861070168..e1eeefc7c707e 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -790,24 +790,36 @@ bool Parser::parseAvailability( // we will synthesize // @available(_PackageDescription, introduced: 4.2) + AvailabilitySpec *PrevSpec = nullptr; for (auto *Spec : Specs) { - // FIXME: [availability] Allow arbitrary availability domains. + if (Spec->isWildcard()) + continue; + std::optional Domain = Spec->getDomain(); if (!Domain) continue; - addAttribute(new (Context) AvailableAttr( - AtLoc, attrRange, *Domain, Spec->getSourceRange().Start, - AvailableAttr::Kind::Default, - /*Message=*/StringRef(), - /*Rename=*/StringRef(), - /*Introduced=*/Spec->getVersion(), - /*IntroducedRange=*/Spec->getVersionSrcRange(), - /*Deprecated=*/llvm::VersionTuple(), - /*DeprecatedRange=*/SourceRange(), - /*Obsoleted=*/llvm::VersionTuple(), - /*ObsoletedRange=*/SourceRange(), - /*Implicit=*/false, AttrName == SPI_AVAILABLE_ATTRNAME)); + auto Attr = new (Context) + AvailableAttr(AtLoc, attrRange, *Domain, Spec->getSourceRange().Start, + AvailableAttr::Kind::Default, + /*Message=*/StringRef(), + /*Rename=*/StringRef(), + /*Introduced=*/Spec->getVersion(), + /*IntroducedRange=*/Spec->getVersionSrcRange(), + /*Deprecated=*/llvm::VersionTuple(), + /*DeprecatedRange=*/SourceRange(), + /*Obsoleted=*/llvm::VersionTuple(), + /*ObsoletedRange=*/SourceRange(), + /*Implicit=*/false, AttrName == SPI_AVAILABLE_ATTRNAME); + addAttribute(Attr); + + if (PrevSpec) { + if (PrevSpec->isWildcard()) + Attr->setIsFollowedByWildcard(); + else + Attr->setIsFollowedByGroupedAvailableAttr(); + } + PrevSpec = Spec; } return true; diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 36fea8c4ea48c..087de520656ff 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -1289,9 +1289,8 @@ static void parseGuardedPattern(Parser &P, GuardedPattern &result, /// removing specs for unrecognized platforms. static void validateAvailabilitySpecList(Parser &P, - SmallVectorImpl &Specs, + const SmallVectorImpl &Specs, Parser::AvailabilitySpecSource Source) { - llvm::SmallSet Platforms; std::optional WildcardSpecLoc = std::nullopt; if (Specs.size() == 1) { @@ -1303,37 +1302,9 @@ validateAvailabilitySpecList(Parser &P, return; } - SmallVector RecognizedSpecs; for (auto *Spec : Specs) { - RecognizedSpecs.push_back(Spec); if (Spec->isWildcard()) { WildcardSpecLoc = Spec->getStartLoc(); - continue; - } - - // We keep specs for unrecognized domains around for error recovery - // during parsing but remove them once parsing is completed. - auto Domain = Spec->getDomain(); - if (!Domain) { - RecognizedSpecs.pop_back(); - continue; - } - - if (!Domain->isPlatform()) { - P.diagnose(Spec->getStartLoc(), diag::availability_must_occur_alone, - Domain->getNameForAttributePrinting()); - continue; - } - - bool Inserted = Platforms.insert(Spec->getPlatform()).second; - if (!Inserted) { - // Rule out multiple version specs referring to the same platform. - // For example, we emit an error for - /// #available(OSX 10.10, OSX 10.11, *) - PlatformKind Platform = Spec->getPlatform(); - P.diagnose(Spec->getStartLoc(), - diag::availability_query_repeated_platform, - platformString(Platform)); } } @@ -1362,8 +1333,6 @@ validateAvailabilitySpecList(Parser &P, break; } } - - Specs = RecognizedSpecs; } // #available(...) @@ -1398,18 +1367,6 @@ ParserResult Parser::parseStmtConditionPoundAvailable() { SmallVector Specs; ParserStatus Status = parseAvailabilitySpecList(Specs, Source); - for (auto *Spec : Specs) { - if (Spec->getPlatform() != PlatformKind::none || Spec->isWildcard()) - continue; - - diagnose(Spec->getStartLoc(), - Spec->getDomain()->isSwiftLanguage() - ? diag::pound_available_swift_not_allowed - : diag::pound_available_package_description_not_allowed, - getTokenText(MainToken)); - Status.setIsParseError(); - } - SourceLoc RParenLoc; if (parseMatchingToken(tok::r_paren, RParenLoc, diag::avail_query_expected_rparen, LParenLoc)) diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 657ac69c12a5b..47e2c4c7b0d4b 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -5123,6 +5123,34 @@ checkImplicitPromotionsInCondition(const StmtConditionElement &cond, /// was emitted. static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info, DeclContext *DC) { + auto &diags = DC->getASTContext().Diags; + + llvm::SmallSet seenDomains; + for (auto spec : info->getQueries()) { + auto domain = spec->getDomain(); + if (!domain) { + continue; + } + + if (!domain->isPlatform()) { + diags.diagnose( + spec->getStartLoc(), + domain->isSwiftLanguage() + ? diag::availability_query_swift_not_allowed + : diag::availability_query_package_description_not_allowed, + info->isUnavailability() ? "#unavailable" : "#available"); + return true; + } + + // Diagnose duplicate platforms. + if (!seenDomains.insert(*domain).second) { + diags.diagnose(spec->getStartLoc(), + diag::availability_query_repeated_platform, + domain->getNameForAttributePrinting()); + return true; + } + } + // Reject inlinable code using availability macros. In order to lift this // restriction, macros would need to either be expanded when printed in // swiftinterfaces or be parsable as macros by module clients. @@ -5130,10 +5158,9 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info, if (fragileKind.kind != FragileFunctionKind::None) { for (auto availSpec : info->getQueries()) { if (availSpec->getMacroLoc().isValid()) { - DC->getASTContext().Diags.diagnose( - availSpec->getMacroLoc(), - swift::diag::availability_macro_in_inlinable, - fragileKind.getSelector()); + diags.diagnose(availSpec->getMacroLoc(), + swift::diag::availability_macro_in_inlinable, + fragileKind.getSelector()); return true; } } diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 6c2a833fbb12f..2e5ca8226600a 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -4957,20 +4957,73 @@ void AttributeChecker::checkOriginalDefinedInAttrs( } } -void AttributeChecker::checkAvailableAttrs(ArrayRef Attrs) { - if (Attrs.empty()) +/// Find each of the `AvailableAttr`s that represents the first attribute in a +/// group of attributes what were parsed from a short-form available attribute, +/// e.g. `@available(macOS , iOS, *)`. +static llvm::SmallSet +getAvailableAttrGroups(ArrayRef attrs) { + llvm::SmallSet heads; + + // Find each attribute that belongs to a group. + for (auto attr : attrs) { + if (attr->getNextGroupedAvailableAttr()) + heads.insert(attr); + } + + // Remove the interior attributes of each group, leaving only the head. + for (auto attr : attrs) { + if (auto next = attr->getNextGroupedAvailableAttr()) { + heads.erase(next); + } + } + + return heads; +} + +void AttributeChecker::checkAvailableAttrs(ArrayRef attrs) { + if (attrs.empty()) return; // Only diagnose top level decls since nested ones may have inherited availability. if (!D->getDeclContext()->getInnermostDeclarationDeclContext()) { // If all available are spi available, we should use @_spi instead. - if (std::all_of(Attrs.begin(), Attrs.end(), [](AvailableAttr *AV) { - return AV->isSPI(); - })) { + if (std::all_of(attrs.begin(), attrs.end(), + [](AvailableAttr *AV) { return AV->isSPI(); })) { diagnose(D->getLoc(), diag::spi_preferred_over_spi_available); } } + auto attrGroups = getAvailableAttrGroups(attrs); + for (const AvailableAttr *groupHead : attrGroups) { + llvm::SmallSet seenDomains; + + for (auto *groupedAttr = groupHead; groupedAttr != nullptr; + groupedAttr = groupedAttr->getNextGroupedAvailableAttr()) { + auto loc = groupedAttr->getLocation(); + auto attr = D->getSemanticAvailableAttr(groupedAttr); + + // If the attribute cannot be resolved, it may have had an unrecognized + // domain. Assume this unrecognized domain could be an unrecognized + // platform and skip it. + if (!attr) + continue; + + // Only platform availability is allowed to be written in short form. + auto domain = attr->getDomain(); + if (!domain.isPlatform()) { + diagnose(loc, diag::availability_must_occur_alone, + domain.getNameForAttributePrinting()); + continue; + } + + // Diagnose duplicate platforms. + if (!seenDomains.insert(domain).second) { + diagnose(loc, diag::availability_query_repeated_platform, + domain.getNameForAttributePrinting()); + } + } + } + if (Ctx.LangOpts.DisableAvailabilityChecking) return; diff --git a/test/Parse/availability_query.swift b/test/Parse/availability_query.swift index 1d4e8820da350..ecfcfe22386af 100644 --- a/test/Parse/availability_query.swift +++ b/test/Parse/availability_query.swift @@ -47,6 +47,7 @@ if #available(OSX 51 { // expected-error {{expected ')'}} expected-note {{to mat if #available(iDishwasherOS 51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}} // expected-error@-1 {{must handle potential future platforms with '*'}} +// expected-error@-2 {{condition required for target platform}} } if #available(iDishwasherOS 51, *) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}} @@ -125,4 +126,7 @@ if let _ = Optional(42), #available(iOS 8.0, *) {} if #available(macOS 51, *) { } +// FIXME: This is weird, but it's already accepted. It should probably be diagnosed. +if #available(*, macOS 51) { +} diff --git a/test/SILGen/availability_query.swift b/test/SILGen/availability_query.swift index ccf2699df3502..2f92ac8c93d5f 100644 --- a/test/SILGen/availability_query.swift +++ b/test/SILGen/availability_query.swift @@ -69,6 +69,16 @@ if #unavailable(OSX 10.52) { if #available(macOS 10.52, *) { } +// CHECK: [[MAJOR:%.*]] = integer_literal $Builtin.Word, 10 +// CHECK: [[MINOR:%.*]] = integer_literal $Builtin.Word, 53 +// CHECK: [[PATCH:%.*]] = integer_literal $Builtin.Word, 0 +// CHECK: [[QUERY_FUNC:%.*]] = function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1 +// CHECK: [[QUERY_RESULT:%.*]] = apply [[QUERY_FUNC]]([[MAJOR]], [[MINOR]], [[PATCH]]) : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1 +// CHECK-NOT: {{.*}}integer_literal $Builtin.Int1, -1 +// CHECK-NOT: builtin "xor_Int1"{{.*}} +if #available(*, macOS 10.53) { +} + // CHECK: [[MAJOR:%.*]] = integer_literal $Builtin.Word, 10 // CHECK: [[MINOR:%.*]] = integer_literal $Builtin.Word, 52 // CHECK: [[PATCH:%.*]] = integer_literal $Builtin.Word, 0 diff --git a/test/Sema/availability_versions.swift b/test/Sema/availability_versions.swift index 2bc9fae98656b..5c3af00535056 100644 --- a/test/Sema/availability_versions.swift +++ b/test/Sema/availability_versions.swift @@ -82,6 +82,13 @@ if #available(OSX 51, *) { // expected-note@-1 {{add 'if #available' version check}} } +// FIXME: This is weird, but it's already accepted. It should probably be diagnosed. +if #available(*, OSX 51) { + let _: Int = globalFuncAvailableOn51() + let _: Int = globalFuncAvailableOn52() // expected-error {{'globalFuncAvailableOn52()' is only available in macOS 52 or newer}} + // expected-note@-1 {{add 'if #available' version check}} +} + if #available(OSX 51, *) { let _: Int = globalFuncAvailableOn51() let _: Int = globalFuncAvailableOn52() // expected-error {{'globalFuncAvailableOn52()' is only available in macOS 52 or newer}} @@ -1799,8 +1806,16 @@ func funcWithMultipleShortFormAnnotationsForTheSamePlatform() { // expected-note@-1 {{add 'if #available' version check}} } +// FIXME: This is weird, but it's already accepted. It should probably be diagnosed. +@available(iOS 14, *, OSX 51) +func funcWithWeirdShortFormAvailableOn51() { + let _ = ClassWithShortFormAvailableOn51() + let _ = ClassWithShortFormAvailableOn54() // expected-error {{'ClassWithShortFormAvailableOn54' is only available in macOS 54 or newer}} + // expected-note@-1 {{add 'if #available' version check}} +} + func useShortFormAvailable() { - // expected-note@-1 4{{add @available attribute to enclosing global function}} + // expected-note@-1 5{{add @available attribute to enclosing global function}} funcWithShortFormAvailableOn10_9() @@ -1817,6 +1832,9 @@ func useShortFormAvailable() { funcWithMultipleShortFormAnnotationsForTheSamePlatform() // expected-error {{'funcWithMultipleShortFormAnnotationsForTheSamePlatform()' is only available in macOS 53 or newer}} // expected-note@-1 {{add 'if #available' version check}} + + funcWithWeirdShortFormAvailableOn51() // expected-error {{'funcWithWeirdShortFormAvailableOn51()' is only available in macOS 51 or newer}} + // expected-note@-1 {{add 'if #available' version check}} } // Unavailability takes precedence over availability and is inherited