Skip to content

Commit

Permalink
Importer: Synthesize inferred availability for unannotated Obj-C prot…
Browse files Browse the repository at this point in the history
…ocols

Based on feedback from Jordan, update r30060 to synthesize availability
attributes on unannotated Obj-C protocols in the importer. This has a
user-visible effect: calls to protocol requirements with these synthesized
attributes will now cause an availability error if the requirement is not
available in the calling type refinement context.

Swift SVN r30096
  • Loading branch information
devincoughlin committed Jul 10, 2015
1 parent 60620fc commit 259b547
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 143 deletions.
14 changes: 14 additions & 0 deletions include/swift/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -207,6 +208,19 @@ class AvailabilityInference {
applyInferredAvailableAttrs(Decl *ToDecl,
ArrayRef<const Decl *> 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<VersionRange> annotatedAvailableRange(const Decl *D,
ASTContext &C);

};

} // end namespace swift
Expand Down
83 changes: 83 additions & 0 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -119,3 +120,85 @@ void AvailabilityInference::applyInferredAvailableAttrs(
Attrs.add(Attr);
}
}

Optional<VersionRange>
AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
Optional<VersionRange> AnnotatedRange;

for (auto Attr : D->getAttrs()) {
auto *AvailAttr = dyn_cast<AvailableAttr>(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<VersionRange> 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<ExtensionDecl>(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 &AC;
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;
}
48 changes: 48 additions & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProtocolDecl>(swiftContext);
if (!proto || proto->getAttrs().hasAttribute<AvailableAttr>())
return;

for (Decl *member : members) {
inferProtocolMemberAvailability(member);
}

}

static bool
Expand Down Expand Up @@ -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<AvailableAttr>())
return;

auto *valueDecl = dyn_cast<ValueDecl>(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())
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
81 changes: 6 additions & 75 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 &AC;
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,
Expand All @@ -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);
Expand All @@ -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<AvailableAttr>() ||
requirement->getAttrs().hasAttribute<AvailableAttr>()) {
return false;
}

VersionRange inferredRequiredRange = VersionRange::all();
if (auto *requirementFuncDecl = dyn_cast<FuncDecl>(requirement)) {
inferredRequiredRange = inferAvailableRange(requirementFuncDecl, Context);
} else if (auto *requirementVarDecl = dyn_cast<VarDecl>(requirement)) {
inferredRequiredRange = inferAvailableRange(requirementVarDecl, Context);
}

requiredRange.constrainWith(inferredRequiredRange);

return requiredRange.isContainedIn(witnessRange);
}

Expand Down
62 changes: 5 additions & 57 deletions lib/Sema/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -739,60 +739,6 @@ void TypeChecker::diagnoseAmbiguousMemberType(Type baseTy,
}
}

Optional<VersionRange> TypeChecker::annotatedAvailableRange(const Decl *D,
ASTContext &Ctx) {
Optional<VersionRange> AnnotatedRange;

for (auto Attr : D->getAttrs()) {
auto *AvailAttr = dyn_cast<AvailableAttr>(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<VersionRange> 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<ExtensionDecl>(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,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -1331,7 +1278,7 @@ TypeChecker::overApproximateOSVersionsAtLocation(SourceLoc loc,
loc = D->getLoc();

Optional<VersionRange> Range =
TypeChecker::annotatedAvailableRange(D, Context);
AvailabilityInference::annotatedAvailableRange(D, Context);

if (Range.hasValue()) {
OverApproximateVersionRange.constrainWith(Range.getValue());
Expand All @@ -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);

Expand Down
Loading

0 comments on commit 259b547

Please sign in to comment.