Skip to content

Add "marker" protocols, indicated by @_marker. #35732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/swift/AST/ASTMangler.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ class ASTMangler : public Mangler {
std::function<bool (SymbolicReferent)> CanSymbolicReference;

bool canSymbolicReference(SymbolicReferent referent) {
// Marker protocols cannot ever be symbolically referenced.
if (auto nominal = referent.dyn_cast<const NominalTypeDecl *>()) {
if (auto proto = dyn_cast<ProtocolDecl>(nominal)) {
if (proto->isMarkerProtocol())
return false;
}
}

return AllowSymbolicReferences
&& (!CanSymbolicReference || CanSymbolicReference(referent));
}
Expand Down
6 changes: 6 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,12 @@ SIMPLE_DECL_ATTR(concurrent, Concurrent,
APIBreakingToAdd | APIBreakingToRemove,
107)

SIMPLE_DECL_ATTR(_marker, Marker,
OnProtocol | UserInaccessible |
ABIBreakingToAdd | ABIBreakingToRemove |
APIBreakingToAdd | APIBreakingToRemove,
108)

#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS
#undef CONTEXTUAL_DECL_ATTR_ALIAS
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4162,6 +4162,10 @@ class ProtocolDecl final : public NominalTypeDecl {
ProtocolRethrowsRequirementList getRethrowingRequirements() const;
bool isRethrowingProtocol() const;

/// Determine whether this is a "marker" protocol, meaning that is indicates
/// semantics but has no corresponding witness table.
bool isMarkerProtocol() const;

private:
void computeKnownProtocolKind() const;

Expand Down
11 changes: 11 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5581,6 +5581,17 @@ NOTE(result_builder_missing_build_limited_availability, none,
"expression shuffles the elements of this tuple; "
"this behavior is deprecated", ())

//------------------------------------------------------------------------------
// MARK: marker protocol diagnostics
//------------------------------------------------------------------------------
ERROR(marker_protocol_requirement, none,
"marker protocol %0 cannot have any requirements", (DeclName))
ERROR(marker_protocol_inherit_nonmarker, none,
"marker protocol %0 cannot inherit non-marker protocol %1",
(DeclName, DeclName))
ERROR(marker_protocol_value,none,
"marker protocol %0 can only be used in generic constraints", (DeclName))

//------------------------------------------------------------------------------
// MARK: differentiable programming diagnostics
//------------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions include/swift/SIL/TypeLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,9 @@ class TypeConverter {

/// True if a protocol uses witness tables for dynamic dispatch.
static bool protocolRequiresWitnessTable(ProtocolDecl *P) {
if (P->isMarkerProtocol())
return false;

return swift::protocolRequiresWitnessTable(getProtocolDispatchStrategy(P));
}

Expand Down
4 changes: 4 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4601,6 +4601,10 @@ ProtocolDecl::ProtocolDecl(DeclContext *DC, SourceLoc ProtocolLoc,
setTrailingWhereClause(TrailingWhere);
}

bool ProtocolDecl::isMarkerProtocol() const {
return getAttrs().hasAttribute<MarkerAttr>();
}

ArrayRef<ProtocolDecl *> ProtocolDecl::getInheritedProtocols() const {
auto *mutThis = const_cast<ProtocolDecl *>(this);
return evaluateOrDefault(getASTContext().evaluator,
Expand Down
9 changes: 9 additions & 0 deletions lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5150,6 +5150,10 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
void IRGenModule::emitProtocolDecl(ProtocolDecl *protocol) {
PrettyStackTraceDecl stackTraceRAII("emitting metadata for", protocol);

// Marker protocols are never emitted.
if (protocol->isMarkerProtocol())
return;

// Emit remote reflection metadata for the protocol.
emitFieldDescriptor(protocol);

Expand Down Expand Up @@ -5239,6 +5243,11 @@ GenericRequirementsMetadata irgen::addGenericRequirements(
case RequirementKind::Conformance: {
auto protocol = requirement.getSecondType()->castTo<ProtocolType>()
->getDecl();

// Marker protocols do not record generic requirements at all.
if (protocol->isMarkerProtocol())
break;

bool needsWitnessTable =
Lowering::TypeConverter::protocolRequiresWitnessTable(protocol);
auto flags = GenericRequirementFlags(GenericRequirementKind::Protocol,
Expand Down
29 changes: 29 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,35 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
}
}
}

void visitMarkerAttr(MarkerAttr *attr) {
auto proto = dyn_cast<ProtocolDecl>(D);
if (!proto)
return;

// A marker protocol cannot inherit a non-marker protocol.
for (auto inheritedProto : proto->getInheritedProtocols()) {
if (!inheritedProto->isMarkerProtocol()) {
proto->diagnose(
diag::marker_protocol_inherit_nonmarker,
proto->getName(), inheritedProto->getName());
inheritedProto->diagnose(
diag::decl_declared_here, inheritedProto->getName());
}
}

// A marker protocol cannot have any requirements.
for (auto member : proto->getAllMembers()) {
auto value = dyn_cast<ValueDecl>(member);
if (!value)
continue;

if (value->isProtocolRequirement()) {
value->diagnose(diag::marker_protocol_requirement, proto->getName());
break;
}
}
}
};
} // end anonymous namespace

Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,7 @@ namespace {
UNINTERESTING_ATTR(Concurrent)

UNINTERESTING_ATTR(AtRethrows)
UNINTERESTING_ATTR(Marker)
#undef UNINTERESTING_ATTR

void visitAvailableAttr(AvailableAttr *attr) {
Expand Down
14 changes: 14 additions & 0 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3918,6 +3918,12 @@ class UnsupportedProtocolVisitor
proto->getName());
T->setInvalid();
}
if (proto->isMarkerProtocol()) {
Ctx.Diags.diagnose(comp->getNameLoc(),
diag::marker_protocol_value,
proto->getName());
T->setInvalid();
}
} else if (auto *alias = dyn_cast_or_null<TypeAliasDecl>(comp->getBoundDecl())) {
auto type = Type(alias->getDeclaredInterfaceType()->getDesugaredType());
type.findIf([&](Type type) -> bool {
Expand All @@ -3928,6 +3934,14 @@ class UnsupportedProtocolVisitor
for (auto *proto : layout.getProtocols()) {
auto *protoDecl = proto->getDecl();

if (protoDecl->isMarkerProtocol()) {
Ctx.Diags.diagnose(comp->getNameLoc(),
diag::marker_protocol_value,
protoDecl->getName());
T->setInvalid();
continue;
}

if (protoDecl->existentialTypeSupported())
continue;

Expand Down
2 changes: 1 addition & 1 deletion lib/TBDGen/TBDGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,7 @@ static bool isValidProtocolMemberForTBDGen(const Decl *D) {
#endif

void TBDGenVisitor::visitProtocolDecl(ProtocolDecl *PD) {
if (!PD->isObjC()) {
if (!PD->isObjC() && !PD->isMarkerProtocol()) {
addSymbol(LinkEntity::forProtocolDescriptor(PD));

struct WitnessVisitor : public SILWitnessVisitor<WitnessVisitor> {
Expand Down
23 changes: 23 additions & 0 deletions test/IRGen/marker_protocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %target-swift-frontend -primary-file %s -emit-ir -o - | %FileCheck %s

// Marker protocols should have no ABI impact at all, so this source file checks
// for the absence of symbols related to marker protocols.

// CHECK-NOT: $s15marker_protocol1PP
// CHECK-NOT: $s15marker_protocol1PMp

@_marker public protocol P { }

extension Int: P { }
extension Array: P where Element: P { }

// Note: no witness tables
// CHECK: swiftcc void @"$s15marker_protocol7genericyyxAA1PRzlF"(%swift.opaque* noalias nocapture %0, %swift.type* %T)
public func generic<T: P>(_: T) { }

public func testGeneric(i: Int, array: [Int]) {
generic(i)
generic(array)
}

public protocol Q: P { }
51 changes: 51 additions & 0 deletions test/attr/attr_marker_protocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// RUN: %target-typecheck-verify-swift

// Marker protocol definition
@_marker protocol P1 {
func f() // expected-error{{marker protocol 'P1' cannot have any requirements}}
}

@_marker protocol P2 {
associatedtype AT // expected-error{{marker protocol 'P2' cannot have any requirements}}
}

@_marker protocol P3 {
typealias A = Int
}

protocol P4 { } // expected-note{{'P4' declared here}}

@_marker protocol P5: P4 { } // expected-error{{marker protocol 'P5' cannot inherit non-marker protocol 'P4'}}

// Legitimate uses of marker protocols.
extension P3 {
func f() { }
}

extension Int: P3 { }
extension Array: P3 where Element: P3 { } // expected-note{{requirement from conditional conformance of '[Double]' to 'P3'}}

protocol P6: P3 { } // okay
@_marker protocol P7: P3 { } // okay

func genericOk<T: P3>(_: T) { }

func testGenericOk(i: Int, arr: [Int], nope: [Double]) {
genericOk(i)
genericOk(arr)
genericOk(nope) // expected-error{{global function 'genericOk' requires that 'Double' conform to 'P3'}}
}

// Incorrect uses of marker protocols in types.
func testNotOkay(a: Any) {
var mp1: P3 = 17 // expected-error{{marker protocol 'P3' can only be used in generic constraints}}
_ = mp1
mp1 = 17

if let mp2 = a as? P3 { _ = mp2 } // expected-error{{marker protocol 'P3' can only be used in generic constraints}}
if let mp3 = a as? AnyObject & P3 { _ = mp3 } // expected-error{{marker protocol 'P3' can only be used in generic constraints}}
if a is AnyObject & P3 { } // expected-error{{marker protocol 'P3' can only be used in generic constraints}}

func inner(p3: P3) { } // expected-error{{marker protocol 'P3' can only be used in generic constraints}}
}