Skip to content

Commit da2ae43

Browse files
committed
Sema: Diagnose invalid back deployed declarations. Back deployed declarations must:
- have public visibility - have at most one back deployment version per-platform - specify an introduced version for each platform with a back deployment version using @available - have a back deployment version that is greater than the introduced version - not have conflicting attributes like @_alwaysEmitIntoClient Refactor to share code with type checking for @_originallyDefinedIn which has overlapping diagnostics.
1 parent d203876 commit da2ae43

File tree

6 files changed

+240
-75
lines changed

6 files changed

+240
-75
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,26 +1599,12 @@ WARNING(option_set_zero_constant,none,
15991599
NOTE(option_set_empty_set_init,none,
16001600
"use [] to silence this warning", ())
16011601

1602-
// FIXME(backDeploy): Refactor to share with back deployment attr
1603-
ERROR(originally_defined_in_dupe_platform,none,
1604-
"duplicate version number for platform %0", (StringRef))
1605-
16061602
ERROR(originally_definedin_topleve_decl,none,
16071603
"@%0 is only applicable to top-level decl", (StringRef))
16081604

1609-
// FIXME(backDeploy): Refactor to share with back deployment attr
1610-
ERROR(originally_definedin_need_available,none,
1611-
"need @available attribute for @%0", (StringRef))
1612-
16131605
ERROR(originally_definedin_must_not_before_available_version,none,
16141606
"symbols are moved to the current module before they were available in the OSs", (StringRef))
16151607

1616-
// FIXME(backDeploy): Refactor to share with back deployment attr
1617-
WARNING(originally_defined_in_on_non_public,
1618-
none, "@%0 does not have any effect on "
1619-
"%select{private|fileprivate|internal|%error|%error}1 declarations",
1620-
(StringRef, AccessLevel))
1621-
16221608
// Alignment attribute
16231609
ERROR(alignment_not_power_of_two,none,
16241610
"alignment value must be a power of two", ())
@@ -3140,9 +3126,25 @@ ERROR(attr_not_on_variadic_parameters,none,
31403126
ERROR(attr_not_on_subscript_parameters,none,
31413127
"'%0' must not be used on subscript parameters", (StringRef))
31423128

3129+
WARNING(attr_has_no_effect_on_decl_with_access_level,none,
3130+
"'%0' does not have any effect on "
3131+
"%select{private|fileprivate|internal|%error|%error}1 declarations",
3132+
(DeclAttribute, AccessLevel))
3133+
ERROR(attr_not_on_decl_with_invalid_access_level,none,
3134+
"'%0' may not be used on "
3135+
"%select{private|fileprivate|internal|%error|%error}1 declarations",
3136+
(DeclAttribute, AccessLevel))
3137+
3138+
ERROR(attr_has_no_effect_decl_not_available_before,none,
3139+
"'%0' has no effect because %1 is not available before %2 %3",
3140+
(DeclAttribute, DeclName, StringRef, llvm::VersionTuple))
3141+
31433142
ERROR(attr_ambiguous_reference_to_decl,none,
31443143
"ambiguous reference to %0 in '@%1' attribute", (DeclNameRef, StringRef))
31453144

3145+
ERROR(attr_contains_multiple_versions_for_platform,none,
3146+
"'%0' contains multiple versions for %1", (DeclAttribute, StringRef))
3147+
31463148
ERROR(override_final,none,
31473149
"%0 overrides a 'final' %1", (DescriptiveDeclKind, DescriptiveDeclKind))
31483150

@@ -5614,6 +5616,14 @@ ERROR(availability_macro_in_inlinable, none,
56145616
"availability macro cannot be used in inlinable %0",
56155617
(DescriptiveDeclKind))
56165618

5619+
ERROR(attr_requires_decl_availability_for_platform,none,
5620+
"'%0' requires that %1 have explicit availability for %2",
5621+
(DeclAttribute, DeclName, StringRef))
5622+
5623+
ERROR(attr_requires_availability_for_platform,none,
5624+
"'%0' requires explicit availability for %1",
5625+
(DeclAttribute, StringRef))
5626+
56175627
// This doesn't display as an availability diagnostic, but it's
56185628
// implemented there and fires when these subscripts are marked
56195629
// unavailable, so it seems appropriate to put it here.
@@ -6163,6 +6173,10 @@ ERROR(differentiable_programming_attr_used_without_required_module, none,
61636173
"'@%0' attribute used without importing module %1",
61646174
(StringRef, Identifier))
61656175

6176+
//------------------------------------------------------------------------------
6177+
// MARK: OSLog
6178+
//------------------------------------------------------------------------------
6179+
61666180
ERROR(oslog_arg_must_be_bool_literal, none,
61676181
"argument must be a bool literal", ())
61686182
ERROR(oslog_arg_must_be_integer_literal, none,
@@ -6270,5 +6284,12 @@ ERROR(cannot_convert_default_value_type_to_argument_type, none,
62706284
"cannot convert default value of type %0 to expected argument type %1 for parameter #%2",
62716285
(Type, Type, unsigned))
62726286

6287+
//------------------------------------------------------------------------------
6288+
// MARK: Back deployment
6289+
//------------------------------------------------------------------------------
6290+
6291+
ERROR(attr_incompatible_with_back_deploy,none,
6292+
"'%0' cannot be applied to back deployed declarations", (DeclAttribute))
6293+
62736294
#define UNDEFINE_DIAGNOSTIC_MACROS
62746295
#include "DefineDiagnosticMacros.h"

lib/Sema/TypeCheckAttr.cpp

Lines changed: 116 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,45 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
6666
std::forward<ArgTypes>(Args)...);
6767
}
6868

69+
/// Emits a diagnostic with a fixit to remove the attribute if the attribute
70+
/// is applied to a non-public declaration. Returns true if a diagnostic was
71+
/// emitted.
72+
bool diagnoseAndRemoveAttrIfDeclIsNonPublic(DeclAttribute *attr,
73+
bool isError) {
74+
if (auto *VD = dyn_cast<ValueDecl>(D)) {
75+
auto access =
76+
VD->getFormalAccessScope(/*useDC=*/nullptr,
77+
/*treatUsableFromInlineAsPublic=*/true);
78+
if (!access.isPublic()) {
79+
diagnoseAndRemoveAttr(
80+
attr,
81+
isError ? diag::attr_not_on_decl_with_invalid_access_level
82+
: diag::attr_has_no_effect_on_decl_with_access_level,
83+
attr, VD->getFormalAccess());
84+
return true;
85+
}
86+
}
87+
return false;
88+
}
89+
90+
/// Emits a diagnostic if there is no availability specified for the given
91+
/// platform, as required by the given attribute. Returns true if a diagnostic
92+
/// was emitted.
93+
bool diagnoseMissingAvailability(DeclAttribute *attr, PlatformKind platform) {
94+
auto IntroVer = D->getIntroducedOSVersion(platform);
95+
if (IntroVer.hasValue())
96+
return false;
97+
98+
if (auto *VD = dyn_cast<ValueDecl>(D)) {
99+
diagnose(attr->AtLoc, diag::attr_requires_decl_availability_for_platform,
100+
attr, VD->getName(), prettyPlatformString(platform));
101+
} else {
102+
diagnose(attr->AtLoc, diag::attr_requires_availability_for_platform, attr,
103+
prettyPlatformString(platform));
104+
}
105+
return true;
106+
}
107+
69108
template <typename... ArgTypes>
70109
InFlightDiagnostic diagnose(ArgTypes &&... Args) const {
71110
return Ctx.Diags.diagnose(std::forward<ArgTypes>(Args)...);
@@ -256,7 +295,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
256295

257296
void visitImplementationOnlyAttr(ImplementationOnlyAttr *attr);
258297
void visitNonEphemeralAttr(NonEphemeralAttr *attr);
259-
void checkOriginalDefinedInAttrs(Decl *D, ArrayRef<OriginallyDefinedInAttr*> Attrs);
298+
void checkOriginalDefinedInAttrs(ArrayRef<OriginallyDefinedInAttr *> Attrs);
260299

261300
void visitDifferentiableAttr(DifferentiableAttr *attr);
262301
void visitDerivativeAttr(DerivativeAttr *attr);
@@ -279,7 +318,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
279318

280319
void visitPrimaryAssociatedTypeAttr(PrimaryAssociatedTypeAttr *attr);
281320

282-
void checkBackDeployAttrs(Decl *D, ArrayRef<BackDeployAttr *> Attrs);
321+
void checkBackDeployAttrs(ArrayRef<BackDeployAttr *> Attrs);
283322
};
284323

285324
} // end anonymous namespace
@@ -1268,8 +1307,9 @@ void TypeChecker::checkDeclAttributes(Decl *D) {
12681307
TypeChecker::applyAccessNote(VD);
12691308

12701309
AttributeChecker Checker(D);
1271-
// We need to check all OriginallyDefinedInAttr relative to each other, so
1272-
// collect them and check in batch later.
1310+
// We need to check all OriginallyDefinedInAttr and BackDeployAttr relative
1311+
// to each other, so collect them and check in batch later.
1312+
llvm::SmallVector<BackDeployAttr *, 4> backDeployAttrs;
12731313
llvm::SmallVector<OriginallyDefinedInAttr*, 4> ODIAttrs;
12741314
for (auto attr : D->getAttrs()) {
12751315
if (!attr->isValid()) continue;
@@ -1279,6 +1319,8 @@ void TypeChecker::checkDeclAttributes(Decl *D) {
12791319
if (attr->canAppearOnDecl(D)) {
12801320
if (auto *ODI = dyn_cast<OriginallyDefinedInAttr>(attr)) {
12811321
ODIAttrs.push_back(ODI);
1322+
} else if (auto *BD = dyn_cast<BackDeployAttr>(attr)) {
1323+
backDeployAttrs.push_back(BD);
12821324
} else {
12831325
// Otherwise, check it.
12841326
Checker.visit(attr);
@@ -1319,7 +1361,8 @@ void TypeChecker::checkDeclAttributes(Decl *D) {
13191361
else
13201362
Checker.diagnoseAndRemoveAttr(attr, diag::invalid_decl_attribute, attr);
13211363
}
1322-
Checker.checkOriginalDefinedInAttrs(D, ODIAttrs);
1364+
Checker.checkBackDeployAttrs(backDeployAttrs);
1365+
Checker.checkOriginalDefinedInAttrs(ODIAttrs);
13231366
}
13241367

13251368
/// Returns true if the given method is an valid implementation of a
@@ -3409,8 +3452,8 @@ void AttributeChecker::visitNonEphemeralAttr(NonEphemeralAttr *attr) {
34093452
attr->setInvalid();
34103453
}
34113454

3412-
void AttributeChecker::checkOriginalDefinedInAttrs(Decl *D,
3413-
ArrayRef<OriginallyDefinedInAttr*> Attrs) {
3455+
void AttributeChecker::checkOriginalDefinedInAttrs(
3456+
ArrayRef<OriginallyDefinedInAttr *> Attrs) {
34143457
if (Attrs.empty())
34153458
return;
34163459
auto &Ctx = D->getASTContext();
@@ -3421,20 +3464,9 @@ void AttributeChecker::checkOriginalDefinedInAttrs(Decl *D,
34213464
for (auto *Attr: Attrs) {
34223465
static StringRef AttrName = "_originallyDefinedIn";
34233466

3424-
if (auto *VD = dyn_cast<ValueDecl>(D)) {
3425-
// This attribute does not make sense on private declarations since
3426-
// clients can't use them.
3427-
auto access =
3428-
VD->getFormalAccessScope(/*useDC=*/nullptr,
3429-
/*treatUsableFromInlineAsPublic=*/true);
3430-
if (!access.isPublic()) {
3431-
diagnoseAndRemoveAttr(Attr,
3432-
diag::originally_defined_in_on_non_public,
3433-
AttrName, VD->getFormalAccess());
3434-
continue;
3435-
}
3436-
}
3437-
3467+
if (diagnoseAndRemoveAttrIfDeclIsNonPublic(Attr, /*isError=*/false))
3468+
continue;
3469+
34383470
if (!Attr->isActivePlatform(Ctx))
34393471
continue;
34403472
auto AtLoc = Attr->AtLoc;
@@ -3443,20 +3475,19 @@ void AttributeChecker::checkOriginalDefinedInAttrs(Decl *D,
34433475
// We've seen the platform before, emit error to the previous one which
34443476
// comes later in the source order.
34453477
diagnose(seenPlatforms[Platform],
3446-
diag::originally_defined_in_dupe_platform,
3478+
diag::attr_contains_multiple_versions_for_platform, Attr,
34473479
platformString(Platform));
34483480
return;
34493481
}
34503482
if (!D->getDeclContext()->isModuleScopeContext()) {
34513483
diagnose(AtLoc, diag::originally_definedin_topleve_decl, AttrName);
34523484
return;
34533485
}
3454-
auto IntroVer = D->getIntroducedOSVersion(Platform);
3455-
if (!IntroVer.hasValue()) {
3456-
diagnose(AtLoc, diag::originally_definedin_need_available,
3457-
AttrName);
3486+
3487+
if (diagnoseMissingAvailability(Attr, Platform))
34583488
return;
3459-
}
3489+
3490+
auto IntroVer = D->getIntroducedOSVersion(Platform);
34603491
if (IntroVer.getValue() > Attr->MovedVersion) {
34613492
diagnose(AtLoc,
34623493
diag::originally_definedin_must_not_before_available_version,
@@ -3466,9 +3497,64 @@ void AttributeChecker::checkOriginalDefinedInAttrs(Decl *D,
34663497
}
34673498
}
34683499

3469-
void AttributeChecker::checkBackDeployAttrs(Decl *D,
3470-
ArrayRef<BackDeployAttr *> Attrs) {
3471-
// FIXME(backDeploy): Diagnose incompatible uses of `@_backDeploy
3500+
void AttributeChecker::checkBackDeployAttrs(ArrayRef<BackDeployAttr *> Attrs) {
3501+
if (Attrs.empty())
3502+
return;
3503+
3504+
// Diagnose conflicting attributes. @_alwaysEmitIntoClient, @inlinable, and
3505+
// @_transparent all conflict with back deployment because they each cause the
3506+
// body of a function to be copied into the client under certain conditions
3507+
// and would defeat the goal of back deployment, which is to always use the
3508+
// ABI version of the declaration when it is available.
3509+
if (auto *AEICA = D->getAttrs().getAttribute<AlwaysEmitIntoClientAttr>()) {
3510+
diagnoseAndRemoveAttr(AEICA, diag::attr_incompatible_with_back_deploy,
3511+
AEICA);
3512+
}
3513+
3514+
if (auto *IA = D->getAttrs().getAttribute<InlinableAttr>()) {
3515+
diagnoseAndRemoveAttr(IA, diag::attr_incompatible_with_back_deploy, IA);
3516+
}
3517+
3518+
if (auto *TA = D->getAttrs().getAttribute<TransparentAttr>()) {
3519+
diagnoseAndRemoveAttr(TA, diag::attr_incompatible_with_back_deploy, TA);
3520+
}
3521+
3522+
std::map<PlatformKind, SourceLoc> seenPlatforms;
3523+
3524+
for (auto *Attr : Attrs) {
3525+
// Back deployment only makes sense for public declarations.
3526+
if (diagnoseAndRemoveAttrIfDeclIsNonPublic(Attr, /*isError=*/true))
3527+
continue;
3528+
3529+
auto AtLoc = Attr->AtLoc;
3530+
auto Platform = Attr->Platform;
3531+
3532+
if (!seenPlatforms.insert({Platform, AtLoc}).second) {
3533+
// We've seen the platform before, emit error to the previous one which
3534+
// comes later in the source order.
3535+
diagnose(seenPlatforms[Platform],
3536+
diag::attr_contains_multiple_versions_for_platform, Attr,
3537+
platformString(Platform));
3538+
continue;
3539+
}
3540+
3541+
// Require explicit availability for back deployed decls.
3542+
if (diagnoseMissingAvailability(Attr, Platform))
3543+
continue;
3544+
3545+
// Verify that the decl is available before the back deployment boundary.
3546+
// If it's not, the attribute doesn't make sense since the back deployment
3547+
// fallback could never be executed at runtime.
3548+
auto IntroVer = D->getIntroducedOSVersion(Platform);
3549+
if (Attr->Version <= IntroVer.getValue()) {
3550+
// Only functions, methods, computed properties, and subscripts are
3551+
// back-deployable.
3552+
auto *VD = cast<ValueDecl>(D);
3553+
diagnose(AtLoc, diag::attr_has_no_effect_decl_not_available_before, Attr,
3554+
VD->getName(), prettyPlatformString(Platform), Attr->Version);
3555+
continue;
3556+
}
3557+
}
34723558
}
34733559

34743560
Type TypeChecker::checkReferenceOwnershipAttr(VarDecl *var, Type type,

test/ModuleInterface/back-deploy-attr.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public struct TopLevelStruct {
2525
// CHECK: @_backDeploy(iOS 15.0)
2626
// FROMSOURCE: public func backDeployedFunc2_MultiPlatform() -> Swift.Int { return 43 }
2727
// FROMMODULE: public func backDeployedFunc2_MultiPlatform() -> Swift.Int
28-
@available(macOS 11.0, *)
28+
@available(macOS 11.0, iOS 14.0, *)
2929
@_backDeploy(macOS 12.0, iOS 15.0)
3030
public func backDeployedFunc2_MultiPlatform() -> Int { return 43 }
3131
}
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// RUN: %target-typecheck-verify-swift
22
// REQUIRES: OS=macosx
33

4-
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-error {{need @available attribute for @_originallyDefinedIn}}
4+
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-error {{'@_originallyDefinedIn' requires that 'foo()' have explicit availability for macOS}}
55
public func foo() {}
66

77
@_originallyDefinedIn(modulename: "foo", OSX 13.13) // expected-error {{expected 'module: "original"' in the first argument to @_originallyDefinedIn}}
88
public func foo1() {}
99

10-
@_originallyDefinedIn(module: "foo", OSX 13.13.3) // expected-warning {{@_originallyDefinedIn only uses major and minor version number}} expected-error {{need @available attribute for @_originallyDefinedIn}}
10+
@_originallyDefinedIn(module: "foo", OSX 13.13.3) // expected-warning {{@_originallyDefinedIn only uses major and minor version number}} expected-error {{'@_originallyDefinedIn' requires that 'ToplevelClass' have explicit availability for macOS}}
1111
public class ToplevelClass {}
1212

1313
@_originallyDefinedIn(module: "foo") // expected-error {{expected at least one platform version in @_originallyDefinedIn}}
@@ -22,7 +22,7 @@ public class ToplevelClass3 {}
2222
@available(OSX 13.10, *)
2323
@_originallyDefinedIn(module: "foo", * 13.13) // expected-warning {{* as platform name has no effect}} expected-error {{expected at least one platform version in @_originallyDefinedIn}}
2424
@_originallyDefinedIn(module: "foo", OSX 13.13, iOS 7.0)
25-
@_originallyDefinedIn(module: "foo", OSX 13.14, * 7.0) // expected-warning {{* as platform name has no effect}} expected-error {{duplicate version number for platform macOS}}
25+
@_originallyDefinedIn(module: "foo", OSX 13.14, * 7.0) // expected-warning {{* as platform name has no effect}} expected-error {{'@_originallyDefinedIn' contains multiple versions for macOS}}
2626
public class ToplevelClass4 {
2727
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-error {{'@_originallyDefinedIn' attribute cannot be applied to this declaration}}
2828
subscript(index: Int) -> Int {
@@ -32,16 +32,16 @@ public class ToplevelClass4 {
3232
}
3333

3434
@available(OSX 13.10, *)
35-
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-warning {{@_originallyDefinedIn does not have any effect on internal declarations}}
36-
@_originallyDefinedIn(module: "foo", iOS 7.0) // expected-warning {{@_originallyDefinedIn does not have any effect on internal declarations}}
35+
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-warning {{'@_originallyDefinedIn' does not have any effect on internal declarations}}
36+
@_originallyDefinedIn(module: "foo", iOS 7.0) // expected-warning {{'@_originallyDefinedIn' does not have any effect on internal declarations}}
3737
internal class ToplevelClass5 {}
3838

3939
@available(OSX 13.10, *)
40-
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-warning {{@_originallyDefinedIn does not have any effect on private declarations}}
41-
@_originallyDefinedIn(module: "foo", iOS 7.0) // expected-warning {{@_originallyDefinedIn does not have any effect on private declarations}}
40+
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-warning {{'@_originallyDefinedIn' does not have any effect on private declarations}}
41+
@_originallyDefinedIn(module: "foo", iOS 7.0) // expected-warning {{'@_originallyDefinedIn' does not have any effect on private declarations}}
4242
private class ToplevelClass6 {}
4343

4444
@available(OSX 13.10, *)
45-
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-warning {{@_originallyDefinedIn does not have any effect on fileprivate declarations}}
46-
@_originallyDefinedIn(module: "foo", iOS 7.0) // expected-warning {{@_originallyDefinedIn does not have any effect on fileprivate declarations}}
45+
@_originallyDefinedIn(module: "foo", OSX 13.13) // expected-warning {{'@_originallyDefinedIn' does not have any effect on fileprivate declarations}}
46+
@_originallyDefinedIn(module: "foo", iOS 7.0) // expected-warning {{'@_originallyDefinedIn' does not have any effect on fileprivate declarations}}
4747
fileprivate class ToplevelClass7 {}

test/Sema/diag_originally_definedin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// RUN: -define-availability "_myProject 1.0:macOS 10.10"
33
// REQUIRES: OS=macosx
44

5-
@_originallyDefinedIn(module: "original", OSX 10.13) // expected-error {{need @available attribute for @_originallyDefinedIn}}
5+
@_originallyDefinedIn(module: "original", OSX 10.13) // expected-error {{'@_originallyDefinedIn' requires that 'foo()' have explicit availability for macOS}}
66
public func foo() {}
77

88
@available(macOS 10.13, *)

0 commit comments

Comments
 (0)