From 91ddf93d0f1e2d360cd9a3f8d19dacf71b01df2c Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Tue, 2 Sep 2025 18:31:04 -0700 Subject: [PATCH] Sema: Introduce an ExportabilityReason for availability attributes. This allows diagnostics to be more precise and will also support logic that allows for special cases for `@available` attributes in exportability checking. Also fixes a bug where the exportability of `@available` attributes attached to extensions were diagnosed twice for slightly differing reasons. --- include/swift/AST/DiagnosticsSema.def | 8 +- lib/Sema/ResilienceDiagnostics.cpp | 7 ++ lib/Sema/TypeCheckAccess.cpp | 16 +--- lib/Sema/TypeCheckAvailability.h | 3 +- ...l-import-availability-custom-domains.swift | 86 ++++++++++--------- 5 files changed, 61 insertions(+), 59 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 0a38e83b5a36b..a332ca7fbca4e 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3826,7 +3826,8 @@ ERROR(decl_from_hidden_module,none, "as result builder here|" "in an extension with public or '@usableFromInline' members|" "in an extension with conditional conformances|" - "in a public or '@usableFromInline' conformance}1; " + "in a public or '@usableFromInline' conformance|" + "in an '@available' attribute here}1; " "%select{%2 has been imported as implementation-only|" "it is an SPI imported from %2|" "it is SPI|" @@ -3841,7 +3842,8 @@ ERROR(typealias_desugars_to_type_from_hidden_module,none, "as result builder here|" "in an extension with public or '@usableFromInline' members|" "in an extension with conditional conformance|" - "in a public or '@usableFromInline' conformance}3 " + "in a public or '@usableFromInline' conformance|" + "<>}3 " "because %select{%4 has been imported as implementation-only|" "it is an SPI imported from %4|" "<>|" @@ -3855,7 +3857,7 @@ ERROR(conformance_from_implementation_only_module,none, "as result builder here|" "in an extension with public or '@usableFromInline' members|" "in an extension with conditional conformances|" - "<>}2; " + "<>|<>}2; " "%select{%3 has been imported as implementation-only|" "the conformance is declared as SPI in %3|" "the conformance is declared as SPI|" diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index 87140669fef19..c6227bbe22137 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -257,6 +257,13 @@ static bool diagnoseValueDeclRefExportability(SourceLoc loc, const ValueDecl *D, return true; case ExportabilityReason::Inheritance: return isa(D); + case ExportabilityReason::AvailableAttribute: + // If the context is an extension and that extension has an explicit + // access level, then access has already been diagnosed for the + // @available attribute. + if (auto *ED = dyn_cast_or_null(DC->getAsDecl())) + return !ED->getAttrs().getAttribute(); + return false; default: return false; } diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index 2493ace340716..fcc368ea843f5 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -2261,11 +2261,13 @@ class DeclAvailabilityChecker : public DeclVisitor { void checkAvailabilityDomains(const Decl *D) { D = D->getAbstractSyntaxDeclForAttributes(); + + auto where = Where.withReason(ExportabilityReason::AvailableAttribute); for (auto attr : D->getSemanticAvailableAttrs()) { if (auto *domainDecl = attr.getDomain().getDecl()) { diagnoseDeclAvailability(domainDecl, attr.getParsedAttr()->getDomainLoc(), nullptr, - Where, std::nullopt); + where, std::nullopt); } } } @@ -2557,18 +2559,6 @@ class DeclAvailabilityChecker : public DeclVisitor { : ExportabilityReason::ExtensionWithConditionalConformances; checkConstrainedExtensionRequirements(ED, reason); - // Diagnose the exportability of the availability domains referenced by the - // @available attributes attached to the extension. - if (Where.isExported()) { - for (auto availableAttr : ED->getSemanticAvailableAttrs()) { - if (auto *domainDecl = availableAttr.getDomain().getDecl()) { - TypeChecker::diagnoseDeclRefExportability( - availableAttr.getParsedAttr()->getDomainLoc(), domainDecl, - Where.withReason(reason)); - } - } - } - // If we haven't already visited the extended nominal visit it here. // This logic is too wide but prevents false reports of an unused public // import. We should instead check for public generic requirements diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index c14c525b44df8..30d5051de022a 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -77,7 +77,8 @@ enum class ExportabilityReason : unsigned { ResultBuilder, ExtensionWithPublicMembers, ExtensionWithConditionalConformances, - Inheritance + Inheritance, + AvailableAttribute, }; /// A description of the restrictions on what declarations can be referenced diff --git a/test/ClangImporter/access-level-import-availability-custom-domains.swift b/test/ClangImporter/access-level-import-availability-custom-domains.swift index 6963ed1f1532b..26d39dbc58da5 100644 --- a/test/ClangImporter/access-level-import-availability-custom-domains.swift +++ b/test/ClangImporter/access-level-import-availability-custom-domains.swift @@ -16,11 +16,11 @@ private import Rivers // also re-exported by Oceans internal import Oceans -// expected-note@-1 22 {{availability domain 'Arctic' imported as 'internal' from 'Oceans' here}} +// expected-note@-1 23 {{availability domain 'Arctic' imported as 'internal' from 'Oceans' here}} // expected-swift5-note@-2 2 {{availability domain 'Arctic' imported as 'internal' from 'Oceans' here}} -// expected-note@-3 23 {{availability domain 'Colorado' imported as 'internal' from 'Oceans' here}} +// expected-note@-3 24 {{availability domain 'Colorado' imported as 'internal' from 'Oceans' here}} // expected-swift5-note@-4 2 {{availability domain 'Colorado' imported as 'internal' from 'Oceans' here}} -// expected-note@-5 22 {{availability domain 'Grand' imported as 'internal' from 'Oceans' here}} +// expected-note@-5 23 {{availability domain 'Grand' imported as 'internal' from 'Oceans' here}} // expected-swift5-note@-6 2 {{availability domain 'Grand' imported as 'internal' from 'Oceans' here}} public import Seas @_spiOnly import Lakes @@ -31,7 +31,7 @@ public import Seas @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public global function 'publicFunc()'}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public func publicFunc() { } @available(Colorado) // expected-swift5-error {{availability domain 'Colorado' used in '@available' on global function 'usableFromInlineFunc()' must be '@usableFromInline' or public}} @@ -40,7 +40,7 @@ public func publicFunc() { } @available(Arctic) // expected-swift5-error {{availability domain 'Arctic' used in '@available' on global function 'usableFromInlineFunc()' must be '@usableFromInline' or public}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} @usableFromInline func usableFromInlineFunc() { } @available(Colorado) // expected-error {{availability domain 'Colorado' is internal and cannot be used in '@available' on public global function 'spiFunc()'}} @@ -74,7 +74,7 @@ private func privateFunc() { } @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public var 'publicGlobalVar'}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public var publicGlobalVar: Int { get { 0 } set { } @@ -86,7 +86,7 @@ public var publicGlobalVar: Int { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public struct 'PublicStruct'}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public struct PublicStruct { } public struct PublicGenericStruct { @@ -98,7 +98,7 @@ public struct PublicGenericStruct { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public initializer 'init(value:)'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public init(value: T) { self.value = value } @@ -109,7 +109,7 @@ public struct PublicGenericStruct { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public property 'publicProperty'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public var publicProperty: T { value } @available(Colorado) // expected-swift5-error {{availability domain 'Colorado' used in '@available' on property 'usableFromInlineProperty' must be '@usableFromInline' or public}} @@ -118,7 +118,7 @@ public struct PublicGenericStruct { @available(Arctic) // expected-swift5-error {{availability domain 'Arctic' used in '@available' on property 'usableFromInlineProperty' must be '@usableFromInline' or public}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} @usableFromInline var usableFromInlineProperty: T { value } public var publicPropertyWithSetter: T { @@ -130,7 +130,7 @@ public struct PublicGenericStruct { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public setter for property 'publicPropertyWithSetter'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} set { value = newValue } } @@ -140,7 +140,7 @@ public struct PublicGenericStruct { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public instance method 'publicMethod()'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public func publicMethod() { } @available(Colorado) // expected-error {{availability domain 'Colorado' is internal and cannot be used in '@available' on public subscript 'subscript(_:)'}} @@ -150,7 +150,7 @@ public struct PublicGenericStruct { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public subscript 'subscript(_:)'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public subscript(indexForSubscriptInColorado: T) -> T { value } } @@ -160,7 +160,7 @@ public struct PublicGenericStruct { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public enum 'PublicEnum'}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public enum PublicEnum { } public enum PublicEnumWithCase { @@ -170,7 +170,7 @@ public enum PublicEnumWithCase { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public enum case 'colorado'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} case colorado } @@ -180,7 +180,7 @@ public enum PublicEnumWithCase { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public class 'PublicClass'}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public class PublicClass { } @available(Colorado) // expected-error {{availability domain 'Colorado' is internal and cannot be used in '@available' on public protocol 'PublicProtocol'}} @@ -189,7 +189,7 @@ public class PublicClass { } @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public protocol 'PublicProtocol'}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public protocol PublicProtocol { } public protocol PublicProtocolWithAssociatedType { @@ -199,7 +199,7 @@ public protocol PublicProtocolWithAssociatedType { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public associated type 'AssociatedType'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} associatedtype AssociatedType @available(Colorado) // expected-error {{availability domain 'Colorado' is internal and cannot be used in '@available' on public instance method 'requirement()'}} @@ -208,7 +208,7 @@ public protocol PublicProtocolWithAssociatedType { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public instance method 'requirement()'}} @available(Baltic) @available(BayBridge) - @available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} + @available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} func requirement() -> AssociatedType } @@ -218,7 +218,7 @@ public protocol PublicProtocolWithAssociatedType { @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public type alias 'PublicTypealias'}} @available(Baltic) @available(BayBridge) -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} public typealias PublicTypealias = Int @available(Colorado) @@ -235,9 +235,20 @@ extension PublicGenericStruct { } @available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public extension of generic struct 'PublicGenericStruct'}} @available(Baltic) @available(BayBridge) -@available(Salt) // FIXME: Should be diangosed +@available(Salt) public extension PublicGenericStruct { } +@available(Colorado) // expected-error {{availability domain 'Colorado' is internal and cannot be used in '@available' on public extension of generic struct 'PublicGenericStruct'}} +@available(Grand) // expected-error {{availability domain 'Grand' is internal and cannot be used in '@available' on public extension of generic struct 'PublicGenericStruct'}} +// expected-warning@-1 {{availability domain 'Grand' is deprecated: Use Colorado instead}} +@available(Arctic) // expected-error {{availability domain 'Arctic' is internal and cannot be used in '@available' on public extension of generic struct 'PublicGenericStruct'}} +@available(Baltic) +@available(BayBridge) +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} +public extension PublicGenericStruct { + func publicMemberOfPublicExtension() { } +} + @available(Colorado) @available(Grand) // expected-warning {{availability domain 'Grand' is deprecated: Use Colorado instead}} @available(Arctic) @@ -248,15 +259,12 @@ extension PublicGenericStruct { func internalMethodInExtensionInColorado() { } } -@available(Colorado) // expected-error {{cannot use availability domain 'Colorado' in an extension with public or '@usableFromInline' members; 'Rivers' was not imported publicly}} -@available(Grand) // expected-error {{cannot use availability domain 'Grand' in an extension with public or '@usableFromInline' members; 'Rivers' was not imported publicly}} -// expected-warning@-1 {{availability domain 'Grand' is deprecated: Use Colorado instead}} -@available(Arctic) // expected-error {{cannot use availability domain 'Arctic' in an extension with public or '@usableFromInline' members; 'Oceans' was not imported publicly}} +@available(Colorado) // expected-error {{cannot use availability domain 'Colorado' in an '@available' attribute here; 'Rivers' was not imported publicly}} +@available(Grand) // expected-error {{cannot use availability domain 'Grand' in an '@available' attribute here; 'Rivers' was not imported publicly}} +@available(Arctic) // expected-error {{cannot use availability domain 'Arctic' in an '@available' attribute here; 'Oceans' was not imported publicly}} @available(Baltic) @available(BayBridge) -// FIXME: Duplicate error -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} -// expected-error@-1 {{cannot use availability domain 'Salt' in an extension with public or '@usableFromInline' members; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} extension PublicGenericStruct { public func publicMethodInExtensionInColorado() { } } @@ -269,26 +277,20 @@ extension PublicGenericStruct { @available(Salt) extension PublicGenericStruct where T: PublicProtocolWithAssociatedType { } -@available(Colorado) // expected-error {{cannot use availability domain 'Colorado' in an extension with conditional conformances; 'Rivers' was not imported publicly}} -@available(Grand) // expected-error {{cannot use availability domain 'Grand' in an extension with conditional conformances; 'Rivers' was not imported publicly}} -// expected-warning@-1 {{availability domain 'Grand' is deprecated: Use Colorado instead}} -@available(Arctic) // expected-error {{cannot use availability domain 'Arctic' in an extension with conditional conformances; 'Oceans' was not imported publicly}} +@available(Colorado) // expected-error {{cannot use availability domain 'Colorado' in an '@available' attribute here; 'Rivers' was not imported publicly}} +@available(Grand) // expected-error {{cannot use availability domain 'Grand' in an '@available' attribute here; 'Rivers' was not imported publicly}} +@available(Arctic) // expected-error {{cannot use availability domain 'Arctic' in an '@available' attribute here; 'Oceans' was not imported publicly}} @available(Baltic) @available(BayBridge) -// FIXME: Duplicate error -@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an extension with conditional conformances; 'Lakes' was imported for SPI only}} -// expected-error@-1 {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} extension PublicGenericStruct: PublicProtocol {} -@available(Colorado) // expected-error {{cannot use availability domain 'Colorado' in an extension with public or '@usableFromInline' members; 'Rivers' was not imported publicly}} -@available(Grand) // expected-error {{cannot use availability domain 'Grand' in an extension with public or '@usableFromInline' members; 'Rivers' was not imported publicly}} -// expected-warning@-1 {{availability domain 'Grand' is deprecated: Use Colorado instead}} -@available(Arctic) // expected-error {{cannot use availability domain 'Arctic' in an extension with public or '@usableFromInline' members; 'Oceans' was not imported publicly}} +@available(Colorado) // expected-error {{cannot use availability domain 'Colorado' in an '@available' attribute here; 'Rivers' was not imported publicly}} +@available(Grand) // expected-error {{cannot use availability domain 'Grand' in an '@available' attribute here; 'Rivers' was not imported publicly}} +@available(Arctic) // expected-error {{cannot use availability domain 'Arctic' in an '@available' attribute here; 'Oceans' was not imported publicly}} @available(Baltic) @available(BayBridge) -// FIXME: Duplicate error -@available(Salt) // expected-error {{cannot use availability domain 'Salt' here; 'Lakes' was imported for SPI only}} -// expected-error@-1 {{cannot use availability domain 'Salt' in an extension with public or '@usableFromInline' members; 'Lakes' was imported for SPI only}} +@available(Salt) // expected-error {{cannot use availability domain 'Salt' in an '@available' attribute here; 'Lakes' was imported for SPI only}} extension PublicGenericStruct: PublicProtocolWithAssociatedType { public func requirement() -> Int { 0 } }