[CodeCompletion] Suggest static members on protocol extensions with Self bound in unresolved member lookup

rdar://122758029
This commit is contained in:
Alex Hoppen
2024-02-20 13:11:44 -08:00
parent 420859cdb4
commit a176c07b8b
2 changed files with 73 additions and 2 deletions

View File

@@ -116,6 +116,31 @@ public:
}
};
/// Returns `true` if `ED` is an extension of `PD` that binds `Self` to a
/// concrete type, like `extension MyProto where Self == MyStruct {}`.
///
/// In these cases, it is possible to access static members defined in the
/// extension when perfoming unresolved member lookup in a type context of
/// `PD`.
static bool isExtensionWithSelfBound(const ExtensionDecl *ED,
ProtocolDecl *PD) {
if (!ED || !PD) {
return false;
}
if (ED->getExtendedNominal() != PD) {
return false;
}
GenericSignature genericSig = ED->getGenericSignature();
Type selfType = genericSig->getConcreteType(ED->getSelfInterfaceType());
if (!selfType) {
return false;
}
if (selfType->is<ExistentialType>()) {
return false;
}
return true;
}
static bool isExtensionAppliedInternal(const DeclContext *DC, Type BaseTy,
const ExtensionDecl *ED) {
// We can't do anything if the base type has unbound generic parameters.
@@ -130,8 +155,20 @@ static bool isExtensionAppliedInternal(const DeclContext *DC, Type BaseTy,
if (!ED->isConstrainedExtension())
return true;
GenericSignature genericSig = ED->getGenericSignature();
ProtocolDecl *BaseTypeProtocolDecl = nullptr;
if (auto opaqueType = dyn_cast<OpaqueTypeArchetypeType>(BaseTy)) {
if (opaqueType->getConformsTo().size() == 1) {
BaseTypeProtocolDecl = opaqueType->getConformsTo().front();
}
} else {
BaseTypeProtocolDecl = dyn_cast_or_null<ProtocolDecl>(BaseTy->getAnyNominal());
}
if (isExtensionWithSelfBound(ED, BaseTypeProtocolDecl)) {
return true;
}
auto *module = DC->getParentModule();
GenericSignature genericSig = ED->getGenericSignature();
SubstitutionMap substMap = BaseTy->getContextSubstitutionMap(
module, ED->getExtendedNominal());
return checkRequirements(module,
@@ -142,7 +179,10 @@ static bool isExtensionAppliedInternal(const DeclContext *DC, Type BaseTy,
static bool isMemberDeclAppliedInternal(const DeclContext *DC, Type BaseTy,
const ValueDecl *VD) {
if (BaseTy->isExistentialType() && VD->isStatic())
if (BaseTy->isExistentialType() && VD->isStatic() &&
!isExtensionWithSelfBound(
dyn_cast<ExtensionDecl>(VD->getDeclContext()),
dyn_cast_or_null<ProtocolDecl>(BaseTy->getAnyNominal())))
return false;
// We can't leak type variables into another constraint system.

View File

@@ -0,0 +1,31 @@
// RUN: %batch-code-completion
protocol MyProto {}
protocol MyOtherProto: MyProto {}
struct MyStruct : MyProto {}
extension MyProto where Self == MyStruct {
static var constrainedOnMyStruct: MyStruct { fatalError() }
}
extension MyProto where Self: MyOtherProto {
static var constrainedOnMyInheritanceOfOtherProto: MyOtherProto { fatalError() }
}
extension MyProto where Self == MyOtherProto {
static var constrainedOnMyEqualityOfOtherProto: MyOtherProto { fatalError() }
}
func testOnMyProto() {
let _: MyProto = .#^ON_MY_PROTO^#
// ON_MY_PROTO: Begin completions, 1 items
// ON_MY_PROTO-DAG: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: constrainedOnMyStruct[#MyStruct#];
}
func testOnMyOtherProto() {
// constrainedOnMyStruct is not valid here
let _: MyOtherProto = .#^ON_MY_OTHER_PROTO^#
// ON_MY_OTHER_PROTO-NOT: Begin completions
}
func testOpaqueMyProto() -> some MyProto {
return .#^IN_OPAQUE_PROTOCOL_POS?check=ON_MY_PROTO^#
}