Skip to content

Add '@_requiresConcreteImplementation' and use it #30417

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

Closed
wants to merge 4 commits into from
Closed
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
6 changes: 6 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,12 @@ DECL_ATTR(transpose, Transpose,
ABIStableToAdd | ABIBreakingToRemove | APIStableToAdd | APIBreakingToRemove,
99)

SIMPLE_DECL_ATTR(_requiresConcreteImplementation, RequiresConcreteImplementation,
OnFunc | OnAccessor | OnVar | OnSubscript | OnConstructor |
LongAttribute | UserInaccessible | NotSerialized |
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APIBreakingToAdd?

100)

#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS
#undef CONTEXTUAL_DECL_ATTR_ALIAS
Expand Down
17 changes: 15 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2116,8 +2116,8 @@ NOTE(declared_protocol_conformance_here,none,
(Type, unsigned, DeclName, DeclName))

ERROR(witness_unavailable,none,
"unavailable %0 %1 was used to satisfy a requirement of protocol %2",
(DescriptiveDeclKind, DeclName, DeclName))
"unavailable %0 %1 was used to satisfy a requirement of protocol %2",
(DescriptiveDeclKind, DeclName, DeclName))

ERROR(redundant_conformance,none,
"redundant conformance of %0 to protocol %1", (Type, DeclName))
Expand Down Expand Up @@ -2900,6 +2900,19 @@ ERROR(implements_attr_non_protocol_type,none,
ERROR(implements_attr_protocol_not_conformed_to,none,
"containing type %0 does not conform to protocol %1",
(DeclName, DeclName))

// @_requiresConcreteImplementation
ERROR(requires_concrete_implementation_attr_wrong_decl_context,none,
"'@_requiresConcreteImplementation' can only be specified on protocol "
"requirements", ())
ERROR(witness_must_be_concrete_implementation,none,
"protocol requirement marked '@_requiresConcreteImplementation' cannot "
"be satisfied by %0 %1 declared in protocol extension",
(DescriptiveDeclKind, DeclName))
ERROR(override_not_requiring_concrete_implementation,none,
"override of protocol requirement marked "
"'@_requiresConcreteImplementation' should also be marked "
"'@_requiresConcreteImplementation'", ())

// @differentiable
ERROR(differentiable_attr_no_vjp_or_jvp_when_linear,none,
Expand Down
10 changes: 10 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {

void visitDifferentiableAttr(DifferentiableAttr *attr);
void visitDerivativeAttr(DerivativeAttr *attr);

void visitRequiresConcreteImplementationAttr(
RequiresConcreteImplementationAttr *attr);
};
} // end anonymous namespace

Expand Down Expand Up @@ -4551,3 +4554,10 @@ void AttributeChecker::visitDerivativeAttr(DerivativeAttr *attr) {
if (typeCheckDerivativeAttr(Ctx, D, attr))
attr->setInvalid();
}

void AttributeChecker::visitRequiresConcreteImplementationAttr(
RequiresConcreteImplementationAttr *attr) {
if (!isa<ProtocolDecl>(D->getDeclContext()))
diagnoseAndRemoveAttr(attr,
diag::requires_concrete_implementation_attr_wrong_decl_context);
}
15 changes: 15 additions & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,21 @@ namespace {
.fixItInsert(Base->getAttributeInsertionLoc(false),
"@objc ");
}

void visitRequiresConcreteImplementationAttr(
RequiresConcreteImplementationAttr *attr) {
// Require '@_requiresConcreteImplementation' on the override if it was
// there on the base.
if (!Base->getAttrs().hasAttribute<RequiresConcreteImplementationAttr>())
return;

if (Override->getAttrs().hasAttribute<RequiresConcreteImplementationAttr>())
return;

Diags.diagnose(Override,
diag::override_not_requiring_concrete_implementation);
Diags.diagnose(Base, diag::overridden_here);
}
};
} // end anonymous namespace

Expand Down
26 changes: 24 additions & 2 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,13 @@ RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
return CheckKind::WitnessUnavailable;
}

// If a requirement has the @_requiresConcreteImplementation attribute, it
// cannot be satisfied by a default implementation in a protocol extension.
if (requirement->getAttrs().hasAttribute<RequiresConcreteImplementationAttr>()
&& match.Witness->getDeclContext()->getExtendedProtocolDecl()) {
return CheckKind::RequiresConcreteImplementation;
}

return CheckKind::Success;
}

Expand Down Expand Up @@ -3433,8 +3440,7 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {

case CheckKind::WitnessUnavailable:
diagnoseOrDefer(requirement, /*isError=*/true,
[witness, requirement](
NormalProtocolConformance *conformance) {
[witness, requirement](NormalProtocolConformance *conformance) {
auto &diags = witness->getASTContext().Diags;
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
diags.diagnose(diagLoc,
Expand All @@ -3448,6 +3454,22 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
requirement->getFullName());
});
break;

case CheckKind::RequiresConcreteImplementation:
diagnoseOrDefer(requirement, /*isError=*/true,
[witness, requirement](NormalProtocolConformance *conformance) {
auto &diags = witness->getASTContext().Diags;
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
diags.diagnose(diagLoc,
diag::witness_must_be_concrete_implementation,
witness->getDescriptiveKind(),
witness->getFullName());
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
diags.diagnose(requirement, diag::kind_declname_declared_here,
DescriptiveDeclKind::Requirement,
requirement->getFullName());
});
break;
}

if (auto *classDecl = Adoptee->getClassOrBoundGenericClass()) {
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/TypeCheckProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ enum class CheckKind : unsigned {

/// The witness itself is inaccessible.
WitnessUnavailable,

/// The requirement was marked '@_requiresConcreteImplementation', but the
/// witness is a default implementation in a protocol extension.
RequiresConcreteImplementation,
};

/// Describes an optional adjustment made to a witness.
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/DeclTypeRecordNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ DECL(PRECEDENCE_GROUP)
DECL(ACCESSOR)

#ifndef DECL_ATTR
#define DECL_ATTR(NAME, CLASS, OPTIONS, CODE) RECORD_VAL(CLASS##_DECL_ATTR, 100+CODE)
#define DECL_ATTR(NAME, CLASS, OPTIONS, CODE) RECORD_VAL(CLASS##_DECL_ATTR, 90+CODE)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not proud of this, but there are 99 existing decl attrs, so with one more we would otherwise crash into FIRST_PATTERN(PAREN, 200).

#endif
#include "swift/AST/Attr.def"

Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 546; // Avoid conflict with downstream bump
const uint16_t SWIFTMODULE_VERSION_MINOR = 547; // Reorder DeclTypeRecordNodes.def

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down
1 change: 1 addition & 0 deletions stdlib/public/core/Random.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public protocol RandomNumberGenerator {
/// method.
///
/// - Returns: An unsigned 64-bit random value.
@_requiresConcreteImplementation
mutating func next() -> UInt64
}

Expand Down
48 changes: 48 additions & 0 deletions test/attr/attr_requires_concrete_implementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// RUN: %target-typecheck-verify-swift

protocol P {
@_requiresConcreteImplementation
func f(_: Int) -> Bool // expected-note {{requirement 'f' declared here}}
// expected-note@-1 {{overridden declaration is here}}
func f<T: SignedInteger>(_: T) -> Bool

func g(_: Int) -> Bool
}

extension P {
func f<T: SignedInteger>(_ x: T) -> Bool { // expected-note {{'f' declared here}}
return f(Int(x))
}

func g(_ x: Int) -> Bool { // expected-note {{'g' declared here}}
return f(x)
}
}

protocol Q: P {
override func f(_: Int) -> Bool // expected-error {{override of protocol requirement marked '@_requiresConcreteImplementation' should also be marked }}
}

protocol R: P {
@_requiresConcreteImplementation
override func g(_: Int) -> Bool // expected-note {{requirement 'g' declared here}}
}

struct S: P { // expected-error {{type 'S' does not conform to protocol 'P'}}
// expected-error@-1 {{protocol requirement marked '@_requiresConcreteImplementation' cannot be satisfied }}
}

struct T: P, R { // expected-error {{type 'T' does not conform to protocol 'R'}}
// expected-error@-1 {{protocol requirement marked '@_requiresConcreteImplementation' cannot be satisfied }}
func f(_: Int) -> Bool { true }
}

struct U: R {
func f(_: Int) -> Bool { true }
func g(_: Int) -> Bool { false }
}

class C {
@_requiresConcreteImplementation // expected-error {{'@_requiresConcreteImplementation' can only be specified on protocol requirements}}
var x: Int = 42
}