Skip to content

Commit 5b90b88

Browse files
committed
Generalize protocol conformance options spelled via attribute and incorporate @unsafe
Protocol conformances have a handful attributes that can apply to them directly, including @unchecked (for Sendable), @preconcurrency, and @retroactive. Generalize this into an option set that we carry around, so it's a bit easier to add them, as well as reworking the serialization logic to deal with an arbitrary number of such options. Use this generality to add support for @unsafe conformances, which are needed when unsafe witnesses are used to conform to safe requirements. Implement general support for @unsafe conformances, including producing a single diagnostic per missing @unsafe that provides a Fix-It and collects together all of the unsafe witnesses as notes.
1 parent b6e995e commit 5b90b88

32 files changed

+468
-228
lines changed

include/swift/AST/ASTContext.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/AST/Evaluator.h"
2323
#include "swift/AST/Identifier.h"
2424
#include "swift/AST/Import.h"
25+
#include "swift/AST/ProtocolConformanceOptions.h"
2526
#include "swift/AST/SILOptions.h"
2627
#include "swift/AST/SearchPathOptions.h"
2728
#include "swift/AST/Type.h"
@@ -1299,8 +1300,7 @@ class ASTContext final {
12991300
SourceLoc loc,
13001301
DeclContext *dc,
13011302
ProtocolConformanceState state,
1302-
bool isUnchecked,
1303-
bool isPreconcurrency,
1303+
ProtocolConformanceOptions options,
13041304
SourceLoc preconcurrencyLoc = SourceLoc());
13051305

13061306
/// Produce a self-conformance for the given protocol.

include/swift/AST/Decl.h

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "swift/AST/Initializer.h"
3535
#include "swift/AST/LayoutConstraint.h"
3636
#include "swift/AST/LifetimeAnnotation.h"
37+
#include "swift/AST/ProtocolConformanceOptions.h"
3738
#include "swift/AST/ReferenceCounting.h"
3839
#include "swift/AST/RequirementSignature.h"
3940
#include "swift/AST/StorageImpl.h"
@@ -1782,14 +1783,8 @@ class ImportDecl final : public Decl,
17821783
/// An entry in the "inherited" list of a type or extension.
17831784
struct InheritedEntry : public TypeLoc {
17841785
private:
1785-
/// Whether there was an @unchecked attribute.
1786-
bool IsUnchecked : 1;
1787-
1788-
/// Whether there was an @retroactive attribute.
1789-
bool IsRetroactive : 1;
1790-
1791-
/// Whether there was an @preconcurrency attribute.
1792-
bool IsPreconcurrency : 1;
1786+
/// Options on a protocol conformance that are expressed as attributes.
1787+
unsigned RawOptions: 8;
17931788

17941789
/// Whether there was a ~ indicating suppression.
17951790
///
@@ -1799,17 +1794,38 @@ struct InheritedEntry : public TypeLoc {
17991794
public:
18001795
InheritedEntry(const TypeLoc &typeLoc);
18011796

1802-
InheritedEntry(const TypeLoc &typeLoc, bool isUnchecked, bool isRetroactive,
1803-
bool isPreconcurrency, bool isSuppressed = false)
1804-
: TypeLoc(typeLoc), IsUnchecked(isUnchecked),
1805-
IsRetroactive(isRetroactive), IsPreconcurrency(isPreconcurrency),
1797+
InheritedEntry(const TypeLoc &typeLoc, ProtocolConformanceOptions options,
1798+
bool isSuppressed = false)
1799+
: TypeLoc(typeLoc), RawOptions(options.toRaw()),
18061800
IsSuppressed(isSuppressed) {}
18071801

1808-
bool isUnchecked() const { return IsUnchecked; }
1809-
bool isRetroactive() const { return IsRetroactive; }
1810-
bool isPreconcurrency() const { return IsPreconcurrency; }
1802+
ProtocolConformanceOptions getOptions() const {
1803+
return ProtocolConformanceOptions(RawOptions);
1804+
}
1805+
1806+
bool isUnchecked() const {
1807+
return getOptions().contains(ProtocolConformanceFlags::Unchecked);
1808+
}
1809+
bool isRetroactive() const {
1810+
return getOptions().contains(ProtocolConformanceFlags::Retroactive);
1811+
}
1812+
bool isPreconcurrency() const {
1813+
return getOptions().contains(ProtocolConformanceFlags::Preconcurrency);
1814+
}
1815+
bool isUnsafe() const {
1816+
return getOptions().contains(ProtocolConformanceFlags::Unsafe);
1817+
}
1818+
1819+
bool isSafe() const {
1820+
return getOptions().contains(ProtocolConformanceFlags::Safe);
1821+
}
1822+
18111823
bool isSuppressed() const { return IsSuppressed; }
18121824

1825+
void setOption(ProtocolConformanceFlags flag) {
1826+
RawOptions = (getOptions() | flag).toRaw();
1827+
}
1828+
18131829
void setSuppressed() {
18141830
assert(!IsSuppressed && "setting suppressed again!?");
18151831
IsSuppressed = true;

include/swift/AST/DiagnosticsSema.def

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5792,22 +5792,19 @@ ERROR(non_sendable_type,none,
57925792
ERROR(sendable_raw_storage,none,
57935793
"struct %0 with @_rawLayout does not conform to the 'Sendable' protocol", (DeclName))
57945794

5795+
ERROR(typeattr_not_inheritance_clause,none,
5796+
"'%0' attribute only applies in inheritance clauses", (StringRef))
5797+
ERROR(typeattr_not_existential,none,
5798+
"'%0' attribute cannot apply to non-protocol type %1", (StringRef, Type))
5799+
57955800
WARNING(unchecked_conformance_not_special,none,
57965801
"@unchecked conformance to %0 has no meaning", (Type))
57975802
WARNING(restate_unchecked_sendable,none,
57985803
"class %0 must restate inherited '@unchecked Sendable' conformance",
57995804
(DeclName))
5800-
ERROR(unchecked_not_inheritance_clause,none,
5801-
"'unchecked' attribute only applies in inheritance clauses", ())
5802-
ERROR(unchecked_not_existential,none,
5803-
"'unchecked' attribute cannot apply to non-protocol type %0", (Type))
58045805

58055806
WARNING(preconcurrency_conformance_not_used,none,
58065807
"@preconcurrency attribute on conformance to %0 has no effect", (Type))
5807-
ERROR(preconcurrency_not_inheritance_clause,none,
5808-
"'preconcurrency' attribute only applies in inheritance clauses", ())
5809-
ERROR(preconcurrency_not_existential,none,
5810-
"'preconcurrency' attribute cannot apply to non-protocol type %0", (Type))
58115808

58125809
ERROR(redundant_any_in_existential,none,
58135810
"redundant 'any' in type %0",
@@ -8094,20 +8091,24 @@ NOTE(note_reference_to_nonisolated_unsafe,none,
80948091
NOTE(note_reference_unowned_unsafe,none,
80958092
"reference to unowned(unsafe) %kind0 is unsafe", (const ValueDecl *))
80968093
NOTE(note_use_of_unchecked_conformance_is_unsafe,none,
8097-
"@unchecked conformance of %0 to %kind1 involves unsafe code",
8094+
"%select{@unchecked|@unsafe}0 conformance of %1 to %kind2 involves unsafe code",
8095+
(bool, Type, const ValueDecl *))
8096+
GROUPED_WARNING(conformance_involves_unsafe,Unsafe,none,
8097+
"conformance of %0 to %kind1 involves unsafe code; use '@unsafe' to "
8098+
"indicate that the conformance is not memory-safe",
80988099
(Type, const ValueDecl *))
8099-
8100-
GROUPED_WARNING(override_safe_withunsafe,Unsafe,none,
8101-
"override of safe %0 with unsafe %0", (DescriptiveDeclKind))
8102-
GROUPED_WARNING(witness_unsafe,Unsafe,none,
8100+
NOTE(note_witness_unsafe,none,
81038101
"unsafe %0 %1 cannot satisfy safe requirement",
81048102
(DescriptiveDeclKind, DeclName))
8105-
GROUPED_WARNING(type_witness_unsafe,Unsafe,none,
8103+
NOTE(note_type_witness_unsafe,none,
81068104
"unsafe type %0 cannot satisfy safe associated type %1",
81078105
(Type, DeclName))
8106+
8107+
GROUPED_WARNING(override_safe_withunsafe,Unsafe,none,
8108+
"override of safe %0 with unsafe %0", (DescriptiveDeclKind))
81088109
GROUPED_WARNING(use_of_unchecked_conformance_is_unsafe,Unsafe,none,
8109-
"@unchecked conformance of %0 to %kind1 involves unsafe code",
8110-
(Type, const ValueDecl *))
8110+
"%select{@unchecked|@unsafe}0 conformance of %1 to %kind2 involves unsafe code",
8111+
(bool, Type, const ValueDecl *))
81118112
GROUPED_WARNING(reference_unowned_unsafe,Unsafe,none,
81128113
"reference to unowned(unsafe) %kind0 is unsafe", (const ValueDecl *))
81138114
GROUPED_WARNING(reference_to_nonisolated_unsafe,Unsafe,none,

include/swift/AST/NameLookup.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,16 +609,24 @@ struct InheritedNominalEntry : Located<NominalTypeDecl *> {
609609
/// The location of the "preconcurrency" attribute if present.
610610
SourceLoc preconcurrencyLoc;
611611

612+
/// The location of the "unsafe" attribute if present.
613+
SourceLoc unsafeLoc;
614+
615+
/// The range of the "safe(unchecked)" attribute if present.
616+
SourceRange safeRange;
617+
612618
/// Whether this inherited entry was suppressed via "~".
613619
bool isSuppressed;
614620

615621
InheritedNominalEntry() { }
616622

617623
InheritedNominalEntry(NominalTypeDecl *item, SourceLoc loc,
618624
SourceLoc uncheckedLoc, SourceLoc preconcurrencyLoc,
625+
SourceLoc unsafeLoc, SourceRange safeRange,
619626
bool isSuppressed)
620627
: Located(item, loc), uncheckedLoc(uncheckedLoc),
621-
preconcurrencyLoc(preconcurrencyLoc), isSuppressed(isSuppressed) {}
628+
preconcurrencyLoc(preconcurrencyLoc), unsafeLoc(unsafeLoc),
629+
safeRange(safeRange), isSuppressed(isSuppressed) {}
622630
};
623631

624632
/// Retrieve the set of nominal type declarations that are directly

include/swift/AST/ProtocolConformance.h

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "swift/AST/ConcreteDeclRef.h"
2020
#include "swift/AST/Decl.h"
21+
#include "swift/AST/ProtocolConformanceOptions.h"
2122
#include "swift/AST/Type.h"
2223
#include "swift/AST/Types.h"
2324
#include "swift/AST/TypeAlignments.h"
@@ -146,20 +147,21 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
146147
SWIFT_INLINE_BITFIELD_EMPTY(RootProtocolConformance, ProtocolConformance);
147148

148149
SWIFT_INLINE_BITFIELD_FULL(NormalProtocolConformance, RootProtocolConformance,
149-
1+1+1+1+bitmax(NumProtocolConformanceStateBits,8)+
150+
1+1+
151+
bitmax(NumProtocolConformanceOptions,8)+
152+
bitmax(NumProtocolConformanceStateBits,8)+
150153
bitmax(NumConformanceEntryKindBits,8),
151154
/// Indicates whether the conformance is invalid.
152155
IsInvalid : 1,
153-
/// The conformance was labeled with @unchecked.
154-
IsUnchecked : 1,
155-
/// The conformance was labeled with @preconcurrency.
156-
IsPreconcurrency : 1,
157156
/// We have allocated the AssociatedConformances array (but not necessarily
158157
/// populated any of its elements).
159158
HasComputedAssociatedConformances : 1,
160159

161160
: NumPadBits,
162161

162+
/// Options.
163+
Options : bitmax(NumProtocolConformanceOptions, 8),
164+
163165
/// The current state of the conformance.
164166
State : bitmax(NumProtocolConformanceStateBits, 8),
165167
/// The reason that this conformance exists.
@@ -567,8 +569,8 @@ class NormalProtocolConformance : public RootProtocolConformance,
567569
public:
568570
NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol,
569571
SourceLoc loc, DeclContext *dc,
570-
ProtocolConformanceState state, bool isUnchecked,
571-
bool isPreconcurrency,
572+
ProtocolConformanceState state,
573+
ProtocolConformanceOptions options,
572574
SourceLoc preconcurrencyLoc)
573575
: RootProtocolConformance(ProtocolConformanceKind::Normal,
574576
conformingType),
@@ -577,12 +579,12 @@ class NormalProtocolConformance : public RootProtocolConformance,
577579
Context(dc) {
578580
assert(!conformingType->hasArchetype() &&
579581
"ProtocolConformances should store interface types");
580-
assert((preconcurrencyLoc.isInvalid() || isPreconcurrency) &&
582+
assert((preconcurrencyLoc.isInvalid() ||
583+
options.contains(ProtocolConformanceFlags::Preconcurrency)) &&
581584
"Cannot have a @preconcurrency location without isPreconcurrency");
582585
setState(state);
583586
Bits.NormalProtocolConformance.IsInvalid = false;
584-
Bits.NormalProtocolConformance.IsUnchecked = isUnchecked;
585-
Bits.NormalProtocolConformance.IsPreconcurrency = isPreconcurrency;
587+
Bits.NormalProtocolConformance.Options = options.toRaw();
586588
Bits.NormalProtocolConformance.HasComputedAssociatedConformances = false;
587589
Bits.NormalProtocolConformance.SourceKind =
588590
unsigned(ConformanceEntryKind::Explicit);
@@ -626,27 +628,42 @@ class NormalProtocolConformance : public RootProtocolConformance,
626628
/// Mark this conformance as invalid.
627629
void setInvalid() { Bits.NormalProtocolConformance.IsInvalid = true; }
628630

631+
ProtocolConformanceOptions getOptions() const {
632+
return ProtocolConformanceOptions(Bits.NormalProtocolConformance.Options);
633+
}
634+
629635
/// Whether this is an "unchecked" conformance.
630636
bool isUnchecked() const {
631-
return Bits.NormalProtocolConformance.IsUnchecked;
637+
return getOptions().contains(ProtocolConformanceFlags::Unchecked);
632638
}
633639

634640
/// Mark the conformance as unchecked (equivalent to the @unchecked
635641
/// conformance attribute).
636642
void setUnchecked() {
637643
// OK to mutate because the flags are not part of the folding set node ID.
638-
Bits.NormalProtocolConformance.IsUnchecked = true;
644+
Bits.NormalProtocolConformance.Options =
645+
(getOptions() | ProtocolConformanceFlags::Unchecked).toRaw();
639646
}
640647

641648
/// Whether this is an preconcurrency conformance.
642649
bool isPreconcurrency() const {
643-
return Bits.NormalProtocolConformance.IsPreconcurrency;
650+
return getOptions().contains(ProtocolConformanceFlags::Preconcurrency);
644651
}
645652

646653
/// Retrieve the location of `@preconcurrency`, if there is one and it is
647654
/// known.
648655
SourceLoc getPreconcurrencyLoc() const { return PreconcurrencyLoc; }
649656

657+
/// Whether this is an "unsafe" conformance.
658+
bool isUnsafe() const {
659+
return getOptions().contains(ProtocolConformanceFlags::Unsafe);
660+
}
661+
662+
/// Whether this is an "safe(unchecked)" conformance.
663+
bool isSafe() const {
664+
return getOptions().contains(ProtocolConformanceFlags::Safe);
665+
}
666+
650667
/// Determine whether we've lazily computed the associated conformance array
651668
/// already.
652669
bool hasComputedAssociatedConformances() const {
@@ -684,8 +701,8 @@ class NormalProtocolConformance : public RootProtocolConformance,
684701
if (auto implying = implyingConformance) {
685702
ImplyingConformance = implying;
686703
PreconcurrencyLoc = implying->getPreconcurrencyLoc();
687-
Bits.NormalProtocolConformance.IsPreconcurrency =
688-
implying->isPreconcurrency();
704+
Bits.NormalProtocolConformance.Options =
705+
implyingConformance->getOptions().toRaw();
689706
}
690707
}
691708

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//===--- ProtocolConformanceOptions.h - Conformance Options -----*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file defines the options for protocol conformances.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
#ifndef SWIFT_AST_PROTOCOLCONFORMANCEOPTIONS_H
17+
#define SWIFT_AST_PROTOCOLCONFORMANCEOPTIONS_H
18+
19+
#include "swift/Basic/OptionSet.h"
20+
21+
namespace swift {
22+
23+
/// Flags that describe extra attributes on protocol conformances.
24+
enum class ProtocolConformanceFlags {
25+
/// @unchecked conformance
26+
Unchecked = 0x01,
27+
28+
/// @preconcurrency conformance
29+
Preconcurrency = 0x02,
30+
31+
/// @unsafe conformance
32+
Unsafe = 0x04,
33+
34+
/// @safe(unchecked) conformance
35+
Safe = 0x08,
36+
37+
/// @retroactive conformance
38+
Retroactive = 0x10,
39+
40+
// Note: whenever you add a bit here, update
41+
// NumProtocolConformanceOptions below.
42+
};
43+
44+
/// Options that describe extra attributes on protocol conformances.
45+
using ProtocolConformanceOptions =
46+
OptionSet<ProtocolConformanceFlags>;
47+
48+
inline ProtocolConformanceOptions operator|(
49+
ProtocolConformanceFlags flag1,
50+
ProtocolConformanceFlags flag2) {
51+
return ProtocolConformanceOptions(flag1) | flag2;
52+
}
53+
54+
enum : unsigned {
55+
NumProtocolConformanceOptions = 5
56+
};
57+
58+
} // end namespace swift
59+
60+
#endif

include/swift/AST/TypeAttr.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ SIMPLE_TYPE_ATTR(Sendable, Sendable)
6060
SIMPLE_TYPE_ATTR(retroactive, Retroactive)
6161
SIMPLE_TYPE_ATTR(unchecked, Unchecked)
6262
SIMPLE_TYPE_ATTR(preconcurrency, Preconcurrency)
63+
SIMPLE_TYPE_ATTR(unsafe, Unsafe)
64+
SIMPLE_TYPE_ATTR(safe, Safe)
6365
SIMPLE_TYPE_ATTR(_local, Local)
6466
SIMPLE_TYPE_ATTR(_noMetadata, NoMetadata)
6567
TYPE_ATTR(_opaqueReturnTypeOf, OpaqueReturnTypeOf)

lib/AST/ASTContext.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2846,8 +2846,7 @@ ASTContext::getNormalConformance(Type conformingType,
28462846
SourceLoc loc,
28472847
DeclContext *dc,
28482848
ProtocolConformanceState state,
2849-
bool isUnchecked,
2850-
bool isPreconcurrency,
2849+
ProtocolConformanceOptions options,
28512850
SourceLoc preconcurrencyLoc) {
28522851
assert(dc->isTypeContext());
28532852

@@ -2864,7 +2863,7 @@ ASTContext::getNormalConformance(Type conformingType,
28642863
// Build a new normal protocol conformance.
28652864
auto result = new (*this, AllocationArena::Permanent)
28662865
NormalProtocolConformance(conformingType, protocol, loc, dc, state,
2867-
isUnchecked, isPreconcurrency, preconcurrencyLoc);
2866+
options, preconcurrencyLoc);
28682867
normalConformances.InsertNode(result, insertPos);
28692868

28702869
return result;

0 commit comments

Comments
 (0)