Skip to content

Commit

Permalink
Sema: Teach ExportContext to compute whether we're inside a deprecate…
Browse files Browse the repository at this point in the history
…d declaration
  • Loading branch information
slavapestov committed Oct 21, 2020
1 parent 548b96a commit d579b69
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 30 deletions.
10 changes: 5 additions & 5 deletions lib/Sema/TypeCheckAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1513,7 +1513,7 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
loc = varDecl->getNameLoc();

diagnoseTypeAvailability(typeRepr, type, loc,
Where.forReason(reason), flags);
Where.withReason(reason), flags);
}

void checkGenericParams(const GenericContext *ownerCtx,
Expand Down Expand Up @@ -1751,14 +1751,14 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
return isExported(valueMember);
});

Where = wasWhere.forExported(hasExportedMembers);
checkType(ED->getExtendedType(), ED->getExtendedTypeRepr(), ED,
Where = wasWhere.withExported(hasExportedMembers);
checkType(ED->getExtendedType(), ED->getExtendedTypeRepr(), ED,
ExportabilityReason::ExtensionWithPublicMembers);

// 3) If the extension contains exported members or defines conformances,
// the 'where' clause must only name exported types.
Where = wasWhere.forExported(hasExportedMembers ||
!ED->getInherited().empty());
Where = wasWhere.withExported(hasExportedMembers ||
!ED->getInherited().empty());
checkConstrainedExtensionRequirements(ED, hasExportedMembers);
}

Expand Down
111 changes: 91 additions & 20 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@
using namespace swift;

ExportContext::ExportContext(DeclContext *DC, FragileFunctionKind kind,
bool spi, bool exported)
bool spi, bool exported, bool deprecated)
: DC(DC), FragileKind(kind) {
SPI = spi;
Exported = exported;
Deprecated = deprecated;
Reason = ExportabilityReason::General;
}

Expand Down Expand Up @@ -95,7 +96,73 @@ bool swift::isExported(const Decl *D) {
return true;
}

template<typename Fn>
static void forEachOuterDecl(DeclContext *DC, Fn fn) {
for (; !DC->isModuleScopeContext(); DC = DC->getParent()) {
switch (DC->getContextKind()) {
case DeclContextKind::AbstractClosureExpr:
case DeclContextKind::TopLevelCodeDecl:
case DeclContextKind::SerializedLocal:
case DeclContextKind::Module:
case DeclContextKind::FileUnit:
break;

case DeclContextKind::Initializer:
if (auto *PBI = dyn_cast<PatternBindingInitializer>(DC))
fn(PBI->getBinding());
break;

case DeclContextKind::SubscriptDecl:
fn(cast<SubscriptDecl>(DC));
break;

case DeclContextKind::EnumElementDecl:
fn(cast<EnumElementDecl>(DC));
break;

case DeclContextKind::AbstractFunctionDecl:
fn(cast<AbstractFunctionDecl>(DC));

if (auto *AD = dyn_cast<AccessorDecl>(DC))
fn(AD->getStorage());
break;

case DeclContextKind::GenericTypeDecl:
fn(cast<GenericTypeDecl>(DC));
break;

case DeclContextKind::ExtensionDecl:
fn(cast<ExtensionDecl>(DC));
break;
}
}
}

static void computeExportContextBits(Decl *D,
bool *implicit, bool *deprecated) {
if (D->isImplicit())
*implicit = true;

if (D->getAttrs().getDeprecated(D->getASTContext()))
*deprecated = true;

if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
for (unsigned i = 0, e = PBD->getNumPatternEntries(); i < e; ++i) {
if (auto *VD = PBD->getAnchoringVarDecl(i))
computeExportContextBits(VD, implicit, deprecated);
}
}
}

ExportContext ExportContext::forDeclSignature(Decl *D) {
bool implicit = false;
bool deprecated = false;
computeExportContextBits(D, &implicit, &deprecated);
forEachOuterDecl(D->getDeclContext(),
[&](Decl *D) {
computeExportContextBits(D, &implicit, &deprecated);
});

auto *DC = D->getInnermostDeclContext();
auto fragileKind = DC->getFragileFunctionKind();

Expand All @@ -113,11 +180,19 @@ ExportContext ExportContext::forDeclSignature(Decl *D) {

bool exported = ::isExported(D);

return ExportContext(DC, fragileKind, spi, exported);
return ExportContext(DC, fragileKind, spi, exported, deprecated);
}

ExportContext ExportContext::forFunctionBody(DeclContext *DC) {
;
assert(DC && "Use ExportContext::forImplicit() for implicit decls");

bool implicit = false;
bool deprecated = false;
forEachOuterDecl(DC,
[&](Decl *D) {
computeExportContextBits(D, &implicit, &deprecated);
});

auto fragileKind = DC->getFragileFunctionKind();

bool spi = false;
Expand All @@ -129,16 +204,21 @@ ExportContext ExportContext::forFunctionBody(DeclContext *DC) {
assert(fragileKind.kind == FragileFunctionKind::None);
}

return ExportContext(DC, fragileKind, spi, exported);
return ExportContext(DC, fragileKind, spi, exported, deprecated);
}

ExportContext ExportContext::forImplicit() {
return ExportContext(nullptr, FragileFunctionKind(),
false, false, false);
}

ExportContext ExportContext::forReason(ExportabilityReason reason) const {
ExportContext ExportContext::withReason(ExportabilityReason reason) const {
auto copy = *this;
copy.Reason = reason;
return copy;
}

ExportContext ExportContext::forExported(bool exported) const {
ExportContext ExportContext::withExported(bool exported) const {
auto copy = *this;
copy.Exported = isExported() && exported;
return copy;
Expand Down Expand Up @@ -1672,17 +1752,6 @@ static bool isInsideCompatibleUnavailableDeclaration(
return someEnclosingDeclMatches(ReferenceRange, ReferenceDC, IsUnavailable);
}

/// Returns true if the reference is lexically contained in a declaration
/// that is deprecated on all deployment targets.
static bool isInsideDeprecatedDeclaration(SourceRange ReferenceRange,
const DeclContext *ReferenceDC){
auto IsDeprecated = [](const Decl *D) {
return D->getAttrs().getDeprecated(D->getASTContext());
};

return someEnclosingDeclMatches(ReferenceRange, ReferenceDC, IsDeprecated);
}

static void fixItAvailableAttrRename(InFlightDiagnostic &diag,
SourceRange referenceRange,
const ValueDecl *renamedDecl,
Expand Down Expand Up @@ -2059,13 +2128,15 @@ getAccessorKindAndNameForDiagnostics(const ValueDecl *D) {
}

void TypeChecker::diagnoseIfDeprecated(SourceRange ReferenceRange,
const DeclContext *ReferenceDC,
ExportContext Where,
const ValueDecl *DeprecatedDecl,
const ApplyExpr *Call) {
const AvailableAttr *Attr = TypeChecker::getDeprecated(DeprecatedDecl);
if (!Attr)
return;

auto *ReferenceDC = Where.getDeclContext();

// We don't want deprecated declarations to trigger warnings
// in synthesized code.
if (ReferenceRange.isInvalid() &&
Expand All @@ -2075,7 +2146,7 @@ void TypeChecker::diagnoseIfDeprecated(SourceRange ReferenceRange,
// We match the behavior of clang to not report deprecation warnings
// inside declarations that are themselves deprecated on all deployment
// targets.
if (isInsideDeprecatedDeclaration(ReferenceRange, ReferenceDC)) {
if (Where.isDeprecated()) {
return;
}

Expand Down Expand Up @@ -2788,7 +2859,7 @@ AvailabilityWalker::diagAvailability(ConcreteDeclRef declRef, SourceRange R,

// Diagnose for deprecation
if (!isAccessorWithDeprecatedStorage)
TypeChecker::diagnoseIfDeprecated(R, DC, D, call);
TypeChecker::diagnoseIfDeprecated(R, Where, D, call);

if (Flags.contains(DeclAvailabilityFlag::AllowPotentiallyUnavailable))
return false;
Expand Down
26 changes: 22 additions & 4 deletions lib/Sema/TypeCheckAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ class ExportContext {
FragileFunctionKind FragileKind;
unsigned SPI : 1;
unsigned Exported : 1;
unsigned Deprecated : 1;
ExportabilityReason Reason;

ExportContext(DeclContext *DC, FragileFunctionKind kind, bool spi, bool exported);
ExportContext(DeclContext *DC, FragileFunctionKind kind,
bool spi, bool exported, bool deprecated);

public:

Expand All @@ -119,30 +121,46 @@ class ExportContext {
/// it can reference anything.
static ExportContext forFunctionBody(DeclContext *DC);

/// Produce a new context describing an implicit declaration. Implicit code
/// does not have source locations. We simply trust the compiler synthesizes
/// implicit declarations correctly, and skip the checks.
static ExportContext forImplicit();

/// Produce a new context with the same properties as this one, except
/// changing the ExportabilityReason. This only affects diagnostics.
ExportContext forReason(ExportabilityReason reason) const;
ExportContext withReason(ExportabilityReason reason) const;

/// Produce a new context with the same properties as this one, except
/// that if 'exported' is false, the resulting context can reference
/// declarations that are not exported. If 'exported' is true, the
/// resulting context is indentical to this one.
///
/// That is, this will perform a 'bitwise and' on the 'exported' bit.
ExportContext forExported(bool exported) const;
ExportContext withExported(bool exported) const;

DeclContext *getDeclContext() const { return DC; }
DeclContext *getDeclContext() const {
assert(DC != nullptr && "This is an implicit context");
return DC;
}

/// If not 'None', the context has the inlinable function body restriction.
FragileFunctionKind getFragileFunctionKind() const { return FragileKind; }

/// If true, the context is part of a synthesized declaration, and
/// availability checking should be disabled.
bool isImplicit() const { return DC == nullptr; }

/// If true, the context is SPI and can reference SPI declarations.
bool isSPI() const { return SPI; }

/// If true, the context is exported and cannot reference SPI declarations
/// or declarations from `@_implementationOnly` imports.
bool isExported() const { return Exported; }

/// If true, the context is part of a deprecated declaration and can
/// reference other deprecated declarations without warning.
bool isDeprecated() const { return Deprecated; }

/// If true, the context can only reference exported declarations, either
/// because it is the signature context of an exported declaration, or
/// because it is the function body context of an inlinable function.
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,7 @@ const AvailableAttr *getDeprecated(const Decl *D);
/// Callers can provide a lambda that adds additional information (such as a
/// fixit hint) to the deprecation diagnostic, if it is emitted.
void diagnoseIfDeprecated(SourceRange SourceRange,
const DeclContext *ReferenceDC,
ExportContext Where,
const ValueDecl *DeprecatedDecl,
const ApplyExpr *Call);
/// @}
Expand Down
42 changes: 42 additions & 0 deletions test/Sema/availability_deprecated.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// RUN: %target-typecheck-verify-swift -parse-as-library

struct DummyType {}

@available(*, deprecated, renamed: "&-")
func -(x: DummyType, y: DummyType) {}

// We don't warn if a deprecated declaration is referenced from
// within another deprecated declaration.

@available(*, deprecated)
func testDeprecatedReferencingDeprecated1(x: DummyType, y: DummyType) {
x - y // no-warning
}

@available(*, deprecated)
var testDeprecatedReferencingDeprecated2: () {
let x = DummyType()
let y = DummyType()
x - y // no-warning
}

// FIXME: This doesn't work because the file is parsed in script mode.
@available(*, deprecated)
var testDeprecatedReferencingDeprecated3: () = DummyType() - DummyType() // no-warning

struct HasDeprecatedMembers {
@available(*, deprecated)
func testDeprecatedReferencingDeprecated1(x: DummyType, y: DummyType) {
x - y // no-warning
}

@available(*, deprecated)
var testDeprecatedReferencingDeprecated2: () {
let x = DummyType()
let y = DummyType()
x - y // no-warning
}

@available(*, deprecated)
var testDeprecatedReferencingDeprecated3: () = DummyType() - DummyType() // no-warning
}
44 changes: 44 additions & 0 deletions test/Sema/availability_deprecated_script_mode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: %target-typecheck-verify-swift

// We don't warn if a deprecated declaration is referenced from
// within another deprecated declaration.

struct DummyType {}

@available(*, deprecated, renamed: "&-")
func -(x: DummyType, y: DummyType) {}

@available(*, deprecated)
func testDeprecatedReferencingDeprecated1(x: DummyType, y: DummyType) {
x - y // no-warning
}

@available(*, deprecated)
var testDeprecatedReferencingDeprecated2: () {
let x = DummyType()
let y = DummyType()
x - y // no-warning
}

// FIXME: This doesn't work because the file is parsed in script mode.
@available(*, deprecated)
var testDeprecatedReferencingDeprecated3: () = DummyType() - DummyType()
// expected-warning@-1 {{'-' is deprecated}}
// expected-note@-2 {{use '&-' instead}}

struct HasDeprecatedMembers {
@available(*, deprecated)
func testDeprecatedReferencingDeprecated1(x: DummyType, y: DummyType) {
x - y // no-warning
}

@available(*, deprecated)
var testDeprecatedReferencingDeprecated2: () {
let x = DummyType()
let y = DummyType()
x - y // no-warning
}

@available(*, deprecated)
var testDeprecatedReferencingDeprecated3: () = DummyType() - DummyType() // no-warning
}

0 comments on commit d579b69

Please sign in to comment.