diff --git a/include/swift/AST/Availability.h b/include/swift/AST/Availability.h index fcf9f3aba52c6..2df5aad14b534 100644 --- a/include/swift/AST/Availability.h +++ b/include/swift/AST/Availability.h @@ -17,6 +17,7 @@ #ifndef SWIFT_AST_AVAILABILITY_H #define SWIFT_AST_AVAILABILITY_H +#include "swift/AST/Type.h" #include "swift/Basic/LLVM.h" #include "clang/Basic/VersionTuple.h" #include "llvm/ADT/Optional.h" @@ -207,6 +208,19 @@ class AvailabilityInference { applyInferredAvailableAttrs(Decl *ToDecl, ArrayRef InferredFromDecls, ASTContext &Context); + + static VersionRange inferForType(Type t); + + /// \brief Returns the version range on which a declaration is available + /// We assume a declaration without an annotation is always available. + static VersionRange availableRange(const Decl *D, ASTContext &C); + + /// \brief Returns the version range on which the declaration for which + /// declaration is annotated as available, or None if the declaration + /// has not availability annotation. + static Optional annotatedAvailableRange(const Decl *D, + ASTContext &C); + }; } // end namespace swift diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 2281a8301ecce..095ae75419134 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -18,6 +18,7 @@ #include "swift/AST/Attr.h" #include "swift/AST/Availability.h" #include "swift/AST/PlatformKind.h" +#include "swift/AST/TypeWalker.h" using namespace swift; @@ -119,3 +120,85 @@ void AvailabilityInference::applyInferredAvailableAttrs( Attrs.add(Attr); } } + +Optional +AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) { + Optional AnnotatedRange; + + for (auto Attr : D->getAttrs()) { + auto *AvailAttr = dyn_cast(Attr); + if (AvailAttr == NULL || !AvailAttr->Introduced.hasValue() || + !AvailAttr->isActivePlatform(Ctx)) { + continue; + } + + VersionRange AttrRange = + VersionRange::allGTE(AvailAttr->Introduced.getValue()); + + // If we have multiple introduction versions, we will conservatively + // assume the worst case scenario. We may want to be more precise here + // in the future or emit a diagnostic. + + if (AnnotatedRange.hasValue()) { + AnnotatedRange.getValue().meetWith(AttrRange); + } else { + AnnotatedRange = AttrRange; + } + } + + return AnnotatedRange; +} + +VersionRange AvailabilityInference::availableRange(const Decl *D, + ASTContext &Ctx) { + Optional AnnotatedRange = annotatedAvailableRange(D, Ctx); + if (AnnotatedRange.hasValue()) { + return AnnotatedRange.getValue(); + } + + // Unlike other declarations, extensions can be used without referring to them + // by name (they don't have one) in the source. For this reason, when checking + // the available range of a declaration we also need to check to see if it is + // immediately contained in an extension and use the extension's availability + // if the declaration does not have an explicit @available attribute + // itself. This check relies on the fact that we cannot have nested + // extensions. + + DeclContext *DC = D->getDeclContext(); + if (auto *ED = dyn_cast(DC)) { + AnnotatedRange = annotatedAvailableRange(ED, Ctx); + if (AnnotatedRange.hasValue()) { + return AnnotatedRange.getValue(); + } + } + + // Treat unannotated declarations as always available. + return VersionRange::all(); +} + +namespace { +/// Infers the availability required to access a type. +class AvailabilityInferenceTypeWalker : public TypeWalker { +public: + ASTContext ∾ + VersionRange AvailableRange = VersionRange::all(); + + AvailabilityInferenceTypeWalker(ASTContext &AC) : AC(AC) {} + + virtual Action walkToTypePre(Type ty) { + if (auto *nominalDecl = ty.getCanonicalTypeOrNull().getAnyNominal()) { + AvailableRange.meetWith( + AvailabilityInference::availableRange(nominalDecl, AC)); + } + + return Action::Continue; + } +}; +}; + + +VersionRange AvailabilityInference::inferForType(Type t) { + AvailabilityInferenceTypeWalker walker(t->getASTContext()); + t.walk(walker); + return walker.AvailableRange; +} diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index c3bb9aa559c1b..63a3b071726fc 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -4087,6 +4087,21 @@ namespace { members.push_back(member); } + + // Hack to deal with unannotated Objective-C protocols. If the protocol + // comes from clang and is not annotated and the protocol requirement + // itself is not annotated, then infer availability of the requirement + // based on its types. This makes it possible for a type to conform to an + // Objective-C protocol that is missing annotations but whose requirements + // use types that are less available than the conforming type. + auto *proto = dyn_cast(swiftContext); + if (!proto || proto->getAttrs().hasAttribute()) + return; + + for (Decl *member : members) { + inferProtocolMemberAvailability(member); + } + } static bool @@ -4448,6 +4463,39 @@ namespace { VD->getAttrs().add(attr); } + /// Synthesize availability attributes for protocol requirements + /// based on availability of the types mentioned in the requirements. + void inferProtocolMemberAvailability(Decl *member) { + // Don't synthesize attributes if there is already an + // availability annotation. + if (member->getAttrs().hasAttribute()) + return; + + auto *valueDecl = dyn_cast(member); + if (!valueDecl) + return; + + VersionRange requiredRange = + AvailabilityInference::inferForType(valueDecl->getType()); + + if (!requiredRange.hasLowerEndpoint()) + return; + + ASTContext &C = Impl.SwiftContext; + clang::VersionTuple noVersion; + auto AvAttr = new (C) AvailableAttr(SourceLoc(), SourceRange(), + targetPlatform(C.LangOpts), + /*message=*/StringRef(), + /*rename=*/StringRef(), + requiredRange.getLowerEndpoint(), + /*deprecated=*/noVersion, + /*obsoleted=*/noVersion, + UnconditionalAvailabilityKind::None, + /*implicit=*/false); + + valueDecl->getAttrs().add(AvAttr); + } + Decl *VisitObjCProtocolDecl(const clang::ObjCProtocolDecl *decl) { Identifier name = Impl.importName(decl); if (name.empty()) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 2450f7bb8de07..ae650c0b78a70 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -820,7 +820,8 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) { while (EnclosingDecl) { EnclosingAnnotatedRange = - TypeChecker::annotatedAvailableRange(EnclosingDecl, TC.Context); + AvailabilityInference::annotatedAvailableRange(EnclosingDecl, + TC.Context); if (EnclosingAnnotatedRange.hasValue()) break; diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index d42812992c9dc..91b7745cecb56 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -5686,56 +5686,13 @@ bool TypeChecker::isAvailabilitySafeForOverride(ValueDecl *override, // API availability ranges are contravariant: make sure the version range // of an overridden declaration is fully contained in the range of the // overriding declaration. - VersionRange overrideRange = TypeChecker::availableRange(override, - Context); - VersionRange baseRange = TypeChecker::availableRange(base, Context); + VersionRange overrideRange = AvailabilityInference::availableRange(override, + Context); + VersionRange baseRange = AvailabilityInference::availableRange(base, Context); return baseRange.isContainedIn(overrideRange); } -namespace { -/// Infers the availability required to access a type. -class AvailabilityInferenceTypeWalker : public TypeWalker { -public: - ASTContext ∾ - VersionRange AvailableRange = VersionRange::all(); - - AvailabilityInferenceTypeWalker(ASTContext &AC) : AC(AC) {} - - virtual Action walkToTypePre(Type ty) { - if (auto *nominalDecl = ty.getCanonicalTypeOrNull().getAnyNominal()) { - AvailableRange.meetWith(TypeChecker::availableRange(nominalDecl, AC)); - } - - return Action::Continue; - } -}; -}; - - -/// Infer the available range for a function from its parameter and return -/// types. -static VersionRange inferAvailableRange(FuncDecl *funcDecl, ASTContext &AC) { - AvailabilityInferenceTypeWalker walker(AC); - - funcDecl->getResultType().walk(walker); - - for (Pattern *pattern : funcDecl->getBodyParamPatterns()) { - pattern->getType().walk(walker); - } - - return walker.AvailableRange; -} - -/// Infer the available range for a property from its type. -static VersionRange inferAvailableRange(VarDecl *varDecl, ASTContext &AC) { - AvailabilityInferenceTypeWalker walker(AC); - - varDecl->getType().walk(walker); - - return walker.AvailableRange; -} - bool TypeChecker::isAvailabilitySafeForConformance( ValueDecl *witness, ValueDecl *requirement, NormalProtocolConformance *conformance, @@ -5760,8 +5717,9 @@ bool TypeChecker::isAvailabilitySafeForConformance( // (an over-approximation of) the intersection of the witnesses's available // range with both the conforming type's available range and the protocol // declaration's available range. - VersionRange witnessRange = TypeChecker::availableRange(witness, Context); - requiredRange = TypeChecker::availableRange(requirement, Context); + VersionRange witnessRange = AvailabilityInference::availableRange(witness, + Context); + requiredRange = AvailabilityInference::availableRange(requirement, Context); VersionRange rangeOfConformingDecl = overApproximateOSVersionsAtLocation( conformingDecl->getLoc(), conformingDecl); @@ -5777,33 +5735,6 @@ bool TypeChecker::isAvailabilitySafeForConformance( witnessRange.constrainWith(rangeOfProtocolDecl); requiredRange.constrainWith(rangeOfProtocolDecl); - if (requiredRange.isContainedIn(witnessRange)) - return true; - - // Hack to deal with unannotated Objective-C protocols. If the protocol - // comes from clang and is not annotated and the protocol requirement itself - // is not annotated, then infer availability of the requirement based - // on its types. This makes it possible for a type to conform to an - // Objective-C protocol that is missing annotations but whose requirements - // use types that are less available than the conforming type. - - if (!protocolDecl->hasClangNode()) - return false; - - if (protocolDecl->getAttrs().hasAttribute() || - requirement->getAttrs().hasAttribute()) { - return false; - } - - VersionRange inferredRequiredRange = VersionRange::all(); - if (auto *requirementFuncDecl = dyn_cast(requirement)) { - inferredRequiredRange = inferAvailableRange(requirementFuncDecl, Context); - } else if (auto *requirementVarDecl = dyn_cast(requirement)) { - inferredRequiredRange = inferAvailableRange(requirementVarDecl, Context); - } - - requiredRange.constrainWith(inferredRequiredRange); - return requiredRange.isContainedIn(witnessRange); } diff --git a/lib/Sema/TypeChecker.cpp b/lib/Sema/TypeChecker.cpp index e8da5da4c8668..97307d0c4406c 100644 --- a/lib/Sema/TypeChecker.cpp +++ b/lib/Sema/TypeChecker.cpp @@ -739,60 +739,6 @@ void TypeChecker::diagnoseAmbiguousMemberType(Type baseTy, } } -Optional TypeChecker::annotatedAvailableRange(const Decl *D, - ASTContext &Ctx) { - Optional AnnotatedRange; - - for (auto Attr : D->getAttrs()) { - auto *AvailAttr = dyn_cast(Attr); - if (AvailAttr == NULL || !AvailAttr->Introduced.hasValue() || - !AvailAttr->isActivePlatform(Ctx)) { - continue; - } - - VersionRange AttrRange = - VersionRange::allGTE(AvailAttr->Introduced.getValue()); - - // If we have multiple introduction versions, we will conservatively - // assume the worst case scenario. We may want to be more precise here - // in the future or emit a diagnostic. - - if (AnnotatedRange.hasValue()) { - AnnotatedRange.getValue().meetWith(AttrRange); - } else { - AnnotatedRange = AttrRange; - } - } - - return AnnotatedRange; -} - -VersionRange TypeChecker::availableRange(const Decl *D, ASTContext &Ctx) { - Optional AnnotatedRange = annotatedAvailableRange(D, Ctx); - if (AnnotatedRange.hasValue()) { - return AnnotatedRange.getValue(); - } - - // Unlike other declarations, extensions can be used without referring to them - // by name (they don't have one) in the source. For this reason, when checking - // the available range of a declaration we also need to check to see if it is - // immediately contained in an extension and use the extension's availability - // if the declaration does not have an explicit @available attribute - // itself. This check relies on the fact that we cannot have nested - // extensions. - - DeclContext *DC = D->getDeclContext(); - if (auto *ED = dyn_cast(DC)) { - AnnotatedRange = annotatedAvailableRange(ED, Ctx); - if (AnnotatedRange.hasValue()) { - return AnnotatedRange.getValue(); - } - } - - // Treat unannotated declarations as always available. - return VersionRange::all(); -} - /// Returns the first availability attribute on the declaration that is active /// on the target platform. static const AvailableAttr *getActiveAvailableAttribute(const Decl *D, @@ -930,7 +876,8 @@ class TypeRefinementContextBuilder : private ASTWalker { // The potential versions in the declaration are constrained by both // the declared availability of the declaration and the potential versions // of its lexical context. - VersionRange DeclVersionRange = TypeChecker::availableRange(D, TC.Context); + VersionRange DeclVersionRange = + swift::AvailabilityInference::availableRange(D, TC.Context); DeclVersionRange.meetWith(getCurrentTRC()->getPotentialVersions()); TypeRefinementContext *NewTRC = @@ -1331,7 +1278,7 @@ TypeChecker::overApproximateOSVersionsAtLocation(SourceLoc loc, loc = D->getLoc(); Optional Range = - TypeChecker::annotatedAvailableRange(D, Context); + AvailabilityInference::annotatedAvailableRange(D, Context); if (Range.hasValue()) { OverApproximateVersionRange.constrainWith(Range.getValue()); @@ -1354,7 +1301,8 @@ bool TypeChecker::isDeclAvailable(const Decl *D, SourceLoc referenceLoc, const DeclContext *referenceDC, VersionRange &OutAvailableRange) { - VersionRange safeRangeUnderApprox = TypeChecker::availableRange(D, Context); + VersionRange safeRangeUnderApprox = + AvailabilityInference::availableRange(D, Context); VersionRange runningOSOverApprox = overApproximateOSVersionsAtLocation( referenceLoc, referenceDC); diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 1735aafdbc8ee..ef3c52b457550 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -1502,16 +1502,6 @@ class TypeChecker final : public LazyResolver { /// potentially unavailable API elements /// @{ - /// \brief Returns the version range on which a declaration is available - /// We assume a declaration without an annotation is always available. - static VersionRange availableRange(const Decl *D, ASTContext &C); - - /// \brief Returns the version range on which the declaration for which - /// declaration is annotated as available, or None if the declaration - /// has not availability annotation. - static Optional annotatedAvailableRange(const Decl *D, - ASTContext &C); - /// \brief Returns true if the availability of the overridding declaration /// makes it a safe override, given the availability of the base declation. bool isAvailabilitySafeForOverride(ValueDecl *override, ValueDecl *base); diff --git a/test/IDE/print_clang_framework.swift b/test/IDE/print_clang_framework.swift index 6d0d5e1c80d9d..451f550395a6f 100644 --- a/test/IDE/print_clang_framework.swift +++ b/test/IDE/print_clang_framework.swift @@ -62,3 +62,24 @@ // FOUNDATION-NEXT: {{^}} case Second{{$}} // FOUNDATION-NEXT: {{^}} @available(OSX 10.10, *){{$}} // FOUNDATION-NEXT: {{^}} case Third{{$}} + +// FOUNDATION-LABEL: {{^}}/// Aaa. UnannotatedFrameworkProtocol. Bbb. +// FOUNDATION-NEXT: {{^}}protocol UnannotatedFrameworkProtocol {{{$}} +// FOUNDATION-NEXT: {{^}} @available(OSX 10.10, *){{$}} +// FOUNDATION-NEXT: {{^}} func doSomethingWithClass(k: AnnotatedFrameworkClass?){{$}} +// FOUNDATION-NEXT: {{^}} @available(OSX 10.10, *){{$}} +// FOUNDATION-NEXT: {{^}} func doSomethingWithNonNullableClass(k: AnnotatedFrameworkClass){{$}} +// FOUNDATION-NEXT: {{^}} @available(OSX 10.10, *){{$}} +// FOUNDATION-NEXT: {{^}} func doSomethingWithIUOClass(k: AnnotatedFrameworkClass!){{$}} +// FOUNDATION-NEXT: {{^}} @available(OSX 10.10, *){{$}} +// FOUNDATION-NEXT: {{^}} func returnSomething() -> AnnotatedFrameworkClass?{{$}} +// FOUNDATION-NEXT: {{^}} func noUnavailableTypesInSignature(){{$}} +// FOUNDATION-NEXT: {{^}} @available(OSX 10.11, *){{$}} +// FOUNDATION-NEXT: {{^}} func doSomethingWithClass(k: AnnotatedFrameworkClass, andLaterClass lk: AnnotatedLaterFrameworkClass){{$}} +// FOUNDATION-NEXT: {{^}} @available(OSX 10.10, *){{$}} +// FOUNDATION-NEXT: {{^}} var someProperty: AnnotatedFrameworkClass { get set }{{$}} + +// FOUNDATION-LABEL: {{^}}/// Aaa. AnnotatedFrameworkProtocol. Bbb. +// FOUNDATION-NEXT:@available(OSX 10.9, *){{$}} +// FOUNDATION-NEXT: {{^}}protocol AnnotatedFrameworkProtocol {{{$}} +// FOUNDATION-NEXT: {{^}} func returnSomething() -> AnnotatedFrameworkClass?{{$}} diff --git a/test/Inputs/clang-importer-sdk/usr/include/Foundation.h b/test/Inputs/clang-importer-sdk/usr/include/Foundation.h index 65d9f34108594..439201f62d9c5 100644 --- a/test/Inputs/clang-importer-sdk/usr/include/Foundation.h +++ b/test/Inputs/clang-importer-sdk/usr/include/Foundation.h @@ -790,6 +790,7 @@ __attribute__((availability(macosx,introduced=10.11))) @end +/// Aaa. UnannotatedFrameworkProtocol. Bbb. @protocol UnannotatedFrameworkProtocol -(void)doSomethingWithClass:(AnnotatedFrameworkClass * __nullable)k; -(void)doSomethingWithNonNullableClass:(AnnotatedFrameworkClass * __nonnull)k; @@ -804,6 +805,12 @@ __attribute__((availability(macosx,introduced=10.11))) @end +/// Aaa. AnnotatedFrameworkProtocol. Bbb. +__attribute__((availability(macosx,introduced=10.9))) +@protocol AnnotatedFrameworkProtocol +-(AnnotatedFrameworkClass * __nullable)returnSomething; +@end + @interface UnusedResults : NSObject -(NSInteger)producesResult __attribute__((warn_unused_result)); @end diff --git a/test/Sema/availability_versions_objc_api.swift b/test/Sema/availability_versions_objc_api.swift index 77a033b42219c..a75df013ca3d8 100644 --- a/test/Sema/availability_versions_objc_api.swift +++ b/test/Sema/availability_versions_objc_api.swift @@ -171,3 +171,15 @@ class UserClass : UnannotatedFrameworkProtocol { set(newValue) { } } } + +func callViaUnannotatedFrameworkProtocol(p: UnannotatedFrameworkProtocol) { + let _ = p.returnSomething() // expected-error {{'returnSomething()' is only available on OS X 10.10 or newer}} + // expected-note@-1 {{add @available attribute to enclosing global function}} + // expected-note@-2 {{add 'if #available' version check}} +} + +func callViaAnnotatedFrameworkProtocol(p: AnnotatedFrameworkProtocol) { + // We won't synthesize availability for AnnotatedFrameworkProtocol because + // the protocol has an availability annotation on it. + let _ = p.returnSomething() +}