Sema: Check custom domain availability during conformance checking.

Protocol requirement witnesses cannot only be available in a custom
availability domain if the requirement does not have the same availability
constraint.

Resolves rdar://156462516.
This commit is contained in:
Allan Shortlidge
2025-07-25 18:32:49 -07:00
parent b45d278bce
commit 0fabfa3f99
7 changed files with 266 additions and 166 deletions

View File

@@ -7123,6 +7123,11 @@ ERROR(availability_protocol_requires_version,
(const ProtocolDecl *, const ValueDecl *, AvailabilityDomain,
AvailabilityRange))
ERROR(availability_protocol_requirement_only_available_in,
none, "protocol %0 requirement %1 cannot be satisfied by %kindonly1 that "
"is only available in %2",
(const ProtocolDecl *, const ValueDecl *, AvailabilityDomain))
NOTE(availability_protocol_requirement_here, none,
"protocol requirement here", ())

View File

@@ -12,6 +12,7 @@
#ifndef SWIFT_AST_REQUIREMENTMATCH_H
#define SWIFT_AST_REQUIREMENTMATCH_H
#include "swift/AST/AvailabilityConstraint.h"
#include "swift/AST/RequirementEnvironment.h"
#include "swift/AST/Type.h"
#include "swift/AST/Types.h"
@@ -252,7 +253,8 @@ class RequirementCheck {
/// Storage for `CheckKind::Availability`.
struct {
AvailabilityRange requiredRange;
AvailabilityConstraint constraint;
AvailabilityContext requiredContext;
} Availability;
};
@@ -266,8 +268,10 @@ public:
RequirementCheck(AccessScope requiredAccessScope, bool forSetter)
: Kind(CheckKind::Access), Access{requiredAccessScope, forSetter} {}
RequirementCheck(AvailabilityRange requiredRange)
: Kind(CheckKind::Availability), Availability{requiredRange} {}
RequirementCheck(AvailabilityConstraint constraint,
AvailabilityContext requiredContext)
: Kind(CheckKind::Availability),
Availability{constraint, requiredContext} {}
CheckKind getKind() const { return Kind; }
@@ -280,7 +284,7 @@ public:
/// True if the witness is less available than the requirement.
bool isLessAvailable() const {
return (Kind == CheckKind::Availability)
? !Availability.requiredRange.isKnownUnreachable()
? !Availability.constraint.isUnavailable()
: false;
}
@@ -291,11 +295,18 @@ public:
return Access.requiredScope;
}
/// The availability constraint that would fail if the witness were accessed
/// from contexts in which the requirement is available.
AvailabilityConstraint getAvailabilityConstraint() const {
ASSERT(Kind == CheckKind::Availability);
return Availability.constraint;
}
/// The required availability range for checks that failed due to the witness
/// being less available than the requirement.
AvailabilityRange getRequiredAvailabilityRange() const {
AvailabilityContext getRequiredAvailabilityContext() const {
ASSERT(Kind == CheckKind::Availability);
return Availability.requiredRange;
return Availability.requiredContext;
}
};

View File

@@ -1670,72 +1670,6 @@ SelfAccessKindRequest::evaluate(Evaluator &evaluator, FuncDecl *FD) const {
return SelfAccessKind::NonMutating;
}
bool TypeChecker::isAvailabilitySafeForConformance(
const ProtocolDecl *proto, const ValueDecl *requirement,
const ValueDecl *witness, const DeclContext *dc,
AvailabilityRange &requirementInfo) {
// We assume conformances in
// non-SourceFiles have already been checked for availability.
if (!dc->getParentSourceFile())
return true;
auto &Context = proto->getASTContext();
assert(dc->getSelfNominalTypeDecl() &&
"Must have a nominal or extension context");
auto contextForConformingDecl =
AvailabilityContext::forDeclSignature(dc->getAsDecl());
// If the conformance is unavailable then it's irrelevant whether the witness
// is potentially unavailable.
if (contextForConformingDecl.isUnavailable())
return true;
// Make sure that any access of the witness through the protocol
// can only occur when the witness is available. That is, make sure that
// on every version where the conforming declaration is available, if the
// requirement is available then the witness is available as well.
// We do this by checking that (an over-approximation of) the intersection of
// the requirement's available range with both the conforming declaration's
// available range and the protocol's available range is fully contained in
// (an over-approximation of) the intersection of the witnesses's available
// range with both the conforming type's available range and the protocol
// declaration's available range.
AvailabilityRange witnessInfo =
AvailabilityInference::availableRange(witness);
requirementInfo = AvailabilityInference::availableRange(requirement);
AvailabilityRange infoForConformingDecl =
contextForConformingDecl.getPlatformRange();
// Relax the requirements for @_spi witnesses by treating the requirement as
// if it were introduced at the deployment target. This is not strictly sound
// since clients of SPI do not necessarily have the same deployment target as
// the module declaring the requirement. However, now that the public
// declarations in API libraries are checked according to the minimum possible
// deployment target of their clients this relaxation is needed for source
// compatibility with some existing code and is reasonably safe for the
// majority of cases.
if (witness->isSPI()) {
AvailabilityRange deploymentTarget =
AvailabilityRange::forDeploymentTarget(Context);
requirementInfo.constrainWith(deploymentTarget);
}
// Constrain over-approximates intersection of version ranges.
witnessInfo.constrainWith(infoForConformingDecl);
requirementInfo.constrainWith(infoForConformingDecl);
AvailabilityRange infoForProtocolDecl =
AvailabilityContext::forDeclSignature(proto).getPlatformRange();
witnessInfo.constrainWith(infoForProtocolDecl);
requirementInfo.constrainWith(infoForProtocolDecl);
return requirementInfo.isContainedIn(witnessInfo);
}
// Returns 'nullptr' if this is the 'newValue' or 'oldValue' parameter;
// otherwise, returns the corresponding parameter of the subscript
// declaration.

View File

@@ -1821,13 +1821,56 @@ static bool checkWitnessAccess(DeclContext *dc,
return false;
}
bool WitnessChecker::
checkWitnessAvailability(ValueDecl *requirement,
ValueDecl *witness,
AvailabilityRange *requiredAvailability) {
return (!getASTContext().LangOpts.DisableAvailabilityChecking &&
!TypeChecker::isAvailabilitySafeForConformance(
Proto, requirement, witness, DC, *requiredAvailability));
static std::optional<AvailabilityConstraint>
checkWitnessAvailability(const ValueDecl *requirement, const ValueDecl *witness,
const DeclContext *dc,
AvailabilityContext &requiredContext) {
auto &ctx = dc->getASTContext();
if (ctx.LangOpts.DisableAvailabilityChecking)
return std::nullopt;
// If the requirement is self-witnessing then no need to check availability.
if (requirement == witness)
return std::nullopt;
// We assume conformances in implicit code have already been checked for
// availability.
if (!dc->getParentSourceFile())
return std::nullopt;
assert(dc->getSelfNominalTypeDecl() &&
"Must have a nominal or extension context");
auto requirementAvailability =
AvailabilityContext::forDeclSignature(requirement);
requiredContext.constrainWithContext(requirementAvailability, ctx);
// The witness is allowed to be less available than the requirement as long
// as it is as available as the overall conformance.
auto conformanceAvailability =
AvailabilityContext::forDeclSignature(dc->getAsDecl());
requiredContext.constrainWithContext(conformanceAvailability, ctx);
// Relax the requirements for @_spi witnesses by treating the requirement as
// if it were introduced at the deployment target. This is not strictly sound
// since clients of SPI do not necessarily have the same deployment target as
// the module declaring the requirement. However, now that the public
// declarations in API libraries are checked according to the minimum possible
// deployment target of their clients this relaxation is needed for source
// compatibility with some existing code and is reasonably safe for the
// majority of cases.
if (witness->isSPI())
requiredContext.constrainWithContext(
AvailabilityContext::forDeploymentTarget(ctx), ctx);
// In order to maintain source compatibility, universally unavailable decls
// are allowed to witness universally unavailable requirements.
AvailabilityConstraintFlags flags;
flags |= AvailabilityConstraintFlag::
AllowUniversallyUnavailableInCompatibleContexts;
return getAvailabilityConstraintsForDecl(witness, requiredContext, flags)
.getPrimaryConstraint();
}
RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
@@ -1850,12 +1893,14 @@ RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
return CheckKind::UsableFromInline;
}
auto requiredAvailability = AvailabilityRange::alwaysAvailable();
if (checkWitnessAvailability(requirement, match.Witness,
&requiredAvailability)) {
return RequirementCheck(requiredAvailability);
}
// A witness cannot be less available than its requirement.
auto requiredContext = AvailabilityContext::forAlwaysAvailable(Context);
if (auto constraint = checkWitnessAvailability(requirement, match.Witness, DC,
requiredContext))
return RequirementCheck(*constraint, requiredContext);
// An unavailable requirement cannot be witnessed, just like an unavailable
// method cannot be overridden.
if (requirement->isUnavailable() && match.Witness->getDeclContext() == DC) {
return RequirementCheck(CheckKind::RequirementUnavailable);
}
@@ -1878,27 +1923,6 @@ RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
}
}
if (match.Witness->isUnavailable() && !requirement->isUnavailable()) {
auto nominalOrExtensionIsUnavailable = [&]() {
if (auto extension = dyn_cast<ExtensionDecl>(DC)) {
if (extension->isUnavailable())
return true;
}
if (auto adoptingNominal = DC->getSelfNominalTypeDecl()) {
if (AvailabilityContext::forDeclSignature(adoptingNominal)
.isUnavailable())
return true;
}
return false;
};
// Allow unavailable nominals or extension to have unavailable witnesses.
if (!nominalOrExtensionIsUnavailable())
return RequirementCheck(AvailabilityRange::neverAvailable());
}
// Warn about deprecated default implementations if the requirement is
// not deprecated, and the conformance is not deprecated.
bool isDefaultWitness = false;
@@ -4468,20 +4492,31 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
case CheckKind::Availability: {
if (check.isLessAvailable()) {
ASSERT(check.getRequiredAvailabilityRange().hasMinimumVersion());
getASTContext().addDelayedConformanceDiag(
Conformance, false,
[witness, requirement,
check](NormalProtocolConformance *conformance) {
ASTContext &ctx = witness->getASTContext();
auto &diags = ctx.Diags;
SourceLoc diagLoc =
getLocForDiagnosingWitness(conformance, witness);
diags.diagnose(diagLoc,
diag::availability_protocol_requires_version,
conformance->getProtocol(), witness,
ctx.getTargetAvailabilityDomain(),
check.getRequiredAvailabilityRange());
auto diagLoc = getLocForDiagnosingWitness(conformance, witness);
auto attr = check.getAvailabilityConstraint().getAttr();
auto domain = attr.getDomain();
auto requiredRange =
check.getRequiredAvailabilityContext().getAvailabilityRange(
domain, ctx);
if (requiredRange) {
diags.diagnose(
diagLoc, diag::availability_protocol_requires_version,
conformance->getProtocol(), witness,
domain.isPlatform() ? ctx.getTargetAvailabilityDomain()
: domain,
*requiredRange);
} else {
diags.diagnose(
diagLoc,
diag::availability_protocol_requirement_only_available_in,
conformance->getProtocol(), witness, domain);
}
emitDeclaredHereIfNeeded(diags, diagLoc, witness);
diags.diagnose(requirement,
diag::availability_protocol_requirement_here);
@@ -4489,12 +4524,12 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
} else {
getASTContext().addDelayedConformanceDiag(
Conformance, true,
[witness, requirement](NormalProtocolConformance *conformance) {
[witness, requirement,
check](NormalProtocolConformance *conformance) {
auto &diags = witness->getASTContext().Diags;
auto diagLoc = getLocForDiagnosingWitness(conformance, witness);
// FIXME: [availability] Get the original constraint.
auto attr = witness->getUnavailableAttr();
EncodedDiagnosticMessage EncodedMessage(attr->getMessage());
auto attr = check.getAvailabilityConstraint().getAttr();
EncodedDiagnosticMessage EncodedMessage(attr.getMessage());
diags.diagnose(diagLoc, diag::witness_unavailable, witness,
conformance->getProtocol(),
EncodedMessage.Message);
@@ -5219,62 +5254,91 @@ static void diagnoseInvariantSelfRequirement(
.warnUntilSwiftVersion(6);
}
static bool diagnoseTypeWitnessAvailability(
NormalProtocolConformance *conformance, const TypeDecl *witness,
const AssociatedTypeDecl *assocType, const ExportContext &where) {
static bool
diagnoseTypeWitnessAvailability(NormalProtocolConformance *conformance,
const TypeDecl *witness,
const AssociatedTypeDecl *assocType) {
auto dc = conformance->getDeclContext();
auto &ctx = dc->getASTContext();
if (ctx.LangOpts.DisableAvailabilityChecking)
return false;
auto requiredAvailability = AvailabilityContext::forAlwaysAvailable(ctx);
auto constraint =
checkWitnessAvailability(assocType, witness, dc, requiredAvailability);
if (!constraint)
return false;
auto attr = constraint->getAttr();
auto domain = attr.getDomain();
// In Swift 6 and earlier type witness availability diagnostics are warnings.
using namespace version;
const unsigned warnBeforeVersion = Version::getFutureMajorLanguageVersion();
bool shouldError =
ctx.LangOpts.EffectiveLanguageVersion.isVersionAtLeast(warnBeforeVersion);
auto constraint =
getAvailabilityConstraintsForDecl(witness, where.getAvailability())
.getPrimaryConstraint();
if (constraint && constraint->isUnavailable()) {
auto attr = constraint->getAttr();
switch (domain.getKind()) {
case AvailabilityDomain::Kind::Universal:
case AvailabilityDomain::Kind::SwiftLanguage:
case AvailabilityDomain::Kind::PackageDescription:
case AvailabilityDomain::Kind::Platform:
break;
case AvailabilityDomain::Kind::Embedded:
case AvailabilityDomain::Kind::Custom:
shouldError = true;
break;
}
if (constraint->isUnavailable()) {
ctx.addDelayedConformanceDiag(
conformance, shouldError,
[witness, assocType, attr](NormalProtocolConformance *conformance) {
[witness, assocType, attr,
shouldError](NormalProtocolConformance *conformance) {
SourceLoc loc = getLocForDiagnosingWitness(conformance, witness);
EncodedDiagnosticMessage encodedMessage(attr.getMessage());
auto &ctx = conformance->getDeclContext()->getASTContext();
ctx.Diags
.diagnose(loc, diag::witness_unavailable, witness,
conformance->getProtocol(), encodedMessage.Message)
.warnUntilSwiftVersion(warnBeforeVersion);
.warnUntilSwiftVersionIf(!shouldError, warnBeforeVersion);
emitDeclaredHereIfNeeded(ctx.Diags, loc, witness);
ctx.Diags.diagnose(assocType, diag::requirement_declared_here,
assocType);
});
}
auto requiredRange = AvailabilityRange::alwaysAvailable();
if (!TypeChecker::isAvailabilitySafeForConformance(
conformance->getProtocol(), assocType, witness, dc, requiredRange)) {
ctx.addDelayedConformanceDiag(
conformance, shouldError,
[witness, requiredRange](NormalProtocolConformance *conformance) {
SourceLoc loc = getLocForDiagnosingWitness(conformance, witness);
auto &ctx = conformance->getDeclContext()->getASTContext();
ctx.Diags
.diagnose(loc, diag::availability_protocol_requires_version,
conformance->getProtocol(), witness,
ctx.getTargetAvailabilityDomain(), requiredRange)
.warnUntilSwiftVersion(warnBeforeVersion);
emitDeclaredHereIfNeeded(ctx.Diags, loc, witness);
});
return true;
}
return false;
auto requiredRange = requiredAvailability.getAvailabilityRange(domain, ctx);
ctx.addDelayedConformanceDiag(
conformance, shouldError,
[witness, attr, requiredRange,
shouldError](NormalProtocolConformance *conformance) {
SourceLoc loc = getLocForDiagnosingWitness(conformance, witness);
auto &ctx = conformance->getDeclContext()->getASTContext();
auto domain = attr.getDomain();
if (requiredRange) {
ctx.Diags
.diagnose(loc, diag::availability_protocol_requires_version,
conformance->getProtocol(), witness,
domain.isPlatform() ? ctx.getTargetAvailabilityDomain()
: domain,
*requiredRange)
.warnUntilSwiftVersionIf(!shouldError, warnBeforeVersion);
} else {
ctx.Diags
.diagnose(
loc,
diag::availability_protocol_requirement_only_available_in,
conformance->getProtocol(), witness, domain)
.warnUntilSwiftVersionIf(!shouldError, warnBeforeVersion);
}
emitDeclaredHereIfNeeded(ctx.Diags, loc, witness);
});
return true;
}
/// Check whether the type witnesses satisfy the protocol's requirement
@@ -5407,7 +5471,7 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
// The type witness must be as available as the associated type.
if (auto witness = type->getAnyNominal())
diagnoseTypeWitnessAvailability(conformance, witness, assocType, where);
diagnoseTypeWitnessAvailability(conformance, witness, assocType);
// Make sure any associated type witnesses don't make reference to a
// type we can't emit metadata for, or we're going to have trouble at

View File

@@ -88,10 +88,6 @@ protected:
unsigned &bestIdx,
bool &doNotDiagnoseMatches);
bool checkWitnessAvailability(ValueDecl *requirement,
ValueDecl *witness,
AvailabilityRange *requirementInfo);
RequirementCheck checkWitness(ValueDecl *requirement,
const RequirementMatch &match);
};

View File

@@ -1025,15 +1025,6 @@ bool diagnoseConformanceExportability(SourceLoc loc,
/// potentially unavailable API elements
/// @{
/// Returns true if the availability of the witness
/// is sufficient to safely conform to the requirement in the context
/// the provided conformance. On return, requiredAvailability holds th
/// availability levels required for conformance.
bool isAvailabilitySafeForConformance(
const ProtocolDecl *proto, const ValueDecl *requirement,
const ValueDecl *witness, const DeclContext *dc,
AvailabilityRange &requiredAvailability);
/// Returns a diagnostic indicating why the declaration cannot be annotated
/// with an @available() attribute indicating it is potentially unavailable
/// or None if this is allowed.

View File

@@ -253,18 +253,117 @@ extension Container {
func unavailableInEnabledDomain() { }
}
protocol P { }
protocol OpaqueReturnType { }
@available(EnabledDomain)
struct AvailableConformsToP: P { }
struct AvailableOpaqueReturnType: OpaqueReturnType { }
@available(EnabledDomain, unavailable)
struct UnavailableConformsToP: P { }
struct UnavailableOpaqueReturnType: OpaqueReturnType { }
func testOpaqueReturnType() -> some P {
func testOpaqueReturnType() -> some OpaqueReturnType {
if #available(EnabledDomain) { // expected-error {{opaque return type cannot depend on EnabledDomain availability}}
return AvailableConformsToP()
return AvailableOpaqueReturnType()
} else {
return UnavailableConformsToP()
return UnavailableOpaqueReturnType()
}
}
protocol HasRequirementInEnabledDomain {
func alwaysAvailableRequirement()
// expected-note@-1 3 {{protocol requirement here}}
// expected-note@-2 {{requirement 'alwaysAvailableRequirement()' declared here}}
@available(EnabledDomain)
func availableInEnabledDomain()
// expected-note@-1 2 {{protocol requirement here}}
// expected-note@-2 {{requirement 'availableInEnabledDomain()' declared here}}
}
protocol HasAssocTypeRequirementInEnabledDomain {
associatedtype A // expected-note {{requirement 'A' declared here}}
@available(EnabledDomain)
associatedtype B // expected-note {{requirement 'B' declared here}}
}
protocol HasRequirementUnavailableInEnabledDomain {
@available(EnabledDomain, unavailable) // expected-error {{protocol members can only be marked unavailable in an '@objc' protocol}}
func unavailableInEnabledDomain()
}
struct ConformsToHasRequirementsInEnabledDomain: HasRequirementInEnabledDomain {
func alwaysAvailableRequirement() { }
func availableInEnabledDomain() { }
}
@available(EnabledDomain)
struct ConformsToHasRequirementsInEnabledDomain1: HasRequirementInEnabledDomain {
func alwaysAvailableRequirement() { }
func availableInEnabledDomain() { }
}
@available(EnabledDomain, unavailable)
struct ConformsToHasRequirementsInEnabledDomain2: HasRequirementInEnabledDomain {
func alwaysAvailableRequirement() { }
func availableInEnabledDomain() { }
}
struct ConformsToHasRequirementsInEnabledDomain3: HasRequirementInEnabledDomain {
@available(EnabledDomain)
func alwaysAvailableRequirement() { } // expected-error {{protocol 'HasRequirementInEnabledDomain' requirement 'alwaysAvailableRequirement()' cannot be satisfied by instance method that is only available in EnabledDomain}}
@available(EnabledDomain)
func availableInEnabledDomain() { }
}
struct ConformsToHasRequirementsInEnabledDomain4: HasRequirementInEnabledDomain { // expected-error {{type 'ConformsToHasRequirementsInEnabledDomain4' does not conform to protocol 'HasRequirementInEnabledDomain'}}
@available(EnabledDomain, unavailable)
func alwaysAvailableRequirement() { } // expected-error {{unavailable instance method 'alwaysAvailableRequirement()' was used to satisfy a requirement of protocol 'HasRequirementInEnabledDomain'}}
@available(EnabledDomain, unavailable)
func availableInEnabledDomain() { } // expected-error {{unavailable instance method 'availableInEnabledDomain()' was used to satisfy a requirement of protocol 'HasRequirementInEnabledDomain'}}
}
struct ConformsToHasRequirementsInEnabledDomain5: HasRequirementInEnabledDomain {
@available(DisabledDomain)
func alwaysAvailableRequirement() { } // expected-error {{protocol 'HasRequirementInEnabledDomain' requirement 'alwaysAvailableRequirement()' cannot be satisfied by instance method that is only available in DisabledDomain}}
@available(DisabledDomain)
func availableInEnabledDomain() { } // expected-error {{protocol 'HasRequirementInEnabledDomain' requirement 'availableInEnabledDomain()' cannot be satisfied by instance method that is only available in DisabledDomain}}
}
struct ConformsToHasRequirementsInEnabledDomain6: HasRequirementInEnabledDomain {
@available(EnabledDomain)
@available(DisabledDomain)
func alwaysAvailableRequirement() { } // expected-error {{protocol 'HasRequirementInEnabledDomain' requirement 'alwaysAvailableRequirement()' cannot be satisfied by instance method that is only available in DisabledDomain}}
@available(EnabledDomain)
@available(DisabledDomain)
func availableInEnabledDomain() { } // expected-error {{protocol 'HasRequirementInEnabledDomain' requirement 'availableInEnabledDomain()' cannot be satisfied by instance method that is only available in DisabledDomain}}
}
struct ConformsToHasAssocTypeRequirementInEnabledDomain: HasAssocTypeRequirementInEnabledDomain {
struct A { }
struct B { }
}
struct ConformsToHasAssocTypeRequirementInEnabledDomain1: HasAssocTypeRequirementInEnabledDomain { // expected-error {{type 'ConformsToHasAssocTypeRequirementInEnabledDomain1' does not conform to protocol 'HasAssocTypeRequirementInEnabledDomain'}}
@available(EnabledDomain)
struct A { } // expected-error {{protocol 'HasAssocTypeRequirementInEnabledDomain' requirement 'A' cannot be satisfied by struct that is only available in EnabledDomain}}
@available(EnabledDomain)
struct B { }
}
struct ConformsToHasAssocTypeRequirementInEnabledDomain2: HasAssocTypeRequirementInEnabledDomain { // expected-error {{type 'ConformsToHasAssocTypeRequirementInEnabledDomain2' does not conform to protocol 'HasAssocTypeRequirementInEnabledDomain'}}
@available(EnabledDomain, unavailable)
struct A { } // expected-error {{unavailable struct 'A' was used to satisfy a requirement of protocol 'HasAssocTypeRequirementInEnabledDomain'}}
@available(EnabledDomain, unavailable)
struct B { } // expected-error {{unavailable struct 'B' was used to satisfy a requirement of protocol 'HasAssocTypeRequirementInEnabledDomain'}}
}
struct ConformsToHasAssocTypeRequirementInEnabledDomain3: HasAssocTypeRequirementInEnabledDomain { // expected-error {{type 'ConformsToHasAssocTypeRequirementInEnabledDomain3' does not conform to protocol 'HasAssocTypeRequirementInEnabledDomain'}}
@available(DisabledDomain)
struct A { } // expected-error {{protocol 'HasAssocTypeRequirementInEnabledDomain' requirement 'A' cannot be satisfied by struct that is only available in DisabledDomain}}
@available(DisabledDomain)
struct B { } // expected-error {{protocol 'HasAssocTypeRequirementInEnabledDomain' requirement 'B' cannot be satisfied by struct that is only available in DisabledDomain}}
}