mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Require @usableFromInline on associated type witnesses
...when the protocol and the conforming type are not both public but
are both public-or-usableFromInline. It's possible to write inlinable
functions that depend on these types:
public protocol HasAssoc {
associatedtype Assoc
}
public func getAssoc<T: HasAssoc>(_: T) -> T.Assoc
@usableFromInline struct Impl: HasAssoc {
@usableFromInline typealias Assoc = Int
}
@inlinable func test() {
let x: Int = getAssoc(Impl())
}
rdar://problem/43824052
This commit is contained in:
@@ -1691,6 +1691,14 @@ ERROR(type_witness_not_accessible_type,none,
|
|||||||
"%0 %1 must be as accessible as its enclosing type because it "
|
"%0 %1 must be as accessible as its enclosing type because it "
|
||||||
"matches a requirement in protocol %3",
|
"matches a requirement in protocol %3",
|
||||||
(DescriptiveDeclKind, DeclName, AccessLevel, DeclName))
|
(DescriptiveDeclKind, DeclName, AccessLevel, DeclName))
|
||||||
|
ERROR(witness_not_usable_from_inline,none,
|
||||||
|
"%0 %1 must be declared '@usableFromInline' because "
|
||||||
|
"because it matches a requirement in protocol %2",
|
||||||
|
(DescriptiveDeclKind, DeclName, DeclName))
|
||||||
|
WARNING(witness_not_usable_from_inline_warn,none,
|
||||||
|
"%0 %1 should be declared '@usableFromInline' because "
|
||||||
|
"because it matches a requirement in protocol %2",
|
||||||
|
(DescriptiveDeclKind, DeclName, DeclName))
|
||||||
ERROR(type_witness_objc_generic_parameter,none,
|
ERROR(type_witness_objc_generic_parameter,none,
|
||||||
"type %0 involving Objective-C type parameter%select{| %1}2 cannot be "
|
"type %0 involving Objective-C type parameter%select{| %1}2 cannot be "
|
||||||
"used for associated type %3 of protocol %4",
|
"used for associated type %3 of protocol %4",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
#include "ConstraintSystem.h"
|
#include "ConstraintSystem.h"
|
||||||
#include "DerivedConformances.h"
|
#include "DerivedConformances.h"
|
||||||
#include "MiscDiagnostics.h"
|
#include "MiscDiagnostics.h"
|
||||||
#include "TypeChecker.h"
|
|
||||||
#include "TypeCheckAvailability.h"
|
#include "TypeCheckAvailability.h"
|
||||||
#include "swift/Basic/SourceManager.h"
|
#include "swift/Basic/SourceManager.h"
|
||||||
#include "swift/Basic/StringExtras.h"
|
#include "swift/Basic/StringExtras.h"
|
||||||
@@ -1233,23 +1232,51 @@ bool WitnessChecker::findBestWitness(
|
|||||||
return isReallyBest;
|
return isReallyBest;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WitnessChecker::checkWitnessAccess(AccessScope &requiredAccessScope,
|
AccessScope WitnessChecker::getRequiredAccessScope() {
|
||||||
ValueDecl *requirement,
|
if (RequiredAccessScopeAndUsableFromInline.hasValue())
|
||||||
|
return RequiredAccessScopeAndUsableFromInline.getValue().first;
|
||||||
|
|
||||||
|
AccessScope result = Proto->getFormalAccessScope(DC);
|
||||||
|
|
||||||
|
bool witnessesMustBeUsableFromInline = false;
|
||||||
|
if (Adoptee) {
|
||||||
|
const NominalTypeDecl *adoptingNominal = Adoptee->getAnyNominal();
|
||||||
|
|
||||||
|
// Compute the intersection of the conforming type's access scope
|
||||||
|
// and the protocol's access scope.
|
||||||
|
auto scopeIntersection =
|
||||||
|
result.intersectWith(adoptingNominal->getFormalAccessScope(DC));
|
||||||
|
assert(scopeIntersection.hasValue());
|
||||||
|
result = scopeIntersection.getValue();
|
||||||
|
|
||||||
|
if (!result.isPublic()) {
|
||||||
|
witnessesMustBeUsableFromInline =
|
||||||
|
Proto->getFormalAccessScope(
|
||||||
|
DC, /*usableFromInlineAsPublic*/true).isPublic() &&
|
||||||
|
adoptingNominal->getFormalAccessScope(
|
||||||
|
DC, /*usableFromInlineAsPublic*/true).isPublic();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!result.isPublic()) {
|
||||||
|
witnessesMustBeUsableFromInline =
|
||||||
|
Proto->getFormalAccessScope(
|
||||||
|
DC, /*usableFromInlineAsPublic*/true).isPublic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RequiredAccessScopeAndUsableFromInline =
|
||||||
|
std::make_pair(result, witnessesMustBeUsableFromInline);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WitnessChecker::checkWitnessAccess(ValueDecl *requirement,
|
||||||
ValueDecl *witness,
|
ValueDecl *witness,
|
||||||
bool *isSetter) {
|
bool *isSetter) {
|
||||||
*isSetter = false;
|
*isSetter = false;
|
||||||
if (!TC.getLangOpts().EnableAccessControl)
|
if (!TC.getLangOpts().EnableAccessControl)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Compute the intersection of the conforming type's access scope
|
AccessScope actualScopeToCheck = getRequiredAccessScope();
|
||||||
// and the protocol's access scope.
|
|
||||||
auto scopeIntersection =
|
|
||||||
requiredAccessScope.intersectWith(Proto->getFormalAccessScope(DC));
|
|
||||||
assert(scopeIntersection.hasValue());
|
|
||||||
|
|
||||||
requiredAccessScope = *scopeIntersection;
|
|
||||||
|
|
||||||
AccessScope actualScopeToCheck = requiredAccessScope;
|
|
||||||
|
|
||||||
// Setting the 'forConformance' flag means that we admit witnesses in
|
// Setting the 'forConformance' flag means that we admit witnesses in
|
||||||
// protocol extensions that we can see, but are not necessarily as
|
// protocol extensions that we can see, but are not necessarily as
|
||||||
@@ -1270,7 +1297,7 @@ bool WitnessChecker::checkWitnessAccess(AccessScope &requiredAccessScope,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actualScopeToCheck.hasEqualDeclContextWith(requiredAccessScope))
|
if (actualScopeToCheck.hasEqualDeclContextWith(getRequiredAccessScope()))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1297,20 +1324,17 @@ checkWitnessAvailability(ValueDecl *requirement,
|
|||||||
DC, *requiredAvailability));
|
DC, *requiredAvailability));
|
||||||
}
|
}
|
||||||
|
|
||||||
RequirementCheck WitnessChecker::
|
RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
|
||||||
checkWitness(AccessScope requiredAccessScope,
|
|
||||||
ValueDecl *requirement,
|
|
||||||
const RequirementMatch &match) {
|
const RequirementMatch &match) {
|
||||||
if (!match.OptionalAdjustments.empty())
|
if (!match.OptionalAdjustments.empty())
|
||||||
return CheckKind::OptionalityConflict;
|
return CheckKind::OptionalityConflict;
|
||||||
|
|
||||||
bool isSetter = false;
|
bool isSetter = false;
|
||||||
if (checkWitnessAccess(requiredAccessScope, requirement, match.Witness,
|
if (checkWitnessAccess(requirement, match.Witness, &isSetter)) {
|
||||||
&isSetter)) {
|
|
||||||
CheckKind kind = (isSetter
|
CheckKind kind = (isSetter
|
||||||
? CheckKind::AccessOfSetter
|
? CheckKind::AccessOfSetter
|
||||||
: CheckKind::Access);
|
: CheckKind::Access);
|
||||||
return RequirementCheck(kind, requiredAccessScope);
|
return RequirementCheck(kind, getRequiredAccessScope());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto requiredAvailability = AvailabilityContext::alwaysAvailable();
|
auto requiredAvailability = AvailabilityContext::alwaysAvailable();
|
||||||
@@ -2340,6 +2364,36 @@ bool ConformanceChecker::checkObjCTypeErasedGenerics(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
/// Helper class for use with ConformanceChecker::diagnoseOrDefer when a witness
|
||||||
|
/// needs to be marked as '\@usableFromInline'.
|
||||||
|
class DiagnoseUsableFromInline {
|
||||||
|
const ValueDecl *witness;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DiagnoseUsableFromInline(const ValueDecl *witness)
|
||||||
|
: witness(witness) {
|
||||||
|
assert(witness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(const NormalProtocolConformance *conformance) {
|
||||||
|
auto proto = conformance->getProtocol();
|
||||||
|
ASTContext &ctx = proto->getASTContext();
|
||||||
|
|
||||||
|
auto diagID = diag::witness_not_usable_from_inline;
|
||||||
|
if (!ctx.isSwiftVersionAtLeast(5))
|
||||||
|
diagID = diag::witness_not_usable_from_inline_warn;
|
||||||
|
|
||||||
|
SourceLoc diagLoc = getLocForDiagnosingWitness(conformance, witness);
|
||||||
|
ctx.Diags.diagnose(diagLoc, diagID,
|
||||||
|
witness->getDescriptiveKind(),
|
||||||
|
witness->getFullName(),
|
||||||
|
proto->getName());
|
||||||
|
emitDeclaredHereIfNeeded(ctx.Diags, diagLoc, witness);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
|
void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
|
||||||
Type type,
|
Type type,
|
||||||
TypeDecl *typeDecl) {
|
TypeDecl *typeDecl) {
|
||||||
@@ -2357,24 +2411,20 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
|
|||||||
|
|
||||||
if (typeDecl) {
|
if (typeDecl) {
|
||||||
// Check access.
|
// Check access.
|
||||||
AccessScope requiredAccessScope =
|
|
||||||
Adoptee->getAnyNominal()->getFormalAccessScope(DC);
|
|
||||||
bool isSetter = false;
|
bool isSetter = false;
|
||||||
if (checkWitnessAccess(requiredAccessScope, assocType, typeDecl,
|
if (checkWitnessAccess(assocType, typeDecl, &isSetter)) {
|
||||||
&isSetter)) {
|
|
||||||
assert(!isSetter);
|
assert(!isSetter);
|
||||||
|
|
||||||
// Avoid relying on the lifetime of 'this'.
|
// Avoid relying on the lifetime of 'this'.
|
||||||
const DeclContext *DC = this->DC;
|
const DeclContext *DC = this->DC;
|
||||||
diagnoseOrDefer(assocType, false,
|
diagnoseOrDefer(assocType, false,
|
||||||
[DC, typeDecl, requiredAccessScope](
|
[this, DC, typeDecl](NormalProtocolConformance *conformance) {
|
||||||
NormalProtocolConformance *conformance) {
|
|
||||||
AccessLevel requiredAccess =
|
AccessLevel requiredAccess =
|
||||||
requiredAccessScope.requiredAccessForDiagnostics();
|
getRequiredAccessScope().requiredAccessForDiagnostics();
|
||||||
auto proto = conformance->getProtocol();
|
auto proto = conformance->getProtocol();
|
||||||
auto protoAccessScope = proto->getFormalAccessScope(DC);
|
auto protoAccessScope = proto->getFormalAccessScope(DC);
|
||||||
bool protoForcesAccess =
|
bool protoForcesAccess =
|
||||||
requiredAccessScope.hasEqualDeclContextWith(protoAccessScope);
|
getRequiredAccessScope().hasEqualDeclContextWith(protoAccessScope);
|
||||||
auto diagKind = protoForcesAccess
|
auto diagKind = protoForcesAccess
|
||||||
? diag::type_witness_not_accessible_proto
|
? diag::type_witness_not_accessible_proto
|
||||||
: diag::type_witness_not_accessible_type;
|
: diag::type_witness_not_accessible_type;
|
||||||
@@ -2391,6 +2441,13 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
|
|||||||
fixItAccess(fixItDiag, typeDecl, requiredAccess);
|
fixItAccess(fixItDiag, typeDecl, requiredAccess);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUsableFromInlineRequired()) {
|
||||||
|
bool witnessIsUsableFromInline = typeDecl->getFormalAccessScope(
|
||||||
|
DC, /*usableFromInlineAsPublic*/true).isPublic();
|
||||||
|
if (!witnessIsUsableFromInline)
|
||||||
|
diagnoseOrDefer(assocType, false, DiagnoseUsableFromInline(typeDecl));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there was no type declaration, synthesize one.
|
// If there was no type declaration, synthesize one.
|
||||||
auto aliasDecl = new (TC.Context) TypeAliasDecl(SourceLoc(),
|
auto aliasDecl = new (TC.Context) TypeAliasDecl(SourceLoc(),
|
||||||
@@ -2939,8 +2996,7 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nominalAccessScope = nominal->getFormalAccessScope(DC);
|
auto check = checkWitness(requirement, best);
|
||||||
auto check = checkWitness(nominalAccessScope, requirement, best);
|
|
||||||
|
|
||||||
switch (check.Kind) {
|
switch (check.Kind) {
|
||||||
case CheckKind::Success:
|
case CheckKind::Success:
|
||||||
@@ -5432,7 +5488,7 @@ DefaultWitnessChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
|
|||||||
|
|
||||||
// Perform the same checks as conformance witness matching, but silently
|
// Perform the same checks as conformance witness matching, but silently
|
||||||
// ignore the candidate instead of diagnosing anything.
|
// ignore the candidate instead of diagnosing anything.
|
||||||
auto check = checkWitness(AccessScope::getPublic(), requirement, best);
|
auto check = checkWitness(requirement, best);
|
||||||
if (check.Kind != CheckKind::Success)
|
if (check.Kind != CheckKind::Success)
|
||||||
return ResolveWitnessResult::ExplicitFailed;
|
return ResolveWitnessResult::ExplicitFailed;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
#ifndef SWIFT_SEMA_PROTOCOL_H
|
#ifndef SWIFT_SEMA_PROTOCOL_H
|
||||||
#define SWIFT_SEMA_PROTOCOL_H
|
#define SWIFT_SEMA_PROTOCOL_H
|
||||||
|
|
||||||
|
#include "TypeChecker.h"
|
||||||
|
#include "swift/AST/AccessScope.h"
|
||||||
#include "swift/AST/RequirementEnvironment.h"
|
#include "swift/AST/RequirementEnvironment.h"
|
||||||
#include "swift/AST/Type.h"
|
#include "swift/AST/Type.h"
|
||||||
#include "swift/AST/Types.h"
|
#include "swift/AST/Types.h"
|
||||||
@@ -475,11 +477,24 @@ protected:
|
|||||||
|
|
||||||
RequirementEnvironmentCache ReqEnvironmentCache;
|
RequirementEnvironmentCache ReqEnvironmentCache;
|
||||||
|
|
||||||
|
Optional<std::pair<AccessScope, bool>> RequiredAccessScopeAndUsableFromInline;
|
||||||
|
|
||||||
WitnessChecker(TypeChecker &tc, ProtocolDecl *proto,
|
WitnessChecker(TypeChecker &tc, ProtocolDecl *proto,
|
||||||
Type adoptee, DeclContext *dc);
|
Type adoptee, DeclContext *dc);
|
||||||
|
|
||||||
bool isMemberOperator(FuncDecl *decl, Type type);
|
bool isMemberOperator(FuncDecl *decl, Type type);
|
||||||
|
|
||||||
|
AccessScope getRequiredAccessScope();
|
||||||
|
|
||||||
|
bool isUsableFromInlineRequired() {
|
||||||
|
if (!TC.getLangOpts().EnableAccessControl)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
assert(RequiredAccessScopeAndUsableFromInline.hasValue() &&
|
||||||
|
"must check access first using getRequiredAccessScope");
|
||||||
|
return RequiredAccessScopeAndUsableFromInline.getValue().second;
|
||||||
|
}
|
||||||
|
|
||||||
/// Gather the value witnesses for the given requirement.
|
/// Gather the value witnesses for the given requirement.
|
||||||
///
|
///
|
||||||
/// \param ignoringNames If non-null and there are no value
|
/// \param ignoringNames If non-null and there are no value
|
||||||
@@ -501,8 +516,7 @@ protected:
|
|||||||
unsigned &bestIdx,
|
unsigned &bestIdx,
|
||||||
bool &doNotDiagnoseMatches);
|
bool &doNotDiagnoseMatches);
|
||||||
|
|
||||||
bool checkWitnessAccess(AccessScope &requiredAccessScope,
|
bool checkWitnessAccess(ValueDecl *requirement,
|
||||||
ValueDecl *requirement,
|
|
||||||
ValueDecl *witness,
|
ValueDecl *witness,
|
||||||
bool *isSetter);
|
bool *isSetter);
|
||||||
|
|
||||||
@@ -510,8 +524,7 @@ protected:
|
|||||||
ValueDecl *witness,
|
ValueDecl *witness,
|
||||||
AvailabilityContext *requirementInfo);
|
AvailabilityContext *requirementInfo);
|
||||||
|
|
||||||
RequirementCheck checkWitness(AccessScope requiredAccessScope,
|
RequirementCheck checkWitness(ValueDecl *requirement,
|
||||||
ValueDecl *requirement,
|
|
||||||
const RequirementMatch &match);
|
const RequirementMatch &match);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
42
test/Compatibility/attr_usableFromInline_protocol.swift
Normal file
42
test/Compatibility/attr_usableFromInline_protocol.swift
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift -swift-version 4
|
||||||
|
// RUN: %target-typecheck-verify-swift -swift-version 4.2
|
||||||
|
|
||||||
|
public protocol PublicProtoWithReqs {
|
||||||
|
associatedtype Assoc
|
||||||
|
func foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline struct UFIAdopter<T> : PublicProtoWithReqs {}
|
||||||
|
// expected-warning@-1 {{type alias 'Assoc' should be declared '@usableFromInline' because because it matches a requirement in protocol 'PublicProtoWithReqs'}} {{none}}
|
||||||
|
extension UFIAdopter {
|
||||||
|
typealias Assoc = Int
|
||||||
|
// expected-note@-1 {{'Assoc' declared here}}
|
||||||
|
func foo() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline struct UFIAdopterAllInOne<T> : PublicProtoWithReqs {
|
||||||
|
typealias Assoc = Int
|
||||||
|
// expected-warning@-1 {{type alias 'Assoc' should be declared '@usableFromInline' because because it matches a requirement in protocol 'PublicProtoWithReqs'}} {{none}}
|
||||||
|
func foo() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct InternalAdopter<T> : PublicProtoWithReqs {}
|
||||||
|
extension InternalAdopter {
|
||||||
|
typealias Assoc = Int // okay
|
||||||
|
func foo() {} // okay
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@usableFromInline protocol UFIProtoWithReqs {
|
||||||
|
associatedtype Assoc
|
||||||
|
func foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PublicAdopter<T> : UFIProtoWithReqs {}
|
||||||
|
// expected-warning@-1 {{type alias 'Assoc' should be declared '@usableFromInline' because because it matches a requirement in protocol 'UFIProtoWithReqs'}} {{none}}
|
||||||
|
extension PublicAdopter {
|
||||||
|
typealias Assoc = Int
|
||||||
|
// expected-note@-1 {{'Assoc' declared here}}
|
||||||
|
func foo() {}
|
||||||
|
}
|
||||||
|
extension InternalAdopter: UFIProtoWithReqs {} // okay
|
||||||
42
test/attr/attr_usableFromInline_protocol.swift
Normal file
42
test/attr/attr_usableFromInline_protocol.swift
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift -swift-version 5
|
||||||
|
// RUN: %target-swift-frontend -typecheck -disable-access-control %s
|
||||||
|
|
||||||
|
public protocol PublicProtoWithReqs {
|
||||||
|
associatedtype Assoc
|
||||||
|
func foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline struct UFIAdopter<T> : PublicProtoWithReqs {}
|
||||||
|
// expected-error@-1 {{type alias 'Assoc' must be declared '@usableFromInline' because because it matches a requirement in protocol 'PublicProtoWithReqs'}} {{none}}
|
||||||
|
extension UFIAdopter {
|
||||||
|
typealias Assoc = Int
|
||||||
|
// expected-note@-1 {{'Assoc' declared here}}
|
||||||
|
func foo() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline struct UFIAdopterAllInOne<T> : PublicProtoWithReqs {
|
||||||
|
typealias Assoc = Int
|
||||||
|
// expected-error@-1 {{type alias 'Assoc' must be declared '@usableFromInline' because because it matches a requirement in protocol 'PublicProtoWithReqs'}} {{none}}
|
||||||
|
func foo() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct InternalAdopter<T> : PublicProtoWithReqs {}
|
||||||
|
extension InternalAdopter {
|
||||||
|
typealias Assoc = Int // okay
|
||||||
|
func foo() {} // okay
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@usableFromInline protocol UFIProtoWithReqs {
|
||||||
|
associatedtype Assoc
|
||||||
|
func foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PublicAdopter<T> : UFIProtoWithReqs {}
|
||||||
|
// expected-error@-1 {{type alias 'Assoc' must be declared '@usableFromInline' because because it matches a requirement in protocol 'UFIProtoWithReqs'}} {{none}}
|
||||||
|
extension PublicAdopter {
|
||||||
|
typealias Assoc = Int
|
||||||
|
// expected-note@-1 {{'Assoc' declared here}}
|
||||||
|
func foo() {}
|
||||||
|
}
|
||||||
|
extension InternalAdopter: UFIProtoWithReqs {} // okay
|
||||||
Reference in New Issue
Block a user