Skip to content

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

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 1 commit into from
Mar 1, 2024
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
44 changes: 42 additions & 2 deletions lib/Sema/IDETypeCheckingRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,31 @@ class ContainsSpecializableArchetype : public TypeWalker {
}
};

/// 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.
Expand All @@ -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,
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Contributor

@slavapestov slavapestov Feb 22, 2024

Choose a reason for hiding this comment

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

Shouldn't this be rejected? (Protocol doesn't self-conform)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, but I still want to check that we don’t suggest it in code completion.

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#];
}
Copy link
Member

Choose a reason for hiding this comment

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

Could you add a test case for a case like

func testOpaqueMyProto() -> some MyProto {
   return .#^HERE^#
}

which doesn't seems work either currently.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, good idea. We handle it now.


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^#
}