Skip to content

Commit 406a9d9

Browse files
committed
Add the notion of @_implementationOnly overrides
When an @_implementationOnly import includes Objective-C categories for existing types, it's useful to be able to override the members provided in those categories without exposing them to clients of the framework being built. Allow this as long as the overriding declaration is marked as @_implementationOnly itself, with an additional check that the type of the declaration does not change. (Normally overrides are allowed to change in covariant ways.) Part of rdar://50827914
1 parent 194dba6 commit 406a9d9

File tree

13 files changed

+329
-8
lines changed

13 files changed

+329
-8
lines changed

include/swift/AST/Attr.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ SIMPLE_DECL_ATTR(_alwaysEmitIntoClient, AlwaysEmitIntoClient,
396396
83)
397397

398398
SIMPLE_DECL_ATTR(_implementationOnly, ImplementationOnly,
399-
OnImport | UserInaccessible,
399+
OnImport | OnFunc | OnConstructor | OnVar | OnSubscript | UserInaccessible,
400400
84)
401401
DECL_ATTR(_custom, Custom,
402402
OnAnyDecl | UserInaccessible,

include/swift/AST/DiagnosticsSema.def

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2485,6 +2485,18 @@ WARNING(warn_implementation_only_conflict,none,
24852485
NOTE(implementation_only_conflict_here,none,
24862486
"imported as implementation-only here", ())
24872487

2488+
ERROR(implementation_only_decl_non_override,none,
2489+
"'@_implementationOnly' can only be used on overrides", ())
2490+
ERROR(implementation_only_override_changed_type,none,
2491+
"'@_implementationOnly' override must have the same type as the "
2492+
"declaration it overrides (%0)", (Type))
2493+
ERROR(implementation_only_override_without_attr,none,
2494+
"override of '@_implementationOnly' %0 should also be declared "
2495+
"'@_implementationOnly'", (DescriptiveDeclKind))
2496+
ERROR(implementation_only_override_import_without_attr,none,
2497+
"override of %0 imported as implementation-only must be declared "
2498+
"'@_implementationOnly'", (DescriptiveDeclKind))
2499+
24882500
// Derived conformances
24892501
ERROR(cannot_synthesize_init_in_extension_of_nonfinal,none,
24902502
"implementation of %0 for non-final class cannot be automatically "

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ PrintOptions PrintOptions::printParseableInterfaceFile(bool preferTypeRepr) {
126126

127127
class ShouldPrintForParseableInterface : public ShouldPrintChecker {
128128
bool shouldPrint(const Decl *D, const PrintOptions &options) override {
129+
// Skip anything that is marked `@_implementationOnly` itself.
130+
if (D->getAttrs().hasAttribute<ImplementationOnlyAttr>())
131+
return false;
132+
129133
// Skip anything that isn't 'public' or '@usableFromInline'.
130134
if (auto *VD = dyn_cast<ValueDecl>(D)) {
131135
if (!isPublicOrUsableFromInline(VD)) {

lib/PrintAsObjC/PrintAsObjC.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
204204
}
205205

206206
bool shouldInclude(const ValueDecl *VD) {
207-
return isVisibleToObjC(VD, minRequiredAccess);
207+
return isVisibleToObjC(VD, minRequiredAccess) &&
208+
!VD->getAttrs().hasAttribute<ImplementationOnlyAttr>();
208209
}
209210

210211
private:

lib/Sema/TypeCheckAccess.cpp

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,9 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16521652
explicit ExportabilityChecker(TypeChecker &TC) : TC(TC) {}
16531653

16541654
static bool shouldSkipChecking(const ValueDecl *VD) {
1655+
if (VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
1656+
return true;
1657+
16551658
// Accessors are handled as part of their Var or Subscript, and we don't
16561659
// want to redo extension signature checking for them.
16571660
if (isa<AccessorDecl>(VD))
@@ -1681,13 +1684,46 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16811684
return true;
16821685
}
16831686

1687+
void checkOverride(const ValueDecl *VD) {
1688+
const ValueDecl *overridden = VD->getOverriddenDecl();
1689+
if (!overridden)
1690+
return;
1691+
1692+
auto *SF = VD->getDeclContext()->getParentSourceFile();
1693+
assert(SF && "checking a non-source declaration?");
1694+
1695+
ModuleDecl *M = overridden->getModuleContext();
1696+
if (SF->isImportedImplementationOnly(M)) {
1697+
TC.diagnose(VD, diag::implementation_only_override_import_without_attr,
1698+
overridden->getDescriptiveKind())
1699+
.fixItInsert(VD->getAttributeInsertionLoc(false),
1700+
"@_implementationOnly ");
1701+
TC.diagnose(overridden, diag::overridden_here);
1702+
return;
1703+
}
1704+
1705+
if (overridden->getAttrs().hasAttribute<ImplementationOnlyAttr>()) {
1706+
TC.diagnose(VD, diag::implementation_only_override_without_attr,
1707+
overridden->getDescriptiveKind())
1708+
.fixItInsert(VD->getAttributeInsertionLoc(false),
1709+
"@_implementationOnly ");
1710+
TC.diagnose(overridden, diag::overridden_here);
1711+
return;
1712+
}
1713+
1714+
// FIXME: Check storage decls where the setter is in a separate module from
1715+
// the getter, which is a thing Objective-C can do.
1716+
}
1717+
16841718
void visit(Decl *D) {
16851719
if (D->isInvalid() || D->isImplicit())
16861720
return;
16871721

1688-
if (auto *VD = dyn_cast<ValueDecl>(D))
1722+
if (auto *VD = dyn_cast<ValueDecl>(D)) {
16891723
if (shouldSkipChecking(VD))
16901724
return;
1725+
checkOverride(VD);
1726+
}
16911727

16921728
DeclVisitor<ExportabilityChecker>::visit(D);
16931729
}
@@ -1729,13 +1765,16 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
17291765
void checkNamedPattern(const NamedPattern *NP,
17301766
const llvm::DenseSet<const VarDecl *> &seenVars) {
17311767
const VarDecl *theVar = NP->getDecl();
1732-
// Only check individual variables if we didn't check an enclosing
1733-
// TypedPattern.
1734-
if (seenVars.count(theVar) || theVar->isInvalid())
1735-
return;
17361768
if (shouldSkipChecking(theVar))
17371769
return;
17381770

1771+
checkOverride(theVar);
1772+
1773+
// Only check the type of individual variables if we didn't check an
1774+
// enclosing TypedPattern.
1775+
if (seenVars.count(theVar) || theVar->isInvalid())
1776+
return;
1777+
17391778
checkType(theVar->getInterfaceType(), /*typeRepr*/nullptr, theVar,
17401779
getDiagnoseCallback(theVar), getDiagnoseCallback(theVar));
17411780
}

lib/Sema/TypeCheckAttr.cpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,6 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
772772
IGNORED_ATTR(IBDesignable)
773773
IGNORED_ATTR(IBInspectable)
774774
IGNORED_ATTR(IBOutlet) // checked early.
775-
IGNORED_ATTR(ImplementationOnly)
776775
IGNORED_ATTR(ImplicitlyUnwrappedOptional)
777776
IGNORED_ATTR(Indirect)
778777
IGNORED_ATTR(Inline)
@@ -858,6 +857,8 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
858857
void visitCustomAttr(CustomAttr *attr);
859858
void visitPropertyWrapperAttr(PropertyWrapperAttr *attr);
860859
void visitFunctionBuilderAttr(FunctionBuilderAttr *attr);
860+
861+
void visitImplementationOnlyAttr(ImplementationOnlyAttr *attr);
861862
};
862863
} // end anonymous namespace
863864

@@ -2630,6 +2631,69 @@ void AttributeChecker::visitFunctionBuilderAttr(FunctionBuilderAttr *attr) {
26302631
// Any other validation?
26312632
}
26322633

2634+
void
2635+
AttributeChecker::visitImplementationOnlyAttr(ImplementationOnlyAttr *attr) {
2636+
if (isa<ImportDecl>(D)) {
2637+
// These are handled elsewhere.
2638+
return;
2639+
}
2640+
2641+
auto *VD = cast<ValueDecl>(D);
2642+
auto *overridden = VD->getOverriddenDecl();
2643+
if (!overridden) {
2644+
diagnoseAndRemoveAttr(attr, diag::implementation_only_decl_non_override);
2645+
return;
2646+
}
2647+
2648+
// Check if VD has the exact same type as what it overrides.
2649+
// Note: This is specifically not using `swift::getMemberTypeForComparison`
2650+
// because that erases more information than we want, like `throws`-ness.
2651+
auto baseInterfaceTy = overridden->getInterfaceType();
2652+
auto derivedInterfaceTy = VD->getInterfaceType();
2653+
2654+
auto selfInterfaceTy = VD->getDeclContext()->getDeclaredInterfaceType();
2655+
2656+
auto overrideInterfaceTy =
2657+
selfInterfaceTy->adjustSuperclassMemberDeclType(overridden, VD,
2658+
baseInterfaceTy);
2659+
2660+
if (isa<AbstractFunctionDecl>(VD)) {
2661+
// Drop the 'Self' parameter.
2662+
// FIXME: The real effect here, though, is dropping the generic signature.
2663+
// This should be okay because it should already be checked as part of
2664+
// making an override, but that isn't actually the case as of this writing,
2665+
// and it's kind of suspect anyway.
2666+
derivedInterfaceTy =
2667+
derivedInterfaceTy->castTo<AnyFunctionType>()->getResult();
2668+
overrideInterfaceTy =
2669+
overrideInterfaceTy->castTo<AnyFunctionType>()->getResult();
2670+
} else if (isa<SubscriptDecl>(VD)) {
2671+
// For subscripts, we don't have a 'Self' type, but turn it
2672+
// into a monomorphic function type.
2673+
// FIXME: does this actually make sense, though?
2674+
auto derivedInterfaceFuncTy = derivedInterfaceTy->castTo<AnyFunctionType>();
2675+
derivedInterfaceTy =
2676+
FunctionType::get(derivedInterfaceFuncTy->getParams(),
2677+
derivedInterfaceFuncTy->getResult());
2678+
auto overrideInterfaceFuncTy =
2679+
overrideInterfaceTy->castTo<AnyFunctionType>();
2680+
overrideInterfaceTy =
2681+
FunctionType::get(overrideInterfaceFuncTy->getParams(),
2682+
overrideInterfaceFuncTy->getResult());
2683+
}
2684+
2685+
if (!derivedInterfaceTy->isEqual(overrideInterfaceTy)) {
2686+
TC.diagnose(VD, diag::implementation_only_override_changed_type,
2687+
overrideInterfaceTy);
2688+
TC.diagnose(overridden, diag::overridden_here);
2689+
return;
2690+
}
2691+
2692+
// FIXME: When compiling without library evolution enabled, this should also
2693+
// check whether VD or any of its accessors need a new vtable entry, even if
2694+
// it won't necessarily be able to say why.
2695+
}
2696+
26332697
void TypeChecker::checkParameterAttributes(ParameterList *params) {
26342698
for (auto param: *params) {
26352699
checkDeclAttributes(param);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import Foundation;
2+
3+
@interface NSObject (Secrets)
4+
- (void)secretMethod;
5+
@end

test/PrintAsObjC/Inputs/custom-modules/module.map

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,8 @@ module resilient_objc_class {
4747
header "resilient_objc_class.h"
4848
export *
4949
}
50+
51+
module MiserablePileOfSecrets {
52+
header "MiserablePileOfSecrets.h"
53+
export *
54+
}

test/PrintAsObjC/imports.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
// CHECK-NEXT: @import MostlyPrivate1;
1818
// CHECK-NEXT: @import MostlyPrivate1_Private;
1919
// CHECK-NEXT: @import MostlyPrivate2_Private;
20+
// CHECK-NEXT: @import ObjectiveC;
2021
// CHECK-NEXT: @import ctypes.bits;
2122

2223
// NEGATIVE-NOT: ctypes;
2324
// NEGATIVE-NOT: ImSub;
2425
// NEGATIVE-NOT: ImplicitSub;
2526
// NEGATIVE-NOT: MostlyPrivate2;
27+
// NEGATIVE-NOT: MiserablePileOfSecrets;
28+
29+
// NEGATIVE-NOT: secretMethod
2630

2731
import ctypes.bits
2832
import Foundation
@@ -40,6 +44,8 @@ import MostlyPrivate1_Private
4044
// Deliberately not importing MostlyPrivate2
4145
import MostlyPrivate2_Private
4246

47+
@_implementationOnly import MiserablePileOfSecrets
48+
4349
@objc class Test {
4450
@objc let word: DWORD = 0
4551
@objc let number: TimeInterval = 0.0
@@ -57,3 +63,7 @@ import MostlyPrivate2_Private
5763

5864
@objc let mp2priv: MP2PrivateType = 0
5965
}
66+
67+
@objc public class TestSubclass: NSObject {
68+
@_implementationOnly public override func secretMethod() {}
69+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@interface Base
2+
@end
3+
4+
@interface Parent : Base
5+
- (nonnull instancetype)init __attribute__((objc_designated_initializer));
6+
@end
7+
8+
@interface GenericParent<T: Base *> : Base
9+
- (nonnull instancetype)init __attribute__((objc_designated_initializer));
10+
@end

0 commit comments

Comments
 (0)